Typescript TV Project
How it works
A Raspberry Pi with the Chromium browser full screen showing the listings page is plugged into an HDMI port on my TV. There is no user interaction other than switching TV inputs. The grid refreshes and scrolls to the bottom automatically after 10 seconds, then repeats again from the top.
The code
Here is an overview of how it operates:
- The boot.ts script calls "function "main()" in the app.ts after window DOMContentLoaded is invoked.
- The app.ts main asks for JSON data of the current listing through an XMLHttpRequest using the endpoint `./?method=listings&start=${start}&end=&${end}`.
- A series of collections returned from the parsed JSON are iterated over to build the HTML that is the listings content.
- The cell widths are calculated using show start/end times and some extra adornments are added, such as "cont'd", and start/end times in human readable form.
- To scroll the grid a CSS transition is calculated based on the size of the browser window, channels listed, and the cell height of each row.
- A dynamic stylesheet is requested based on that information. See http://tv.codebot.org/?method=css&height=1337 as an example.
- When the hour changes new data listings JSON is requested and the process repeats.
My base Typescript library with configuration files for the tcs compiler and visual studio code is hosted on github.
The grid isn't a component or anything like that, it's just html divs with a stylesheet. When the window is resized or new show data is pulled in, this bit of Typescript is invoked.
function doResize() { function getShow(cell: HTMLElement): ShowData { let channel = parseInt(cell.getAttribute("channel-index")); let show = parseInt(cell.getAttribute("show-index")); return state.listings.Channels[channel].Shows[show]; } // channelWidth is the blue area with the channel number const channelWidth = 146; // if resized scroll, revert the css transition state.block.removeClass("bottom"); // the width of a cell, or 30 minutes let width = (window.innerWidth - channelWidth) / 4.0; for (let cell of getAll("div.show")) { // retrieve the show let show = getShow(cell); let d = show.Duration; // truncate the duration if show is cont'd if (show.Start < now.time) d -= minutesBetween(now.time, show.Start); // calculate the cell width let w = (d / 30.0 * width) - 2.0; // and left starting position let x = channelWidth; // offset the cell left based on start time if (show.Start > now.time) x += minutesBetween(show.Start, now.time) / 30 * width; // and finally reposition the cell cell.style.left = x + "px" cell.style.width = w + "px"; } }I'm using just plain HTML and CSS with some Typescript to figure out how cells (again div markup) are positioned. So no components are being used, just a Typescript function, partially listed below, that updates the grid headers (more divs) and creates channel rows (again more divs).
Arguably the most useful feature of Typescript is that you get type information while you work. When working with HTML elements and I get code insight as I type notifying me of element types properties, events, and methods. In my case I'm just setting the contents of an element using "innerHTML = html", but it's great to be able to examine the DOM and anything else while I type.
Another great thing Typescript feature is literal templates, that is available even if the user's browser doesn't support it. This allows for easy html generation in Typescript:
state.listings = data; let html = ""; let channelIndex = 0; for (let c of data.Channels) { html += ` <div class="row"> <div class="column channel"> <div class="logo"><img src="https://imageserver/guide/${c.Logo}?width=48"></div> <div class="channel">${c.Channel}</div> <div class="sign">${c.Sign}</div> </div>`; let showIndex = 0; for (let s of c.Shows) { // and so on building htmlThe "state.listings" object is a parsed JSON object as returned from the C# backend. If you go to the TV page linked in the original post, open your developer tools, hit the console tab and type "state.listings", you can see how it's structured.
The backend code is written in C#. It's based on my simple POCO IHttpHandler class library that is also hosted on github. HomePage returns a html template that can be filled out by properties on the page. You can also tag methods on the page for invocation either through query strings or form submits (POST or GET requests).
The page class specifies template "listings.htm" using an attribute. It has 3 methods. Method "css" returns the content of css based on an input height query string. Method "scan" is called by a cron job once a day to get new tv listing data. Method "listings" returns JSON data for my favorite channels given a start and end time corresponding to a long returned by the JavaScript Date.now() function.
Here is the listing of the backend code.
using System; using Codebot.Web; namespace Codebot.Television.Web { [DefaultPage("listings.htm")] public class HomePage : PageHandler { [MethodPage("css")] public void MethodCss() { Response.ContentType = "text/css"; var s = String.Format("#listings.bottom {{\ntop: -{0}px;\ntransition: top linear 10s;\n}}", Read("height")); Response.Write(s); } [MethodPage("scan")] public void MethodScan() { var listings = new Model.ListingData(); try { listings.Scan(); Response.Write("OK"); } catch (Exception e) { Response.Write(e); } } [MethodPage("listings")] public void MethodListings() { try { var start = Read<long>("start", 0); var end = Read<long>("end", 0); var listings = new Model.ListingData(); listings.Load(start, end); Response.Write(listings); } catch { Response.Write("Error"); } } } }
If like these kinds of project write ups, let in know by responding here and I may create more in the future.