The banner gains a short cast line (put away while the controls are up), and the details (I) show the whole description now that Schedules Direct's run long. Network names are matched whatever their case, so Schedules Direct's "Public Broadcasting Service" is PBS again. Schedules Direct also has each station's logo drawn for a dark background, in white: the guide shows that one on itself rather than the colored one on a light chip, which stays for listings without one.
- commit
- d0bba5f90c9f7ba23be0e1def006e7a9d7cc0166
- parent
- 2d030fb47ff44863b02338be3c070a8a50642d14
- author
- FelineStateMachine <226808055+FelineStateMachine@users.noreply.github.com>
- kind
- feat risk score 1.5 of 3, Jev's estimate
| M | frontend/ | 6 | ++++-- |
| M | frontend/ | 1 | + |
| M | frontend/ | 7 | +++++++ |
| M | internal/ | 7 | ++++++- |
| M | internal/ | 4 | ++++ |
| M | internal/ | 6 | ++++-- |
| M | internal/ | 2 | +- |
| M | internal/ | 9 | ++++++--- |
@@ -747,7 +747,7 @@ function firstAired(d) {
747747 }748748 749749 // castLine names a program's cast: "With Missy Peregrym, Zeeko Zaki".750-const castLine = (p) => (p && p.cast && p.cast.length ? `With ${p.cast.slice(0, 5).map((c) => c.replace(/ \(.*\)$/, '')).join(', ')}` : '');750+const castLine = (p, n = 5) => (p && p.cast && p.cast.length ? `With ${p.cast.slice(0, n).map((c) => c.replace(/ \(.*\)$/, '')).join(', ')}` : '');751751 752752 function renderBanner() {753753 updateMediaSession();@@ -772,6 +772,7 @@ function renderBanner() {
772772 f(b, 'chips').innerHTML = (rec ? `<span class="chip live">${rec.status === 'recording' ? 'Recording' : 'Will record'}</span>` : '') + chipsFor(p, ch);773773 f(b, 'title').textContent = p ? p.title : 'No listings';774774 f(b, 'ep').textContent = episodeLine(p);775+ f(b, 'cast').textContent = castLine(p, 3);775776 f(b, 'progress').style.width = p ? `${Math.min(100, ((now - p._s) / (p._e - p._s)) * 100)}%` : '0';776777 f(b, 'desc').textContent = p ? p.description || '' : ch.description || '';777778 f(b, 'next').textContent = nx ? `${clock(nx._s)} ${nx.title}` : '';@@ -2090,7 +2091,8 @@ function renderGuide() {
20902091 html += `<div class="${cls}"><div class="g-ch" data-row="${r}">20912092 <div class="num${ch.number.length > 4 ? ' long' : ''}">${esc(ch.number)}</div>20922093 <div class="who"><div class="net">${isFav(ch) ? '<span class="fav">★</span>' : ''}${esc(ch.network || displayCall(ch))}</div><div class="call"${bad ? ` title="${esc(`${sig.label}: ${sigDetail(sig)}`)}"` : ''}>${bad ? meter(sig) : ''}<span>${esc(displayCall(ch))}</span></div></div>2093- ${ch.logo ? `<div class="logo${ch.own ? ' own' : ''}" style="background-image:url('${esc(ch.logo)}')"></div>` : '<div></div>'}2094+ ${ch.logoDark ? `<div class="logo own" style="background-image:url('${esc(ch.logoDark)}')"></div>`2095+ : ch.logo ? `<div class="logo${ch.own ? ' own' : ''}" style="background-image:url('${esc(ch.logo)}')"></div>` : '<div></div>'}20942096 </div><div class="g-progs">`;20952097 const progs = programsFor(ch).filter((p) => p._e > lo && p._s < hi);20962098 if (!progs.length) {@@ -41,6 +41,7 @@
4141 <div class="bn-kicker"><span class="bn-label">Now</span><span data-f="time"></span><span class="bn-shift" data-f="shift"></span><span class="chips" data-f="chips"></span></div>4242 <h2 class="bn-title" data-f="title"></h2>4343 <div class="bn-ep" data-f="ep"></div>44+ <div class="bn-cast" data-f="cast"></div>4445 <div class="bn-progress"><i data-f="progress"></i></div>4546 <p class="bn-desc" data-f="desc"></p>4647 <div class="bn-next"><span class="bn-label">Next</span><span data-f="next"></span></div>@@ -153,6 +153,10 @@ body.mode-tv #banner.show { transform: none; opacity: 1; }
153153 .bn-progress { height: 3px; background: var(--line-2); margin: 12px 0 10px; border-radius: 2px; overflow: hidden; }154154 .bn-progress i { display: block; height: 100%; background: var(--amber); width: 0; transition: width .6s; }155155 .bn-desc { margin: 0; color: var(--muted); display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; max-width: 72ch; }156+.bn-cast { color: var(--muted); font-size: .92em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 72ch; margin-top: 2px; }157+.bn-cast:empty, #banner.osd .bn-cast { display: none; }158+/* The details (I) have room for the whole description. */159+#banner.detail .bn-desc { -webkit-line-clamp: 6; }156160 .bn-next { display: flex; gap: 10px; align-items: center; margin-top: 12px; font-size: 14px; }157161 .bn-signal { border-left: 1px solid var(--line); padding-left: 24px; display: none; flex-direction: column; gap: 8px; justify-content: center; }158162 #banner.detail, #banner.warn { grid-template-columns: 210px minmax(0, 1fr) 270px; }@@ -243,6 +247,9 @@ body.mode-guide #guide, body.mode-antenna #antenna, body.mode-settings #settings
243247 .g-ch .who { min-width: 0; }244248 .g-ch .call { font: 400 11px var(--f-mono); letter-spacing: .06em; color: var(--muted); display: flex; gap: 6px; align-items: center; }245249 .g-ch .net { font-size: 13px; font-weight: 500; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }250+/* A station's logo drawn for a light background sits on a light chip; one251+ drawn for the dark (white, from Schedules Direct) and the server's own252+ channels' sit on the guide itself. */246253 .g-ch .logo { width: 44px; height: 26px; border-radius: 2px; background: #e9e3d4 center/80% no-repeat; }247254 .g-ch .logo.own { background: center/contain no-repeat; }248255 .g-ch .num.long { font-size: 22px; }@@ -35,6 +35,9 @@ type Channel struct {
3535 CallSign string `json:"callSign"` // "KWGNDT"3636 Network string `json:"network"`3737 Logo string `json:"logo"`38+ // LogoDark is the logo drawn for a dark background (white), when the39+ // listings have one: Schedules Direct's do.40+ LogoDark string `json:"logoDark,omitempty"`3841 }3942 4043 // Program is one airing.@@ -280,7 +283,9 @@ var acronyms = map[string]bool{
280283 // NetworkName is an affiliate's name as the guide shows it: "NBC" for281284 // "NATIONAL BROADCASTING COMPANY", "Start TV" for "START TV NETWORK".282285 func NetworkName(affiliate string) string {283- if n, ok := networkNames[affiliate]; ok {286+ // Gracenote's grid writes them in capitals, Schedules Direct not287+ // always.288+ if n, ok := networkNames[strings.ToUpper(affiliate)]; ok {284289 return n285290 }286291 if strings.HasPrefix(affiliate, "METV") {@@ -2,6 +2,7 @@ package guide
22 33 import (44 "slices"5+ "strings"56 "testing"67 )78 @@ -33,6 +34,9 @@ func TestNetworkName(t *testing.T) {
3334 "METV TOONS": "MeTV Toons",3435 "SHOP LC": "Shop LC",3536 } {37+ if got := NetworkName(strings.ToLower(in)); networkNames[in] != "" && got != want {38+ t.Errorf("NetworkName(%q) = %q, want %q", strings.ToLower(in), got, want)39+ }3640 if got := NetworkName(in); got != want {3741 t.Errorf("NetworkName(%q) = %q, want %q", in, got, want)3842 }@@ -38,12 +38,13 @@ type TunerChannel struct {
3838 CallSign string `json:"callSign"`3939 BaseCall string `json:"baseCall"`4040 // GuideID, GuideCallSign ("KWGNDT", "KCNCDT2"), Network ("CW",41- // "Start TV") and Logo come from the listings, when they have the42- // channel.41+ // "Start TV"), Logo and LogoDark (for a dark background) come from the42+ // listings, when they have the channel.4343 GuideID string `json:"guideId,omitempty"`4444 GuideCallSign string `json:"guideCallSign,omitempty"`4545 Network string `json:"network"`4646 Logo string `json:"logo,omitempty"`47+ LogoDark string `json:"logoDark,omitempty"`4748 // RF is the RF channel the tuner receives it on; 0 when unknown.4849 RF int `json:"rf"`4950 // FacilityID and Transmitter are the licensed transmitter on RF that@@ -155,6 +156,7 @@ func Match(rep *Report, g *guide.Guide, found []Scanned) []TunerChannel {
155156 }156157 if gc, ok := pickListing(listed[f.Number], nameBase(f.Name), t); ok {157158 ch.GuideID, ch.GuideCallSign, ch.BaseCall, ch.Network, ch.Logo = gc.ID, gc.CallSign, guide.BaseCall(gc.CallSign), gc.Network, gc.Logo159+ ch.LogoDark = gc.LogoDark158160 } else if t != nil && t.VirtualChannel == major {159161 ch.BaseCall = t.BaseCall160162 } else if s := byVirtual[major]; s != nil {@@ -195,7 +195,7 @@ func TestUpdate(t *testing.T) {
195195 }196196 197197 g := s.Guide()198- if g == nil || len(g.Channels) != 2 || g.Channels[0].Number != "2.1" || g.Channels[0].CallSign != "KWGNDT" || g.Channels[0].Logo != "https://example.com/kwgn-light.png" {198+ if g == nil || len(g.Channels) != 2 || g.Channels[0].Number != "2.1" || g.Channels[0].CallSign != "KWGNDT" || g.Channels[0].Logo != "https://example.com/kwgn-light.png" || g.Channels[0].LogoDark != "https://example.com/kwgn-dark.png" {199199 t.Fatalf("guide channels = %+v", g)200200 }201201 ps := g.Programs["20370"]@@ -52,8 +52,11 @@ type station struct {
5252 Logo string `json:"logo,omitempty"`5353 }5454 55-// logo is the station's logo for the guide's light logo chips: the one56-// drawn for a light background, else gray, else any.55+// logoDark is the station's logo drawn for a dark background: white.56+func (s station) logoDark() string { return cmp.Or(s.Logos["dark"], s.Logos["white"]) }57+58+// logo is the station's logo for a light background: the colored one,59+// else gray, else any.5760 func (s station) logo() string {5861 for _, c := range []string{"light", "gray"} {5962 if u := s.Logos[c]; u != "" {@@ -240,7 +243,7 @@ func (st *state) guide(zip string, now time.Time) *guide.Guide {
240243 from := now.Add(-24 * time.Hour)241244 for _, sta := range st.Stations {242245 g.Channels = append(g.Channels, guide.Channel{243- ID: sta.ID, Number: sta.Number, CallSign: sta.CallSign, Network: guide.NetworkName(sta.Affiliate), Logo: sta.logo(),246+ ID: sta.ID, Number: sta.Number, CallSign: sta.CallSign, Network: guide.NetworkName(sta.Affiliate), Logo: sta.logo(), LogoDark: sta.logoDark(),244247 })245248 for _, d := range st.Days[sta.ID] {246249 for _, a := range d.Airings {