This project implements an app player targeting large-scale vertical 16:9 touch displays. The player is split into three main, permanently visible areas:
- app slider at the top
- app slider at the bottom
- message area
Each slider is assigned a list of apps to start and the user can switch to the next or previous app using on-screen buttons.
The message area is for encouraging visitors to use the player and informing them about interesting app related facts or single-time or recurring events like anniversaries.
Additionally, the player is able to display full-screen announcements when it's idle.
Clone the repository locally and put it somewhere under the root directory of a web server serving static files.
The main entry point is content-slider.html
.
The slider can be configured by specifying a cfg
URL parameter, e.g.
content-slider.html?cfg=config.sample.yaml
The given path will be resolved against the cfg
directory. If the parameter is omitted, the default config.sample.yaml
will be used.
The main configuration file specifies player settings like idle delays, credits texts, debug options and, most importantly, the selection of apps to include in the two sliders. The file format is YAML, but only the JSON compatible subset is used such that plain JSON files are also accepted. However, YAML is usually easier readable by human beings.
All paths given in the configuration file that are not absolute paths are interpreted relative to the configuration file itself.
The collection of apps is specified as follows:
apps:
- - path/to/slider1/app1.js
- path/to/slider1/app2.js
- path/to/slider1/app3.js
- ...
- - path/to/slider2/app1.js
- path/to/slider2/app2.js
- ...
Each path points to a ES6 module that implements a subclass of Application
and exports this class as the default (e.g. via export default AppClassName
).
ES6 apps might rely on certain libraries to be loaded before the app is initialized. If possible, these libraries should be imported into the app module directly such that different apps don't interfere. Libraries not supporting the ES6 module syntax can be included via the config file as global dependencies common to all apps:
common:
- path/to/common/dependency1.js
- path/to/common/dependency1.js
- ...
Note that including mutually incompatible libraries as common dependencies can break certain apps. Think of apps that use the same framework, but in different versions that can not be used simultaneously.
To get started, check out our ready-to-use collection of apps. Don't forget to include the common dependencies.
For a full reference of the all configurable options, check out the annotated config.samle.yaml
and the config file schema definition.
Some of the properties in the config file schema definition are marked with cfg-overridable: true
. Those can be passed as URL parameters to the player. e.g.
content-slider.html?cfg=config.sample.yaml&today=2020-01-01'
will override the property today
in the config file config.sample.yaml
with 2020-01-01
.
The player contains an area for displaying visitor-facing messages. These messages are read from a file provided via
messagesUrl: path/to/messages.yaml
According to the messagges-of-the-day schema, the YAML or JSON file contains a list of message entries. Each message is defined by the message text and a range of dates when it should be displayed.
- when: '#always'
message: 'Show this every day.'
- when: '????-12-??'
message: 'Show this in December only.'
The when
property has to be a valid Whenzel pattern. The message text can contain arbitrary HTML tags, but normally, you would not need anything else besides <br>
for introducing line breaks.
Certain recurring events have some similarity. For example, notifications about birthdays should basically all look the same. They might also include the number of the birthday which needs to be computed using the current date. Messages for these kinds of events can specified via the file provided via
anniversariesUrl: path/to/anniversaries.yaml
For details on the file format, check out the example file and the anniversary schema defition.
The entries from the anniversaries file are converted into the above messages format, i.e. having a when
and a message
property, substituting the templated messages along the way.
Announcements are similar to messages-of-the-day but they are displayed full screen to catch the visitors attention more aggressively. Announcements are provided in a separate file:
announcementsUrl: path/to/announcements.yaml
A randomly chosen announcement is displayed once the player is idle for the given amount of seconds:
announcementDelay: 20
The file format is identical to the messages-of-the-day with the only change that the message text is interpreted by text-poster, i.e. you have to use \n
for adding line break.
The announcer accepts additional configuration options:
announcementSettingsUrl: path/to/announcementSettings.yaml
The YAML or JSON file contains a list of objects like so:
- when: '#always'
data: {...}
- when: '????-03-20 / ????-06-19' # ~ spring
data: {...}
...
The when
property is again a Whenzel pattern. The data
property contains options passed into content-slider-announcer. When the player starts, it removes all entries not relevant on the current date and collapses them into a single object. The options in the file are merged in order such that some always applicable default values can be specified at the top of the file and more specific settings can be added towards the bottom of the file, e.g. settings for Christmas are added to settings for winter which are added to the default settings.
Apps are defined as ES6 modules that export a subclass of Application
by default. All apps run in the same HTML document since the two simultaneously visible apps are supposed to be usable at the same time, which can not be achieved if each apps would have been encapsulated into an <iframe>
(only one <iframe>
would have the input focus, even on a multi-touch screens).
A minimal app class looks like this:
import Application from '../common/js/application.js';
class MinimalApp extends Application {
constructor(config = {}) {
super(Object.assign(MinimalApp.defaultConfig, config));
this._domElement = document.createElement("div");
this._domElement.innerText = "Hello!";
}
static get defaultConfig() {
return {
appName: 'Minimal app',
};
}
get domElement() {
return this._domElement;
}
}
export default MinimalApp;
The constructor has the create the DOM element that will be added to the player. However, it is not necessary to create the full DOM subtree in the constructor. The construction of all children of the main element can be performed asynchronously. In order to do so, you have to overwrite the ready()
get
-method that returns a promise that resolves when the app is fully initialized.
In contrast to the minimal app, most real-life apps have some dynamic content and allow for user interaction. Since some apps might perform computationally intense tasks, the Application
class provides methods to pause()
, resume()
and reset()
applications and those methods should be overwritten in subclasses:
pause()
: disable user interaction, pause animations and background tasksplay()
: enable user interaction, start animations and background tasks. An app should continue exactly where it left off whenpause()
was called.reset()
: reset the app to its initial state
Calls of these methods before an app is ready()
should be ignored.
Each app should be configurable via the constructor. The string properties appName
appDescription
and appCredits
are available to all applications. They are passed into the name()
, description()
and credits()
get
-methods. Further properties can be added. To offer a highly flexible system, each app provides a set of default options via the static defaultConfig()
get
-method.
Additional predefined config can handed over to the player by overwriting the static asynchronous method retrieveConfigOverrides()
. It has to return an array of objects having the following properties:
type
(required, string): Defines how to interpret this config override. While this can be app specific, the only type currently acknowledged by the player iswhenzel
, which requires the additionalwhen
property for filtering.config
(required, object): A configuration suitable for passing into the constructor of the app.when
(required for typewhenzel
, string): A Whenzel pattern for selecting valid config entries for the current date.
Configuration objects should be passed to the superclass via super()
(see Minimal app) because not all properties might apply to the subclass itself.
Check out CindyApp
and its subclass for some more complex examples of apps compatible with this player.
This project optionally uses the commercial font Brown (regular, thin and bold variants).
The following font files need to be put into the /fonts
folder:
Brown-Bold.eof
Brown-Bold.woff
Brown-Regular.woff
BrownStd-Regular.eot
BrownStd-Regular.woff`
brown-light-webfont.woff2
brown_thin-webfont.woff2
The font files can also be included via the access restricted /fonts
git submodule for those who have access to it:
git submodule update --init --recursive
Copyright (c) 2019 IMAGINARY
Licensed under the Apache v2.0 license. See LICENSE
file.