Skip to content

Commit

Permalink
Make config page available offline
Browse files Browse the repository at this point in the history
  • Loading branch information
mddub committed Oct 18, 2016
1 parent 18eeec0 commit 802cba8
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 86 deletions.
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ Contributions are welcome in the form of bugs and pull requests. To report a bug

**Tips:**

* **Testing the configuration page**: If you make changes to the configuration page, you must build the watchface to point to your local copy of the page (`file:///...`). To do this, set `BUILD_ENV` to `development`. (More info [here][build-env-development].)
* **Testing the configuration page**: For a default build, the contents of `config/index.html` and its dependencies are inlined and converted into a data URI. This data URI is rebuilt and included in the JS during a `pebble build`. When testing changes to the configuration page, however, it's easier if the emulator opens the un-inlined version. To build the watchface to open the HTML file, set `BUILD_ENV` to `development`:
```
BUILD_ENV=development pebble build
pebble install --emulator basalt
Expand Down Expand Up @@ -193,7 +193,6 @@ The most effective method of integration testing I've found is to [compare scree

This project is intended for educational and informational purposes only. It is not FDA approved and should not be used to make medical decisions. It is neither affiliated with nor endorsed by Dexcom.

[build-env-development]: https://github.com/mddub/urchin-cgm/blob/ede29c/wscript#L17
[care-portal]: http://www.nightscout.info/wiki/welcome/website-features/cgm-remote-monitor-care-portal
[Expect]: https://github.com/Automattic/expect.js
[file-issue]: https://github.com/mddub/urchin-cgm/issues
Expand Down
2 changes: 1 addition & 1 deletion config/css/slate.min.css

Large diffs are not rendered by default.

Binary file removed config/fonts/PFDinDisplayPro-Light.woff
Binary file not shown.
Binary file removed config/fonts/ptsans-regular.woff
Binary file not shown.
1 change: 0 additions & 1 deletion config/index.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
Expand Down
58 changes: 45 additions & 13 deletions config/js/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,30 @@
var customLayout;
var currentLayoutChoice;

// On the phone, this string is substituted by the app JS.
// On the emulator, it's provided in the query string by pebble-tool.
var returnTo = getQueryParam('return_to') || '$$RETURN_TO$$';

var watchInfo = {};
try {
watchInfo = JSON.parse(decodeURIComponent('$$WATCH_INFO$$'));
} catch (e) {
watchInfo = JSON.parse(getQueryParam('watchInfo', '{}'));
}
// Older versions of Urchin pass these as separate query params
watchInfo.version = watchInfo.version || getQueryParam('version');
watchInfo.pf = watchInfo.pf || getQueryParam('pf');
watchInfo.fw = watchInfo.fw || getQueryParam('fw');
watchInfo.at = watchInfo.at || getQueryParam('at');
watchInfo.wt = watchInfo.wt || getQueryParam('wt');

var phoneConfig = {};
try {
phoneConfig = JSON.parse(decodeURIComponent('$$CURRENT$$'));
} catch (e) {
phoneConfig = JSON.parse(getQueryParam('current', '{}'));
}

function upgradeConfig(config) {
if (config.layout === undefined) {
// v0.0.4
Expand Down Expand Up @@ -645,10 +669,19 @@

function onSubmit(e) {
e.preventDefault();
document.location = getQueryParam('return_to', 'pebblejs://close#') + encodeURIComponent(JSON.stringify(buildConfig()));
document.location = returnTo + encodeURIComponent(JSON.stringify(buildConfig()));
}

// TODO remove this
function trackGA() {
// In older versions, this config page is hosted on the web, accessed in a
// webview, and this page is responsible for GA tracking.
// In v0.0.13 and later, Urchin ships with this config page as a data URI,
// accessed offline, and the app JS is responsible for GA tracking.
if (document.location.href.indexOf('https://mddub.github.io') === -1) {
return;
}

/* jshint ignore:start */
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
Expand All @@ -657,39 +690,38 @@
/* jshint ignore:end */

// redact PII
var current = JSON.parse(getQueryParam('current', '{}'));
var current = JSON.parse(JSON.stringify(phoneConfig));
delete current['nightscout_url'];
delete current['statusText'];
delete current['statusUrl'];
delete current['statusJsonUrl'];
var cleansed = [
['version', getQueryParam('version')],
['pf', getQueryParam('pf')],
['fw', getQueryParam('fw')],
['at', getQueryParam('at')],
['wt', getQueryParam('wt')],
['version', watchInfo.version],
['pf', watchInfo.pf],
['fw', watchInfo.fw],
['at', watchInfo.at],
['wt', watchInfo.wt],
['current', JSON.stringify(current)],
].filter(function(pair) {
return pair[1] !== false;
return pair[1];
}).map(function(pair) {
return pair[0] + '=' + pair[1];
}).join('&');

ga('create', 'UA-72399627-1', 'auto');
ga('create', c.CONFIG_GA_ID, 'auto');
ga('send', 'pageview', location.pathname + '?' + cleansed);
}

$(function() {

var phoneConfig = JSON.parse(getQueryParam('current', '{}'));
var current = upgradeConfig(phoneConfig);
populateValues(current);

$('#update-available #running-version').text(getQueryParam('version') || '0.0.0');
$('#update-available #running-version').text(watchInfo.version || '0.0.0');
$('#update-available #available-version').text(c.VERSION);
$('#update-available').toggle(c.VERSION !== getQueryParam('version'));
$('#update-available').toggle(c.VERSION !== watchInfo.version);

$('.color-platforms-only').toggle(['aplite', 'diorite'].indexOf(getQueryParam('pf')) === -1);
$('.color-platforms-only').toggle(['aplite', 'diorite'].indexOf(watchInfo.pf) === -1);

initializeStatusOptions(current);

Expand Down
105 changes: 63 additions & 42 deletions make_inline_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,80 @@
# dependencies are inlined, and convert the result into a data URI.
#
# This makes it possible to view the Urchin configuration page without an
# internet connection.
# internet connection. Also, since the config page is bundled with the app
# (rather than hosted on the web), its available settings always match the
# features of the current version.
#
# See https://github.com/pebble/clay for a more robust implementation of offline
# configuration pages.

import base64
import json
import os
import re

out = 'data:text/html;base64'

# Use re instead of something like PyQuery so that the hell of installing lxml
# is not a prerequisite for building the watchface
html = open('config/index.html').read()
css_tags = re.findall('(<link[^>]* href="([^"]+)">)', html, re.I)
assert len(css_tags) == 1

css = open('config/' + css_tags[0][1]).read()
css_dir = os.path.dirname(css_tags[0][1])
urls = re.findall('(url\(([^)]+)\))', css, re.I)
assert len(urls) > 0
for url in urls:
filename = url[1]
filename = re.sub('(^"|"$)', '', filename)
filename = re.sub("(^'|'$)", '', filename)
assert filename.endswith('.woff') or filename.endswith('.png')
full_filename = os.path.join('config', css_dir, filename)
encoded = base64.b64encode(open(full_filename, "rb").read())
if filename.endswith('.woff'):
mime_type = 'application/font-woff'
import re
import subprocess

def call_minify(command_str, stdin, filename):
parts = command_str.split(' ')
try:
proc = subprocess.Popen(parts, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
except Exception, e:
raise Exception("Command failed: {}: {}".format(command_str, e))
out, err = proc.communicate(input=stdin)
if err:
print command_str
raise Exception('{}: {}: failed with return code {}'.format(command_str, filename, err))
else:
print '{}: {}: {} -> {} bytes'.format(parts[0], filename, len(stdin), len(out))
return out

def make_inline_config(task, html_file_node):
config_dir = html_file_node.parent.abspath()

html = html_file_node.read()
css_tags = re.findall('(<link[^>]* href="([^"]+)">)', html, re.I)
assert len(css_tags) == 1

css_filename = os.path.join(config_dir, css_tags[0][1])
css_dir = os.path.dirname(css_filename)

css = open(css_filename).read()
css = re.sub('\s*([{}:;])\s*', lambda match: match.group(1), css)

urls = re.findall('(url\(([^)]+)\))', css, re.I)
assert len(urls) > 0
for url in urls:
filename = url[1]
filename = re.sub('(^"|"$)', '', filename)
filename = re.sub("(^'|'$)", '', filename)
assert filename.endswith('.png')
mime_type = 'image/png'
css = css.replace(
url[0],
'url(data:{};base64,{})'.format(mime_type, encoded)
)
encoded = base64.b64encode(open(os.path.join(css_dir, filename), "rb").read())
css = css.replace(
url[0],
'url(data:{};base64,{})'.format(mime_type, encoded)
)

minified_css = call_minify('./node_modules/clean-css/bin/cleancss', css, os.path.relpath(css_filename))

# TODO trim newlines/whitespace in css
html = html.replace(
css_tags[0][0],
'<style type="text/css">{}</style>'.format(css)
)

js_tags = re.findall('(<script[^>]* src="([^"]+)"></script>)', html, re.I)
assert len(js_tags) > 0
for js_tag in js_tags:
filename = js_tag[1]
# TODO trim newlines/whitespace
js = open('config/' + js_tag[1]).read()
html = html.replace(
js_tag[0],
'<script type="text/javascript">{}</script>'.format(js)
css_tags[0][0],
'<style>{}</style>'.format(minified_css)
)

# TODO trim newlines/whitespace
print html
js_tags = re.findall('(<script[^>]* src="([^"]+)"></script>)', html, re.I)
assert len(js_tags) > 0
for js_tag in js_tags:
js_filename = os.path.join(config_dir, js_tag[1])
js = open(js_filename).read()
minified_js = call_minify('./node_modules/uglify-js/bin/uglifyjs', js, os.path.relpath(js_filename))
html = html.replace(
js_tag[0],
'<script>{}</script>'.format(minified_js)
)

minified_html = call_minify('./node_modules/html-minifier/cli.js --remove-comments --remove-attribute-quotes', html, html_file_node.relpath())

return json.dumps({'configPage': minified_html})
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
"version": "0.1.0",
"keywords": ["pebble-app"],
"private": true,
"dependencies": {},
"devDependencies": {
"clean-css": "~3.4.20",
"html-minifier": "~3.1.0",
"uglify-js": "~2.7.3"
},
"pebble": {
"displayName": "Urchin CGM",
"uuid": "ea361603-0373-4865-9824-8f52c65c6e07",
Expand Down
42 changes: 42 additions & 0 deletions proxy_config.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<!DOCTYPE html>
<!--
Copied directly from:
http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/
See https://github.com/pebble/clay/blob/4a6382/index.js#L152-L182
and app.js to understand its purpose.
//-->
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Config Page Emulator</title>
</head>
<script>
function getQueryParam(variable, defaultValue) {
var query = location.search.substring(1);
var vars = query.split('&');
for (var i = 0; i < vars.length; i++) {
var pair = vars[i].split('=');

if (pair[0] === variable) {
return decodeURIComponent(pair[1]);
}
}
return defaultValue || null;
}

var returnTo = getQueryParam('return_to');
var data = decodeURIComponent(location.hash.substring(1));

// Check if we dealing with a base64 encoded URI
if (data && data.charAt(0) !== '<') {
data = window.atob(data).replace('$$RETURN_TO$$', returnTo);
window.location.href = 'data:text/html;base64,' + encodeURIComponent(window.btoa(data));
} else if (data) {
data = data.replace('$$RETURN_TO$$', returnTo);
window.location.href = 'data:text/html;charset=utf-8,' + encodeURIComponent(data);
}
</script>
<body>
</body>
</html>
Loading

0 comments on commit 802cba8

Please sign in to comment.