Fix unreadable event text when a calendar's color is white/very light
The month view rendered each event's text (and its little leading dot) in the calendar's own color, on a very lightly tinted (~13% opacity) background of that same color. For a light color like white or pale yellow, the text ended up effectively invisible against its own near-white background. internal/web/templates/calendar.templ: add eventTextColor(hex), which darkens colors above a perceived-luminance threshold (keeping the hue, e.g. white -> a mid gray, pale yellow -> olive) while leaving already- legible colors untouched; used for both the event text and its leading dot. Also add a subtle ring to the event dot and to the calendar legend's color swatch so a white/near-white swatch stays visible against the page's white background, independent of the text-color fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package templates
|
||||
|
||||
import "fmt"
|
||||
import "strconv"
|
||||
import "strings"
|
||||
|
||||
// CalendarSummary is one calendar (own or shared) shown in the combined
|
||||
// month view's legend.
|
||||
@@ -119,7 +121,7 @@ templ MonthView(username string, data MonthViewData) {
|
||||
|
||||
templ calendarLegendRow(c CalendarSummary) {
|
||||
<div class="px-4 py-3 flex items-center gap-3 text-sm flex-wrap">
|
||||
<span class="w-3 h-3 rounded-full shrink-0" style={ "background-color: " + colorOrDefault(c.Color) }></span>
|
||||
<span class="w-3 h-3 rounded-full shrink-0 ring-1 ring-inset ring-black/10" style={ "background-color: " + colorOrDefault(c.Color) }></span>
|
||||
<span class="font-medium">{ c.Name }</span>
|
||||
if c.Shared {
|
||||
<span class="text-xs text-gray-500">shared by { c.Owner }</span>
|
||||
@@ -155,10 +157,10 @@ templ monthDayCell(day MonthDay) {
|
||||
<a
|
||||
href={ templ.URL(eventLinkURL(ev)) }
|
||||
class="block truncate rounded px-1.5 py-0.5 text-xs hover:opacity-80"
|
||||
style={ "background-color: " + colorOrDefault(ev.Color) + "22; color: " + colorOrDefault(ev.Color) }
|
||||
style={ "background-color: " + colorOrDefault(ev.Color) + "22; color: " + eventTextColor(ev.Color) }
|
||||
title={ ev.Summary }
|
||||
>
|
||||
<span class="inline-block w-1.5 h-1.5 rounded-full mr-1" style={ "background-color: " + colorOrDefault(ev.Color) }></span>
|
||||
<span class="inline-block w-1.5 h-1.5 rounded-full mr-1 ring-1 ring-inset ring-black/10" style={ "background-color: " + eventTextColor(ev.Color) }></span>
|
||||
if !ev.AllDay && ev.TimeText != "" {
|
||||
<span class="font-medium">{ ev.TimeText }</span>
|
||||
}
|
||||
@@ -175,6 +177,55 @@ func eventLinkURL(ev EventSummary) string {
|
||||
return "/web/calendar/" + ev.CalRef + "/" + ev.ID + "/edit"
|
||||
}
|
||||
|
||||
// eventTextColor returns a color derived from hex, darkened if needed so
|
||||
// it stays legible as text on monthDayCell's very light (~13% opacity)
|
||||
// tinted event background. Without this, a light calendar color like
|
||||
// white or pale yellow renders its own event text as invisible or
|
||||
// near-invisible, since the same color is used for both the tint and the
|
||||
// text. Colors that are already dark enough are returned unchanged.
|
||||
func eventTextColor(hex string) string {
|
||||
c := colorOrDefault(hex)
|
||||
r, g, b, ok := parseHexColor(c)
|
||||
if !ok {
|
||||
return c
|
||||
}
|
||||
// Perceived luminance (ITU-R BT.601).
|
||||
luminance := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
|
||||
const maxReadableLuminance = 170
|
||||
if luminance <= maxReadableLuminance {
|
||||
return c
|
||||
}
|
||||
// Scale each channel down so the color darkens towards black while
|
||||
// keeping its hue, ending up comfortably below the threshold.
|
||||
scale := maxReadableLuminance / luminance * 0.75
|
||||
return fmt.Sprintf("#%02x%02x%02x", clampByte(float64(r)*scale), clampByte(float64(g)*scale), clampByte(float64(b)*scale))
|
||||
}
|
||||
|
||||
// parseHexColor parses a "#rrggbb" string into its red/green/blue
|
||||
// components. ok is false for anything else (including 3-digit shorthand
|
||||
// hex, which colorOrDefault/hexColorRe never produce).
|
||||
func parseHexColor(s string) (r, g, b int, ok bool) {
|
||||
s = strings.TrimPrefix(s, "#")
|
||||
if len(s) != 6 {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 16, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
return int(v >> 16 & 0xff), int(v >> 8 & 0xff), int(v & 0xff), true
|
||||
}
|
||||
|
||||
func clampByte(v float64) int {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if v > 255 {
|
||||
return 255
|
||||
}
|
||||
return int(v)
|
||||
}
|
||||
|
||||
|
||||
|
||||
templ EventForm(username string, data EventFormData, errMsg string) {
|
||||
|
||||
@@ -9,6 +9,8 @@ import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import "fmt"
|
||||
import "strconv"
|
||||
import "strings"
|
||||
|
||||
// CalendarSummary is one calendar (own or shared) shown in the combined
|
||||
// month view's legend.
|
||||
@@ -121,7 +123,7 @@ func MonthView(username string, data MonthViewData) templ.Component {
|
||||
var templ_7745c5c3_Var3 templ.SafeURL
|
||||
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(data.PrevMonthURL))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 81, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 83, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -134,7 +136,7 @@ func MonthView(username string, data MonthViewData) templ.Component {
|
||||
var templ_7745c5c3_Var4 templ.SafeURL
|
||||
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(data.TodayURL))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 82, Col: 38}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 84, Col: 38}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -147,7 +149,7 @@ func MonthView(username string, data MonthViewData) templ.Component {
|
||||
var templ_7745c5c3_Var5 templ.SafeURL
|
||||
templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(data.NextMonthURL))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 83, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 85, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -160,7 +162,7 @@ func MonthView(username string, data MonthViewData) templ.Component {
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(data.MonthLabel)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 84, Col: 60}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 86, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -198,7 +200,7 @@ func MonthView(username string, data MonthViewData) templ.Component {
|
||||
var templ_7745c5c3_Var7 string
|
||||
templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(wd)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 102, Col: 91}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 104, Col: 91}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -263,14 +265,14 @@ func calendarLegendRow(c CalendarSummary) templ.Component {
|
||||
templ_7745c5c3_Var8 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"px-4 py-3 flex items-center gap-3 text-sm flex-wrap\"><span class=\"w-3 h-3 rounded-full shrink-0\" style=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "<div class=\"px-4 py-3 flex items-center gap-3 text-sm flex-wrap\"><span class=\"w-3 h-3 rounded-full shrink-0 ring-1 ring-inset ring-black/10\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var9 string
|
||||
templ_7745c5c3_Var9, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-color: " + colorOrDefault(c.Color))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 122, Col: 100}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 124, Col: 132}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -283,7 +285,7 @@ func calendarLegendRow(c CalendarSummary) templ.Component {
|
||||
var templ_7745c5c3_Var10 string
|
||||
templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(c.Name)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 123, Col: 36}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 125, Col: 36}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -301,7 +303,7 @@ func calendarLegendRow(c CalendarSummary) templ.Component {
|
||||
var templ_7745c5c3_Var11 string
|
||||
templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(c.Owner)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 125, Col: 58}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 127, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -330,7 +332,7 @@ func calendarLegendRow(c CalendarSummary) templ.Component {
|
||||
var templ_7745c5c3_Var12 templ.SafeURL
|
||||
templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL("/web/calendar/" + c.Ref + "/export"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 132, Col: 60}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 134, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -349,7 +351,7 @@ func calendarLegendRow(c CalendarSummary) templ.Component {
|
||||
var templ_7745c5c3_Var13 templ.SafeURL
|
||||
templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL("/web/calendar/" + c.Ref + "/import"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 135, Col: 79}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 137, Col: 79}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -423,7 +425,7 @@ func monthDayCell(day MonthDay) templ.Component {
|
||||
var templ_7745c5c3_Var18 templ.SafeURL
|
||||
templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL("/web/calendar/new?date=" + day.Date))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 147, Col: 58}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 149, Col: 58}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -449,7 +451,7 @@ func monthDayCell(day MonthDay) templ.Component {
|
||||
var templ_7745c5c3_Var20 string
|
||||
templ_7745c5c3_Var20, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint(day.Day))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 151, Col: 25}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 153, Col: 25}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var20))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -467,7 +469,7 @@ func monthDayCell(day MonthDay) templ.Component {
|
||||
var templ_7745c5c3_Var21 templ.SafeURL
|
||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL(eventLinkURL(ev)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 156, Col: 38}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 158, Col: 38}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -478,9 +480,9 @@ func monthDayCell(day MonthDay) templ.Component {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-color: " + colorOrDefault(ev.Color) + "22; color: " + colorOrDefault(ev.Color))
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-color: " + colorOrDefault(ev.Color) + "22; color: " + eventTextColor(ev.Color))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 158, Col: 102}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 160, Col: 102}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -493,20 +495,20 @@ func monthDayCell(day MonthDay) templ.Component {
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.ResolveAttributeValue(ev.Summary)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 159, Col: 22}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 161, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var23)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"><span class=\"inline-block w-1.5 h-1.5 rounded-full mr-1\" style=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "\"><span class=\"inline-block w-1.5 h-1.5 rounded-full mr-1 ring-1 ring-inset ring-black/10\" style=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-color: " + colorOrDefault(ev.Color))
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templruntime.SanitizeStyleAttributeValues("background-color: " + eventTextColor(ev.Color))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 161, Col: 116}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 163, Col: 148}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -524,7 +526,7 @@ func monthDayCell(day MonthDay) templ.Component {
|
||||
var templ_7745c5c3_Var25 string
|
||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(ev.TimeText)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 163, Col: 44}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 165, Col: 44}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -538,7 +540,7 @@ func monthDayCell(day MonthDay) templ.Component {
|
||||
var templ_7745c5c3_Var26 string
|
||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(" " + ev.Summary)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 165, Col: 22}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 167, Col: 22}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -564,6 +566,55 @@ func eventLinkURL(ev EventSummary) string {
|
||||
return "/web/calendar/" + ev.CalRef + "/" + ev.ID + "/edit"
|
||||
}
|
||||
|
||||
// eventTextColor returns a color derived from hex, darkened if needed so
|
||||
// it stays legible as text on monthDayCell's very light (~13% opacity)
|
||||
// tinted event background. Without this, a light calendar color like
|
||||
// white or pale yellow renders its own event text as invisible or
|
||||
// near-invisible, since the same color is used for both the tint and the
|
||||
// text. Colors that are already dark enough are returned unchanged.
|
||||
func eventTextColor(hex string) string {
|
||||
c := colorOrDefault(hex)
|
||||
r, g, b, ok := parseHexColor(c)
|
||||
if !ok {
|
||||
return c
|
||||
}
|
||||
// Perceived luminance (ITU-R BT.601).
|
||||
luminance := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
|
||||
const maxReadableLuminance = 170
|
||||
if luminance <= maxReadableLuminance {
|
||||
return c
|
||||
}
|
||||
// Scale each channel down so the color darkens towards black while
|
||||
// keeping its hue, ending up comfortably below the threshold.
|
||||
scale := maxReadableLuminance / luminance * 0.75
|
||||
return fmt.Sprintf("#%02x%02x%02x", clampByte(float64(r)*scale), clampByte(float64(g)*scale), clampByte(float64(b)*scale))
|
||||
}
|
||||
|
||||
// parseHexColor parses a "#rrggbb" string into its red/green/blue
|
||||
// components. ok is false for anything else (including 3-digit shorthand
|
||||
// hex, which colorOrDefault/hexColorRe never produce).
|
||||
func parseHexColor(s string) (r, g, b int, ok bool) {
|
||||
s = strings.TrimPrefix(s, "#")
|
||||
if len(s) != 6 {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
v, err := strconv.ParseInt(s, 16, 32)
|
||||
if err != nil {
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
return int(v >> 16 & 0xff), int(v >> 8 & 0xff), int(v & 0xff), true
|
||||
}
|
||||
|
||||
func clampByte(v float64) int {
|
||||
if v < 0 {
|
||||
return 0
|
||||
}
|
||||
if v > 255 {
|
||||
return 255
|
||||
}
|
||||
return int(v)
|
||||
}
|
||||
|
||||
func EventForm(username string, data EventFormData, errMsg string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
@@ -624,7 +675,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var29 string
|
||||
templ_7745c5c3_Var29, templ_7745c5c3_Err = templ.JoinStringErrs(errMsg)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 191, Col: 98}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 242, Col: 98}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var29))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -652,7 +703,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var30 templ.SafeURL
|
||||
templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinURLErrs(eventFormAction(data))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 200, Col: 33}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 251, Col: 33}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -685,7 +736,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var31 string
|
||||
templ_7745c5c3_Var31, templ_7745c5c3_Err = templ.ResolveAttributeValue(c.Ref)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 209, Col: 28}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 260, Col: 28}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var31)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -708,7 +759,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var32 string
|
||||
templ_7745c5c3_Var32, templ_7745c5c3_Err = templ.JoinStringErrs(c.Label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 209, Col: 75}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 260, Col: 75}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var32))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -731,7 +782,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var33 string
|
||||
templ_7745c5c3_Var33, templ_7745c5c3_Err = templ.JoinStringErrs(data.CalendarLabel)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 213, Col: 63}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 264, Col: 63}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var33))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -749,7 +800,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var34 string
|
||||
templ_7745c5c3_Var34, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Summary)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 219, Col: 67}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 270, Col: 67}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var34)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -772,7 +823,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var35 string
|
||||
templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.StartDate)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 231, Col: 73}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 282, Col: 73}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var35)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -807,7 +858,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var38 string
|
||||
templ_7745c5c3_Var38, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.StartTime)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 236, Col: 64}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 287, Col: 64}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var38)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -820,7 +871,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var39 string
|
||||
templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.EndDate)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 241, Col: 69}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 292, Col: 69}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var39)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -855,7 +906,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var42 string
|
||||
templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.EndTime)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 246, Col: 60}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 297, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var42)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -868,7 +919,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var43 string
|
||||
templ_7745c5c3_Var43, templ_7745c5c3_Err = templ.ResolveAttributeValue(data.Location)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 253, Col: 60}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 304, Col: 60}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var43)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -881,7 +932,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var44 string
|
||||
templ_7745c5c3_Var44, templ_7745c5c3_Err = templ.JoinStringErrs(data.Description)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 259, Col: 103}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 310, Col: 103}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var44))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -905,7 +956,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var45 templ.SafeURL
|
||||
templ_7745c5c3_Var45, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL("/web/calendar/" + data.CalRef + "/" + data.ID + "/delete"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 270, Col: 103}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 321, Col: 103}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var45))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@@ -924,7 +975,7 @@ func EventForm(username string, data EventFormData, errMsg string) templ.Compone
|
||||
var templ_7745c5c3_Var46 templ.SafeURL
|
||||
templ_7745c5c3_Var46, templ_7745c5c3_Err = templ.JoinURLErrs(templ.URL("/web/calendar/" + data.CalRef + "/" + data.ID + "/export"))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 275, Col: 84}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/web/templates/calendar.templ`, Line: 326, Col: 84}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var46))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package templates
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestEventTextColorKeepsReadableColorsUnchanged covers the reported bug:
|
||||
// picking white (or any other very light color) for a calendar made its
|
||||
// events unreadable in the month view, since monthDayCell used the same
|
||||
// color for both the (barely tinted) background and the text. Light
|
||||
// colors must be darkened for text use; colors that were already fine
|
||||
// must render exactly as chosen.
|
||||
func TestEventTextColorKeepsReadableColorsUnchanged(t *testing.T) {
|
||||
for _, c := range []string{"#3b82f6", "#ec4899", "#10b981", "#000000"} {
|
||||
if got := eventTextColor(c); got != c {
|
||||
t.Errorf("eventTextColor(%s) = %s, want unchanged", c, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventTextColorDarkensLightColors(t *testing.T) {
|
||||
for _, c := range []string{"#ffffff", "#ffff00", "#f0f0f0", "#fefefe"} {
|
||||
got := eventTextColor(c)
|
||||
if got == c {
|
||||
t.Errorf("eventTextColor(%s) = %s, want darkened for readability", c, got)
|
||||
}
|
||||
r, g, b, ok := parseHexColor(got)
|
||||
if !ok {
|
||||
t.Fatalf("eventTextColor(%s) returned unparseable color %s", c, got)
|
||||
}
|
||||
luminance := 0.299*float64(r) + 0.587*float64(g) + 0.114*float64(b)
|
||||
if luminance > 170 {
|
||||
t.Errorf("eventTextColor(%s) = %s still too light (luminance %.1f)", c, got, luminance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventTextColorFallsBackForInvalidInput(t *testing.T) {
|
||||
if got := eventTextColor(""); got != colorOrDefault("") {
|
||||
t.Errorf("eventTextColor(\"\") = %s, want the same default colorOrDefault returns", got)
|
||||
}
|
||||
if got := eventTextColor("not-a-color"); got != "not-a-color" {
|
||||
t.Errorf("eventTextColor(unparseable) = %s, want passed through unchanged", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user