<html>
<head>
<title>Paranoid — Shiny Demos</title>
<meta charset="utf-8">
<meta name="viewport" content="width=372, user-scalable=no">
<link rel="stylesheet" href="styles/style.css">
<link rel="stylesheet" href="/styles/panel.css">
</head>
<body>
<div id="container">
<h1>Paranoid 0.4</h1>
<canvas id="bouncy" width="360" height="378"></canvas>
<div id="controls">
<h2>Instructions</h2>
<p>To control the paddle, use the mouse, touchscreen or keyboard.</p>
<dl>
<dt>(a)</dt>
<dd>toggle autopilot</dd>
<dt>(<)</dt>
<dd>move left</dd>
<dt>(>)</dt>
<dd>move right</dd>
</dl>
</div>
<p id="autopilot">Autopilot</p>
</div>
<script src="/scripts/modernizr.js"></script>
<script src="/scripts/panel.js"></script>
<script src="scripts/options.js"></script>
<script src="scripts/PxLoader.js"></script>
<script src="scripts/PxLoaderImage.js"></script>
<script src="scripts/script.js"></script>
<body>
<script>
/**
* PixelLab Resource Loader
* Loads resources while providing progress updates.
*/
function PxLoader(settings) {
// merge settings with defaults
settings = settings || {};
// how frequently we poll resources for progress
if (settings.statusInterval === null) {
settings.statusInterval = 5000; // every 5 seconds by default
}
// delay before logging since last progress change
if (settings.loggingDelay === null) {
settings.loggingDelay = 20 * 1000; // log stragglers after 20 secs
}
// stop waiting if no progress has been made in the moving time window
if (settings.noProgressTimeout === null) {
settings.noProgressTimeout = Infinity; // do not stop waiting by default
}
var entries = [], // holds resources to be loaded with their status
progressListeners = [],
timeStarted,
progressChanged = +new Date();
/**
* The status of a resource
* @enum {number}
*/
var ResourceState = {
QUEUED: 0,
WAITING: 1,
LOADED: 2,
ERROR: 3,
TIMEOUT: 4
};
// places non-array values into an array.
var ensureArray = function(val) {
if (val === null) {
return [];
}
if (Array.isArray(val)) {
return val;
}
return [ val ];
};
// add an entry to the list of resources to be loaded
this.add = function(resource) {
// ensure tags are in an array
resource.tags = ensureArray(resource.tags);
// ensure priority is set
if (resource.priority === null) {
resource.priority = Infinity;
}
entries.push({
resource: resource,
state: ResourceState.QUEUED
});
};
this.addProgressListener = function(callback, tags) {
progressListeners.push({
callback: callback,
tags: ensureArray(tags)
});
};
this.addCompletionListener = function(callback, tags) {
progressListeners.push({
tags: ensureArray(tags),
callback: function(e) {
if (e.completedCount === e.totalCount) {
callback();
}
}
});
};
// creates a comparison function for resources
var getResourceSort = function(orderedTags) {
// helper to get the top tag's order for a resource
orderedTags = ensureArray(orderedTags);
var getTagOrder = function(entry) {
var resource = entry.resource,
bestIndex = Infinity;
for (var i = 0, len = resource.tags.length; i < len; i++) {
var index = orderedTags.indexOf(resource.tags[i]);
if (index >= 0 && index < bestIndex) {
bestIndex = index;
}
}
return bestIndex;
};
return function(a, b) {
// check tag order first
var aOrder = getTagOrder(a),
bOrder = getTagOrder(b);
if (aOrder < bOrder) return -1;
if (aOrder > bOrder) return 1;
// now check priority
if (a.priority < b.priority) return -1;
if (a.priority > b.priority) return 1;
return 0;
};
};
this.start = function(orderedTags) {
timeStarted = +new Date();
// first order the resources
var compareResources = getResourceSort(orderedTags);
entries.sort(compareResources);
// trigger requests for each resource
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i];
entry.status = ResourceState.WAITING;
entry.resource.start(this);
}
// do an initial status check soon since items may be loaded from the cache
setTimeout(statusCheck, 100);
};
var statusCheck = function() {
var checkAgain = false,
noProgressTime = (+new Date()) - progressChanged,
timedOut = (noProgressTime >= settings.noProgressTimeout),
shouldLog = (noProgressTime >= settings.loggingDelay);
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i];
if (entry.status !== ResourceState.WAITING) {
continue;
}
// see if the resource has loaded
entry.resource.checkStatus();
// if still waiting, mark as timed out or make sure we check again
if (entry.status === ResourceState.WAITING) {
if (timedOut) {
entry.resource.onTimeout();
}
else {
checkAgain = true;
}
}
}
// log any resources that are still pending
if (shouldLog && checkAgain) {
log();
}
if (checkAgain) {
setTimeout(statusCheck, settings.statusInterval);
}
};
this.isBusy = function() {
for (var i = 0, len = entries.length; i < len; i++) {
if (entries[i].status === ResourceState.QUEUED ||
entries[i].status === ResourceState.WAITING) {
return true;
}
}
return false;
};
// helper which returns true if two arrays share at least one item
var arraysIntersect = function(a, b) {
for (var i = 0, len = a.length; i < len; i++) {
if (b.indexOf(a[i]) >= 0) {
return true;
}
}
return false;
};
var onProgress = function(resource, statusType) {
// find the entry for the resource
var entry = null;
for(var i=0, len = entries.length; i < len; i++) {
if (entries[i].resource === resource) {
entry = entries[i];
break;
}
}
// we have already updated the status of the resource
if (entry === null || entry.status !== ResourceState.WAITING) {
return;
}
entry.status = statusType;
progressChanged = +new Date();
var numResourceTags = resource.tags.length;
// fire callbacks for interested listeners
for (var i = 0, numListeners = progressListeners.length; i < numListeners; i++) {
var listener = progressListeners[i],
shouldCall;
if (listener.tags.length === 0) {
// no tags specified so always tell the listener
shouldCall = true;
}
else {
// listener only wants to hear about certain tags
shouldCall = arraysIntersect(resource.tags, listener.tags);
}
if (shouldCall) {
sendProgress(entry, listener);
}
}
};
this.onLoad = function(resource) {
onProgress(resource, ResourceState.LOADED);
};
this.onError = function(resource) {
onProgress(resource, ResourceState.ERROR);
};
this.onTimeout = function(resource) {
onProgress(resource, ResourceState.TIMEOUT);
};
// sends a progress report to a listener
var sendProgress = function(updatedEntry, listener) {
// find stats for all the resources the caller is interested in
var completed = 0,
total = 0;
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i],
includeResource;
if (listener.tags.length === 0) {
// no tags specified so always tell the listener
includeResource = true;
}
else {
includeResource = arraysIntersect(entry.resource.tags, listener.tags);
}
if (includeResource) {
total++;
if (entry.status === ResourceState.LOADED ||
entry.status === ResourceState.ERROR ||
entry.status === ResourceState.TIMEOUT) {
completed++;
}
}
}
listener.callback({
// info about the resource that changed
resource: updatedEntry.resource,
// should we expose StatusType instead?
loaded: (updatedEntry.status === ResourceState.LOADED),
error: (updatedEntry.status === ResourceState.ERROR),
timeout: (updatedEntry.status === ResourceState.TIMEOUT),
// updated stats for all resources
completedCount: completed,
totalCount: total
});
};
// prints the status of each resource to the console
var log = this.log = function(showAll) {
if (!window.console) {
return;
}
var elapsedSeconds = Math.round((+new Date() - timeStarted) / 1000);
window.console.log('PxLoader elapsed: ' + elapsedSeconds + ' sec');
for (var i = 0, len = entries.length; i < len; i++) {
var entry = entries[i];
if (!showAll && entry.status !== ResourceState.WAITING) {
continue;
}
var message = 'PxLoader: #' + i + ' ' + entry.resource.getName();
switch(entry.status) {
case ResourceState.QUEUED:
message += ' (Not Started)';
break;
case ResourceState.WAITING:
message += ' (Waiting)';
break;
case ResourceState.LOADED:
message += ' (Loaded)';
break;
case ResourceState.ERROR:
message += ' (Error)';
break;
case ResourceState.TIMEOUT:
message += ' (Timeout)';
break;
}
if (entry.resource.tags.length > 0) {
message += ' Tags: [' + entry.resource.tags.join(',') + ']';
}
window.console.log(message);
}
};
}
</script>
<style>
/* typeface: Adore 64 by [email protected] http://www.freakyfonts.de/ */
@font-face {
font-family: "Adore64";
src: url(http://media.shinydemos.com/paranoid/Adore64.ttf);
}
html, body { margin: 0; padding: 0; }
body { font: 10px Adore64, sans-serif; background-color: #212121; color: #fff; }
#container { width: 360px; margin: 0 auto 0 auto; padding-top: 24px; position: relative; }
#container h1,h2 { font-size: 20px; margin: 0; padding: 0; text-transform: uppercase; color: #f00; }
#container h2 { margin-bottom: 8px; }
#container p { margin: 0; padding: 0; }
#container small { font-size: 8px; }
#container a { color: #f00; }
#container canvas { display: block; margin: 16px 0; border-bottom: 2px #555 solid; cursor: none; }
#container dt { float: left; }
#autopilot { font-size: 16px; text-transform: uppercase; position: absolute; top: 200px; width:360px; text-align:center; background-color: rgba(0,0,0,0.5); }
#container .off { display: none; }
@media (max-height: 500px) {
h1,h2 { font-size: 12px; }
#container { font-size: 6px; margin: 0 auto; }
canvas { margin: 2px 0; }
}
@-o-viewport {
width: 372px auto;
orientation: portrait;
resolution: device;
}
@-ms-viewport {
width: 372px auto;
orientation: portrait;
resolution: device;
}
</style>
</body>
</html>