npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@livetv-app/tvguide

v0.2.0

Published

An Android TV Live Channels-like Electronic Programme Guide for React DOM and React Native applications.

Downloads

9

Readme

React Programme Guide

An Android TV Live Channels-like Electronic Programme Guide for React and React Native applications.

Grid screenshot

List screenshot

  • [x] Grid view
    • Material design similar to Android TV's Live Channels app
    • Supports extended data fields (channel logos, programme series/episode information, programme images, etc) - see https://gitlab.fancy.org.uk/livetv-app/epgdata
    • Supports custom actions for programmes (e.g. Open in BBC iPlayer)
    • Renderer support:
      • [x] React DOM
      • [x] React Native
      • Supports pointer/touch controls, and directional controls for TVs
    • TODO:
      • [ ] Automatic directional controls handling with react-native-tvos
        • Support tvOS touch controls
      • [x] Automatically scroll when using directional controls
        • ~~Fix issue where a keypress might not do anything due to the scroll limit~~
      • [ ] Adapt to the system reduced transparency preference
      • [ ] Scrolling animations
      • [ ] Disable scrolling animations when the system reduced motion preference is enabled
      • [ ] Support using a custom colour scheme
      • [x] Support setting scrolling boundaries
      • [x] Hooks for loading additional data when scrolling to the end of the loaded data
  • [x] Channels list view
    • Lists channels with their name and number or icon
    • Shows the current and next programme and it's start time
    • Automatically adapts to the system colour scheme
    • Renderer support:
      • [ ] React DOM
      • [x] React Native
      • Only supports pointer/touch controls
  • [ ] Programmes list view
    • Show programmes in a list with details similar to the grid view
    • Group programmes by date (like Outlook's agenda view)
    • To be used with the channels list view, after pressing a channel

Data

Data is provided in formats defined in https://gitlab.fancy.org.uk/livetv-app/epgdata. This project also includes utilities to read XMLTV data and other formats.

import { Channel, Programme } from '@livetv-app/epgdata';

const channels: Channel[] = [
    // ...
];

const programmes: Programme[] = [
    // ...
];

Grid view

The ProgrammeGuide component expects two props: channels and programmes. A third prop, channel can be provided to indicate which channel is active - this is different to the channel that is currently selected in the guide.

import { ProgrammeGuide } from '@livetv-app/tvguide';

const channels: Channel[] = /* ... */;
const programmes: Programme[] = /* ... */;
const channel: Channel | null = /* ... */;

<ProgrammeGuide channels={channels} programmes={programmes} channel={channel} />

All props are expected to be immutable. When updating any data in either the channels or programmes array the entire array must be replaced. Programmes are linked to channels by the channel ID, not any object instance, so updating data in only one array does not require updating the other.

Scrolling

To enable (vertical) scrolling in the programme guide set the internalScrolling prop.

<ProgrammeGuide channels={channels} programmes={programmes} channel={channel} internalScrolling />

Passing user input

To support directional controls you must use a reference to send input events.

const ref = React.createRef<ProgrammeGuideRef>(null);

<ProgrammeGuide
    channels={channels} programmes={programmes} channel={channel}
    onSelectChannel={c => setChannel(c.id)}
    ref={ref} />

// When user input is received...
if (event.key === 'Enter') ref.current?.sendSelectKeypress();
if (isLongSelect) ref.current?.sendLongSelectKeypress();
if (event.key === 'ArrowUp') ref.current?.sendUpKeypress();
if (event.key === 'ArrowDown') ref.current?.sendDownKeypress();
if (event.key === 'ArrowLeft') ref.current?.sendLeftKeypress();
if (event.key === 'ArrowRight') ref.current?.sendRightKeypress();

By default scrolling up/down at the end of the list will scroll to the other end, and scrolling left/right at a boundary will be ignored. This can be changed by passing options to the send*Keypress functions and watching the return value.

const result = event.key === 'Enter' ? ref.current?.sendSelectKeypress() :
    isLongSelect ? ref.current?.sendLongSelectKeypress() :
    event.key === 'ArrowUp' ? ref.current?.sendUpKeypress(/* wrap */ false) :
    event.key === 'ArrowDown' ? ref.current?.sendDownKeypress(/* wrap */ false) :
    event.key === 'ArrowLeft' ? ref.current?.sendLeftKeypress() :
    event.key === 'ArrowRight' ? ref.current?.sendRightKeypress() : null;

if (result === KeypressResult.IGNORED_DUE_TO_BOUNDARY) [
    // ...
]

Scroll boundaries

Scroll boundaries can be set using the leftBoundary and rightBoundary props.

const DAY = 1000 * 60 * 60 * 24;
// 12:00 am, 7 days in the past
const leftBoundary = new Date((Math.floor(Date.now() / DAY) * DAY) - (DAY * 7));
// 12:00 am, 7 days in the future
const rightBoundary = new Date((Math.ceil(Date.now() / DAY) * DAY) + (DAY * 7));

<ProgrammeGuide
    channels={channels} programmes={programmes} channel={channel}
    leftBoundary={leftBoundary} rightBoundary={rightBoundary} />

Lazy loading data

Use the onScroll prop to react to the current scroll window.

function onScroll({top, bottom, left, right}: {
    top: Channel;
    bottom: Channel;
    left: Date;
    right: Date;
}) {
    // Load any new data if necessary
    // This will be called on every scroll event so you should wait until some specific point is passed before doing anything
}

<ProgrammeGuide
    channels={channels} programmes={programmes} channel={channel}
    onScroll={onScroll} />

List view

The list views are designed for use on mobile devices, and only show the current and uncoming programme.

The ChannelList component expects only a channels and programmes prop, and optionally a onChannelPressed prop.

import * as React from 'react';
import { Text, View } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { ChannelList, ProgrammeList } from '@livetv-app/tvguide';

function ChannelListScreen({navigation}) {
    const {channels, programmes} = /* ... */;

    return <View>
        <ChannelList channels={channels} programmes={programmes}
            onChannelPressed={c => navigation.navigate('Channel', {id: c.id})} />
    </View>;
}

function ChannelScreen({navigation, route}) {
    const {programmes} = /* ... */;

    return <View>
        <ProgrammeList programmes={programmes.filter(p => p.channel === route.params.id)} />
    </View>;
}

const Stack = createStackNavigator();

function App() {
    return <NavigationContainer>
        <Stack.Navigator>
            <Stack.Screen name="Channels" component={ChannelListScreen} />
            <Stack.Screen name="Channel" component={ChannelScreen} />
        </Stack.Navigator>
    </NavigationContainer>;
}