mirror of
https://github.com/pebble-dev/clay.git
synced 2026-10-27 06:59:59 -04:00
Merge pull request #7 from pebble/PBL-33804/duplicate-components
Fix duplicate components being injected into data URI
This commit is contained in:
commit
d3ee516ab1
6 files changed
+381
-141
No files matched your search
Vendored
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
/* Clay - https://github.com/pebble/clay - Version: 0.1.0 - Build Date: 2016-02-17T01:04:02.060Z */
|
/* Clay - https://github.com/pebble/clay - Version: 0.1.0 - Build Date: 2016-02-18T09:01:29.228Z */
|
||||||
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.clay=t()}}(function(){return function t(e,n,o){function r(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var u=n[a]={exports:{}};e[a][0].call(u.exports,function(t){var n=e[a][1][t];return r(n?n:t)},u,u.exports,t,e,n,o)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a<o.length;a++)r(o[a]);return r}({1:[function(t,e,n){function o(t){return/^[a-z_$][0-9a-z_$]*$/gi.test(t)&&!i.test(t)}function r(t){if(a)return t.toString();var e=t.source.replace(/\//g,function(t,e,n){return 0===e||"\\"!==n[e-1]?"\\/":"/"}),n=(t.global&&"g"||"")+(t.ignoreCase&&"i"||"")+(t.multiline&&"m"||"");return"/"+e+"/"+n}e.exports=function(t,e,n,i){function a(t,e,n,i,c){function s(t){return n.slice(1)+t.join(","+(n&&"\n")+l)+(n?" ":"")}var l=i+n;switch(t=e?e(t):t,typeof t){case"string":return JSON.stringify(t);case"boolean":case"number":case"undefined":return""+t;case"function":return t.toString()}if(null===t)return"null";if(t instanceof RegExp)return r(t);if(t instanceof Date)return"new Date("+t.getTime()+")";var u=c.indexOf(t)+1;if(u>0)return"{$circularReference:"+u+"}";if(c.push(t),Array.isArray(t))return"["+s(t.map(function(t){return a(t,e,n,l,c.slice())}))+"]";var f=Object.keys(t);return f.length?"{"+s(f.map(function(r){return(o(r)?r:JSON.stringify(r))+":"+a(t[r],e,n,l,c.slice())}))+"}":"{}"}var c=[];return a(t,e,void 0===n?" ":n||"",i||"",c)};var i=/^(abstract|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|undefined|var|void|volatile|while|with)$/,a="\\/"===new RegExp("/").source},{}],2:[function(t,e,n){"use strict";e.exports={name:"checkboxgroup",template:t("../../templates/components/checkboxgroup.tpl"),style:t("../../styles/clay/components/checkboxgroup.scss"),manipulator:"checkboxgroup",defaults:{label:"",options:[],attributes:{}}}},{"../../styles/clay/components/checkboxgroup.scss":13,"../../templates/components/checkboxgroup.tpl":19}],3:[function(t,e,n){"use strict";e.exports={name:"color",template:t("../../templates/components/color.tpl"),style:t("../../styles/clay/components/color.scss"),manipulator:"color",defaults:{label:""},initialize:function(t){function e(t){if(t===!1)return"transparent";for("number"==typeof t&&(t=t.toString(16));t.length<6;)t="0"+t;return"#"+(r?i[t]:t)}for(var n=t.HTML,o=this,r=o.config.sunlight!==!1,i={"000000":"000000","000055":"001e41","0000aa":"004387","0000ff":"0068ca","005500":"2b4a2c","005555":"27514f","0055aa":"16638d","0055ff":"007dce","00aa00":"5e9860","00aa55":"5c9b72","00aaaa":"57a5a2","00aaff":"4cb4db","00ff00":"8ee391","00ff55":"8ee69e","00ffaa":"8aebc0","00ffff":"84f5f1",550000:"4a161b",550055:"482748","5500aa":"40488a","5500ff":"2f6bcc",555500:"564e36",555555:"545454","5555aa":"4f6790","5555ff":"4180d0","55aa00":"759a64","55aa55":"759d76","55aaaa":"71a6a4","55aaff":"69b5dd","55ff00":"9ee594","55ff55":"9de7a0","55ffaa":"9becc2","55ffff":"95f6f2",aa0000:"99353f",aa0055:"983e5a",aa00aa:"955694",aa00ff:"8f74d2",aa5500:"9d5b4d",aa5555:"9d6064",aa55aa:"9a7099",aa55ff:"9587d5",aaaa00:"afa072",aaaa55:"aea382",aaaaaa:"ababab",ffffff:"ffffff",aaaaff:"a7bae2",aaff00:"c9e89d",aaff55:"c9eaa7",aaffaa:"c7f0c8",aaffff:"c3f9f7",ff0000:"e35462",ff0055:"e25874",ff00aa:"e16aa3",ff00ff:"de83dc",ff5500:"e66e6b",ff5555:"e6727c",ff55aa:"e37fa7",ff55ff:"e194df",ffaa00:"f1aa86",ffaa55:"f1ad93",ffaaaa:"efb5b8",ffaaff:"ecc3eb",ffff00:"ffeeab",ffff55:"fff1b5",ffffaa:"fff6d3"},a=o.config.layout||[[!1,!1,"55ff00","aaff55",!1,"ffff55","ffffaa",!1,!1],[!1,"aaffaa","55ff55","00ff00","aaff00","ffff00","ffaa55","ffaaaa",!1],["55ffaa","00ff55","00aa00","55aa00","aaaa55","aaaa00","ffaa00","ff5500","ff5555"],["aaffff","00ffaa","00aa55","55aa55","005500","555500","aa5500","ff0000","ff0055"],[!1,"55aaaa","00aaaa","005555","ffffff","000000","aa5555","aa0000",!1],["55ffff","00ffff","00aaff","0055aa","aaaaaa","555555","550000","aa0055","ff55aa"],["55aaff","0055ff","0000ff","0000aa","000055","550055","aa00aa","ff00aa","ffaaff"],[!1,"5555aa","5555ff","5500ff","5500aa","aa00ff","ff00ff","ff55ff",!1],[!1,!1,!1,"aaaaff","aa55ff","aa55aa",!1,!1,!1]],c="",s=100/a[0].length,l=100/a.length,u=o.$element,f=0;f<a.length;f++)for(var p=0;p<a[f].length;p++){var d=a[f][p],m=d?" selectable":"",b=0===f&&0===p||0===f&&!a[f][p-1]||!a[f][p-1]&&!a[f-1][p]?" rounded-tl":"",g=0===f&&!a[f][p+1]||!a[f][p+1]&&!a[f-1][p]?" rounded-tr
Line truncated
|
!function(t){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var e;e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this,e.clay=t()}}(function(){return function t(e,n,o){function r(a,c){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!c&&s)return s(a,!0);if(i)return i(a,!0);var l=new Error("Cannot find module '"+a+"'");throw l.code="MODULE_NOT_FOUND",l}var u=n[a]={exports:{}};e[a][0].call(u.exports,function(t){var n=e[a][1][t];return r(n?n:t)},u,u.exports,t,e,n,o)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;a<o.length;a++)r(o[a]);return r}({1:[function(t,e,n){function o(t){return/^[a-z_$][0-9a-z_$]*$/gi.test(t)&&!i.test(t)}function r(t){if(a)return t.toString();var e=t.source.replace(/\//g,function(t,e,n){return 0===e||"\\"!==n[e-1]?"\\/":"/"}),n=(t.global&&"g"||"")+(t.ignoreCase&&"i"||"")+(t.multiline&&"m"||"");return"/"+e+"/"+n}e.exports=function(t,e,n,i){function a(t,e,n,i,c){function s(t){return n.slice(1)+t.join(","+(n&&"\n")+l)+(n?" ":"")}var l=i+n;switch(t=e?e(t):t,typeof t){case"string":return JSON.stringify(t);case"boolean":case"number":case"undefined":return""+t;case"function":return t.toString()}if(null===t)return"null";if(t instanceof RegExp)return r(t);if(t instanceof Date)return"new Date("+t.getTime()+")";var u=c.indexOf(t)+1;if(u>0)return"{$circularReference:"+u+"}";if(c.push(t),Array.isArray(t))return"["+s(t.map(function(t){return a(t,e,n,l,c.slice())}))+"]";var f=Object.keys(t);return f.length?"{"+s(f.map(function(r){return(o(r)?r:JSON.stringify(r))+":"+a(t[r],e,n,l,c.slice())}))+"}":"{}"}var c=[];return a(t,e,void 0===n?" ":n||"",i||"",c)};var i=/^(abstract|boolean|break|byte|case|catch|char|class|const|continue|debugger|default|delete|do|double|else|enum|export|extends|false|final|finally|float|for|function|goto|if|implements|import|in|instanceof|int|interface|long|native|new|null|package|private|protected|public|return|short|static|super|switch|synchronized|this|throw|throws|transient|true|try|typeof|undefined|var|void|volatile|while|with)$/,a="\\/"===new RegExp("/").source},{}],2:[function(t,e,n){"use strict";e.exports={name:"checkboxgroup",template:t("../../templates/components/checkboxgroup.tpl"),style:t("../../styles/clay/components/checkboxgroup.scss"),manipulator:"checkboxgroup",defaults:{label:"",options:[],attributes:{}}}},{"../../styles/clay/components/checkboxgroup.scss":13,"../../templates/components/checkboxgroup.tpl":19}],3:[function(t,e,n){"use strict";e.exports={name:"color",template:t("../../templates/components/color.tpl"),style:t("../../styles/clay/components/color.scss"),manipulator:"color",defaults:{label:""},initialize:function(t){function e(t){if(t===!1)return"transparent";for("number"==typeof t&&(t=t.toString(16));t.length<6;)t="0"+t;return"#"+(r?i[t]:t)}for(var n=t.HTML,o=this,r=o.config.sunlight!==!1,i={"000000":"000000","000055":"001e41","0000aa":"004387","0000ff":"0068ca","005500":"2b4a2c","005555":"27514f","0055aa":"16638d","0055ff":"007dce","00aa00":"5e9860","00aa55":"5c9b72","00aaaa":"57a5a2","00aaff":"4cb4db","00ff00":"8ee391","00ff55":"8ee69e","00ffaa":"8aebc0","00ffff":"84f5f1",550000:"4a161b",550055:"482748","5500aa":"40488a","5500ff":"2f6bcc",555500:"564e36",555555:"545454","5555aa":"4f6790","5555ff":"4180d0","55aa00":"759a64","55aa55":"759d76","55aaaa":"71a6a4","55aaff":"69b5dd","55ff00":"9ee594","55ff55":"9de7a0","55ffaa":"9becc2","55ffff":"95f6f2",aa0000:"99353f",aa0055:"983e5a",aa00aa:"955694",aa00ff:"8f74d2",aa5500:"9d5b4d",aa5555:"9d6064",aa55aa:"9a7099",aa55ff:"9587d5",aaaa00:"afa072",aaaa55:"aea382",aaaaaa:"ababab",ffffff:"ffffff",aaaaff:"a7bae2",aaff00:"c9e89d",aaff55:"c9eaa7",aaffaa:"c7f0c8",aaffff:"c3f9f7",ff0000:"e35462",ff0055:"e25874",ff00aa:"e16aa3",ff00ff:"de83dc",ff5500:"e66e6b",ff5555:"e6727c",ff55aa:"e37fa7",ff55ff:"e194df",ffaa00:"f1aa86",ffaa55:"f1ad93",ffaaaa:"efb5b8",ffaaff:"ecc3eb",ffff00:"ffeeab",ffff55:"fff1b5",ffffaa:"fff6d3"},a=o.config.layout||[[!1,!1,"55ff00","aaff55",!1,"ffff55","ffffaa",!1,!1],[!1,"aaffaa","55ff55","00ff00","aaff00","ffff00","ffaa55","ffaaaa",!1],["55ffaa","00ff55","00aa00","55aa00","aaaa55","aaaa00","ffaa00","ff5500","ff5555"],["aaffff","00ffaa","00aa55","55aa55","005500","555500","aa5500","ff0000","ff0055"],[!1,"55aaaa","00aaaa","005555","ffffff","000000","aa5555","aa0000",!1],["55ffff","00ffff","00aaff","0055aa","aaaaaa","555555","550000","aa0055","ff55aa"],["55aaff","0055ff","0000ff","0000aa","000055","550055","aa00aa","ff00aa","ffaaff"],[!1,"5555aa","5555ff","5500ff","5500aa","aa00ff","ff00ff","ff55ff",!1],[!1,!1,!1,"aaaaff","aa55ff","aa55aa",!1,!1,!1]],c="",s=100/a[0].length,l=100/a.length,u=o.$element,f=0;f<a.length;f++)for(var p=0;p<a[f].length;p++){var d=a[f][p],m=d?" selectable":"",b=0===f&&0===p||0===f&&!a[f][p-1]||!a[f][p-1]&&!a[f-1][p]?" rounded-tl":"",g=0===f&&!a[f][p+1]||!a[f][p+1]&&!a[f-1][p]?" rounded-tr
Line truncated
|
||||||
},{}],"pebble-clay":[function(t,e,n){"use strict";function o(t,e){if(e="undefined"!=typeof e?e:"data:text/html;base64,",window.btoa)return e+encodeURIComponent(window.btoa(t));var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t=String(t);var o,r,i,a,c,s,l,u=0,f=[];if(/[^\x00-\xFF]/.test(t))throw Error("InvalidCharacterError");for(;u<t.length;)o=t.charCodeAt(u++),r=t.charCodeAt(u++),i=t.charCodeAt(u++),a=o>>2,c=(3&o)<<4|r>>4,s=(15&r)<<2|i>>6,l=63&i,u===t.length+2?(s=64,l=64):u===t.length+1&&(l=64),f.push(n.charAt(a),n.charAt(c),n.charAt(s),n.charAt(l));return e+encodeURIComponent(f.join(""))}function r(t,e,n){function o(t){Array.isArray(t)?t.forEach(function(t){o(t)}):"section"===t.type?o(t.items):c[t.type]&&r.registerComponent(c[t.type])}var r=this;if(!Array.isArray(t))throw new Error("config must be an Array");if(e&&"function"!=typeof e)throw new Error('customFn must be an function or "null"');n=n||{},r.config=t,r.customFn=e||function(){},r.components=[],n.autoHandleEvents!==!1&&Pebble&&(Pebble.addEventListener("showConfiguration",function(t){Pebble.openURL(r.generateUrl())}),Pebble.addEventListener("webviewclosed",function(t){(!t||t.response)&&Pebble.sendAppMessage(r.getSettings(t.response),function(t){console.log("Sent config data to Pebble")},function(){console.log("Failed to send config data!"),console.log(JSON.stringify(t))})})),o(r.config)}var i=t("./tmp/config-page.html"),a=t("tosource"),c=t("./src/scripts/components");r.prototype.registerComponent=function(t){this.components.push(t)},r.prototype.generateUrl=function(){var t,e=!Pebble||"pypkjs"===Pebble.platform,n=e?"$$$RETURN_TO$$$":"pebblejs://close#";try{t=JSON.parse(localStorage.getItem("clay-settings"))||{}}catch(r){console.error(r),t={}}var c=i.replace("$$RETURN_TO$$",n).replace("$$CUSTOM_FN$$",a(this.customFn)).replace("$$CONFIG$$",a(this.config)).replace("$$SETTINGS$$",a(t)).replace("$$COMPONENTS$$",a(this.components));return e?o(c,"http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/#"):o(c)},r.prototype.getSettings=function(t){var e=JSON.parse(decodeURIComponent(t));return e?(localStorage.setItem("clay-settings",JSON.stringify(e)),e):{}},e.exports=r},{"./src/scripts/components":6,"./tmp/config-page.html":29,tosource:1}]},{},["pebble-clay"])("pebble-clay")});
|
},{}],"pebble-clay":[function(t,e,n){"use strict";function o(t,e,n){function o(t){Array.isArray(t)?t.forEach(function(t){o(t)}):"section"===t.type?o(t.items):a[t.type]&&r.registerComponent(a[t.type])}var r=this;if(!Array.isArray(t))throw new Error("config must be an Array");if(e&&"function"!=typeof e)throw new Error('customFn must be a function or "null"');n=n||{},r.config=t,r.customFn=e||function(){},r.components={},n.autoHandleEvents!==!1&&"undefined"!=typeof Pebble&&(Pebble.addEventListener("showConfiguration",function(){Pebble.openURL(r.generateUrl())}),Pebble.addEventListener("webviewclosed",function(t){t&&t.response&&Pebble.sendAppMessage(r.getSettings(t.response),function(){console.log("Sent config data to Pebble")},function(t){console.log("Failed to send config data!"),console.log(JSON.stringify(t))})})),o(r.config)}var r=t("./tmp/config-page.html"),i=t("tosource"),a=t("./src/scripts/components");o.prototype.registerComponent=function(t){this.components[t.name]=t},o.prototype.generateUrl=function(){var t={},e=!Pebble||"pypkjs"===Pebble.platform,n=e?"$$$RETURN_TO$$$":"pebblejs://close#";try{t=JSON.parse(localStorage.getItem("clay-settings"))||{}}catch(a){console.error(a.toString())}var c=r.replace("$$RETURN_TO$$",n).replace("$$CUSTOM_FN$$",i(this.customFn)).replace("$$CONFIG$$",i(this.config)).replace("$$SETTINGS$$",i(t)).replace("$$COMPONENTS$$",i(this.components));return e?o.encodeDataUri(c,"http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/#"):o.encodeDataUri(c)},o.prototype.getSettings=function(t){var e={};try{e=JSON.parse(decodeURIComponent(t))}catch(n){throw new Error("The provided response was not valid JSON")}return localStorage.setItem("clay-settings",JSON.stringify(e)),e},o.encodeDataUri=function(t,e){if(e="undefined"!=typeof e?e:"data:text/html;base64,",window.btoa)return e+encodeURIComponent(window.btoa(t));var n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";t=String(t);var o,r,i,a,c,s,l,u=0,f=[];if(/[^\x00-\xFF]/.test(t))throw Error("InvalidCharacterError");for(;u<t.length;)o=t.charCodeAt(u++),r=t.charCodeAt(u++),i=t.charCodeAt(u++),a=o>>2,c=(3&o)<<4|r>>4,s=(15&r)<<2|i>>6,l=63&i,u===t.length+2?(s=64,l=64):u===t.length+1&&(l=64),f.push(n.charAt(a),n.charAt(c),n.charAt(s),n.charAt(l));return e+encodeURIComponent(f.join(""))},e.exports=o},{"./src/scripts/components":6,"./tmp/config-page.html":29,tosource:1}]},{},["pebble-clay"])("pebble-clay")});
|
||||||
@@ -4,13 +4,149 @@ var configPageHtml = require('./tmp/config-page.html');
|
|||||||
var toSource = require('tosource');
|
var toSource = require('tosource');
|
||||||
var standardComponents = require('./src/scripts/components');
|
var standardComponents = require('./src/scripts/components');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} config - the Clay config
|
||||||
|
* @param {function} [customFn] - Custom code to run from the config page. Will run
|
||||||
|
* with the ClayConfig instance as context
|
||||||
|
* @param {Object} [options] - Additional options to pass to Clay
|
||||||
|
* @param {boolean} [options.autoHandleEvents] - If false, Clay will not
|
||||||
|
* automatically handle the 'showConfiguration' and 'webviewclosed' events
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
function Clay(config, customFn, options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
if (!Array.isArray(config)) {
|
||||||
|
throw new Error('config must be an Array');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (customFn && typeof customFn !== 'function') {
|
||||||
|
throw new Error('customFn must be a function or "null"');
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
self.config = config;
|
||||||
|
self.customFn = customFn || function() {};
|
||||||
|
self.components = {};
|
||||||
|
|
||||||
|
// Let Clay handle all the magic
|
||||||
|
if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {
|
||||||
|
|
||||||
|
Pebble.addEventListener('showConfiguration', function() {
|
||||||
|
Pebble.openURL(self.generateUrl());
|
||||||
|
});
|
||||||
|
|
||||||
|
Pebble.addEventListener('webviewclosed', function(e) {
|
||||||
|
|
||||||
|
if (!e || !e.response) { return; }
|
||||||
|
|
||||||
|
// Send settings to Pebble watchapp
|
||||||
|
Pebble.sendAppMessage(self.getSettings(e.response), function() {
|
||||||
|
console.log('Sent config data to Pebble');
|
||||||
|
}, function(error) {
|
||||||
|
console.log('Failed to send config data!');
|
||||||
|
console.log(JSON.stringify(error));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @param {Clay~ConfigItem|Array} item
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function _registerStandardComponents(item) {
|
||||||
|
if (Array.isArray(item)) {
|
||||||
|
item.forEach(function(item) {
|
||||||
|
_registerStandardComponents(item);
|
||||||
|
});
|
||||||
|
} else if (item.type === 'section') {
|
||||||
|
_registerStandardComponents(item.items);
|
||||||
|
} else if (standardComponents[item.type]) {
|
||||||
|
self.registerComponent(standardComponents[item.type]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_registerStandardComponents(self.config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a component to Clay.
|
||||||
|
* @param {Object} component - the clay component to register
|
||||||
|
* @param {string} component.name - the name of the component
|
||||||
|
* @param {string} component.template - HTML template to use for the component
|
||||||
|
* @param {string|Object} component.manipulator - methods to attach to the component
|
||||||
|
* @param {function} component.manipulator.set - set manipulator method
|
||||||
|
* @param {function} component.manipulator.get - get manipulator method
|
||||||
|
* @param {Object} [component.defaults] - template defaults
|
||||||
|
* @param {function} [component.initialize] - method to scaffold the component
|
||||||
|
* @return {boolean} - Returns true if component was registered correctly
|
||||||
|
*/
|
||||||
|
Clay.prototype.registerComponent = function(component) {
|
||||||
|
this.components[component.name] = component;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate the Data URI used by the config Page with settings injected
|
||||||
|
* @return {string}
|
||||||
|
*/
|
||||||
|
Clay.prototype.generateUrl = function() {
|
||||||
|
var settings = {};
|
||||||
|
var emulator = !Pebble || Pebble.platform === 'pypkjs';
|
||||||
|
var returnTo = emulator ? '$$$RETURN_TO$$$' : 'pebblejs://close#';
|
||||||
|
|
||||||
|
try {
|
||||||
|
settings = JSON.parse(localStorage.getItem('clay-settings')) || {};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
var compiledHtml = configPageHtml
|
||||||
|
.replace('$$RETURN_TO$$', returnTo)
|
||||||
|
.replace('$$CUSTOM_FN$$', toSource(this.customFn))
|
||||||
|
.replace('$$CONFIG$$', toSource(this.config))
|
||||||
|
.replace('$$SETTINGS$$', toSource(settings))
|
||||||
|
.replace('$$COMPONENTS$$', toSource(this.components));
|
||||||
|
|
||||||
|
// if we are in the emulator then we need to proxy the data via a webpage to
|
||||||
|
// obtain the return_to.
|
||||||
|
// @todo calculate this from the Pebble object or something
|
||||||
|
if (emulator) {
|
||||||
|
return Clay.encodeDataUri(
|
||||||
|
compiledHtml,
|
||||||
|
'http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/#'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Clay.encodeDataUri(compiledHtml);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the response from the webviewclosed event data
|
||||||
|
* @param {string} response
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
Clay.prototype.getSettings = function(response) {
|
||||||
|
// Decode and parse config data as JSON
|
||||||
|
var settings = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
settings = JSON.parse(decodeURIComponent(response));
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('The provided response was not valid JSON');
|
||||||
|
}
|
||||||
|
|
||||||
|
localStorage.setItem('clay-settings', JSON.stringify(settings));
|
||||||
|
return settings;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} input
|
* @param {string} input
|
||||||
* @param {string} [prefix]
|
* @param {string} [prefix]
|
||||||
* @private
|
|
||||||
* @returns {string}
|
* @returns {string}
|
||||||
*/
|
*/
|
||||||
function encodeDataUri(input, prefix) {
|
Clay.encodeDataUri = function(input, prefix) {
|
||||||
prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;base64,';
|
prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;base64,';
|
||||||
|
|
||||||
if (window.btoa) {
|
if (window.btoa) {
|
||||||
@@ -59,140 +195,6 @@ function encodeDataUri(input, prefix) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return prefix + encodeURIComponent(out.join(''));
|
return prefix + encodeURIComponent(out.join(''));
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Array} config - the Clay config
|
|
||||||
* @param {function} [customFn] - Custom code to run from the config page. Will run
|
|
||||||
* with the ClayConfig instance as context
|
|
||||||
* @param {Object} [options] - Additional options to pass to Clay
|
|
||||||
* @param {boolean} [options.autoHandleEvents] - If false, Clay will not
|
|
||||||
* automatically handle the 'showConfiguration' and 'webviewclosed' events
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function Clay(config, customFn, options) {
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
if (!Array.isArray(config)) {
|
|
||||||
throw new Error('config must be an Array');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customFn && typeof customFn !== 'function') {
|
|
||||||
throw new Error('customFn must be an function or "null"');
|
|
||||||
}
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
self.config = config;
|
|
||||||
self.customFn = customFn || function() {};
|
|
||||||
self.components = [];
|
|
||||||
|
|
||||||
// Let Clay handle all the magic
|
|
||||||
if (options.autoHandleEvents !== false && Pebble) {
|
|
||||||
|
|
||||||
Pebble.addEventListener('showConfiguration', function(e) {
|
|
||||||
Pebble.openURL(self.generateUrl());
|
|
||||||
});
|
|
||||||
|
|
||||||
Pebble.addEventListener('webviewclosed', function(e) {
|
|
||||||
|
|
||||||
if (e && !e.response) { return; }
|
|
||||||
|
|
||||||
// Send settings to Pebble watchapp
|
|
||||||
Pebble.sendAppMessage(self.getSettings(e.response), function(e) {
|
|
||||||
console.log('Sent config data to Pebble');
|
|
||||||
}, function() {
|
|
||||||
console.log('Failed to send config data!');
|
|
||||||
console.log(JSON.stringify(e));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @param {Clay~ConfigItem|Array} item
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
function _registerStandardComponents(item) {
|
|
||||||
if (Array.isArray(item)) {
|
|
||||||
item.forEach(function(item) {
|
|
||||||
_registerStandardComponents(item);
|
|
||||||
});
|
|
||||||
} else if (item.type === 'section') {
|
|
||||||
_registerStandardComponents(item.items);
|
|
||||||
} else if (standardComponents[item.type]) {
|
|
||||||
self.registerComponent(standardComponents[item.type]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_registerStandardComponents(self.config);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a component to Clay.
|
|
||||||
* @param {Object} component - the clay component to register
|
|
||||||
* @param {string} component.name - the name of the component
|
|
||||||
* @param {string} component.template - HTML template to use for the component
|
|
||||||
* @param {string|Object} component.manipulator - methods to attach to the component
|
|
||||||
* @param {function} component.manipulator.set - set manipulator method
|
|
||||||
* @param {function} component.manipulator.get - get manipulator method
|
|
||||||
* @param {Object} [component.defaults] - template defaults
|
|
||||||
* @param {function} [component.initialize] - method to scaffold the component
|
|
||||||
* @return {boolean} - Returns true if component was registered correctly
|
|
||||||
*/
|
|
||||||
Clay.prototype.registerComponent = function(component) {
|
|
||||||
this.components.push(component);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the Data URI used by the config Page with settings injected
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
Clay.prototype.generateUrl = function() {
|
|
||||||
var settings;
|
|
||||||
var emulator = !Pebble || Pebble.platform === 'pypkjs';
|
|
||||||
var returnTo = emulator ? '$$$RETURN_TO$$$' : 'pebblejs://close#';
|
|
||||||
|
|
||||||
try {
|
|
||||||
settings = JSON.parse(localStorage.getItem('clay-settings')) || {};
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
settings = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
var compiledHtml = configPageHtml
|
|
||||||
.replace('$$RETURN_TO$$', returnTo)
|
|
||||||
.replace('$$CUSTOM_FN$$', toSource(this.customFn))
|
|
||||||
.replace('$$CONFIG$$', toSource(this.config))
|
|
||||||
.replace('$$SETTINGS$$', toSource(settings))
|
|
||||||
.replace('$$COMPONENTS$$', toSource(this.components));
|
|
||||||
|
|
||||||
// if we are in the emulator then we need to proxy the data via a webpage to
|
|
||||||
// obtain the return_to.
|
|
||||||
// @todo calculate this from the Pebble object or something
|
|
||||||
if (emulator) {
|
|
||||||
return encodeDataUri(
|
|
||||||
compiledHtml,
|
|
||||||
'http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/#'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return encodeDataUri(compiledHtml);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse the response from the webviewclosed event data
|
|
||||||
* @param {string} response
|
|
||||||
* @returns {Object}
|
|
||||||
*/
|
|
||||||
Clay.prototype.getSettings = function(response) {
|
|
||||||
// Decode and parse config data as JSON
|
|
||||||
var settings = JSON.parse(decodeURIComponent(response));
|
|
||||||
|
|
||||||
if (!settings) return {};
|
|
||||||
|
|
||||||
localStorage.setItem('clay-settings', JSON.stringify(settings));
|
|
||||||
return settings;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Clay;
|
module.exports = Clay;
|
||||||
+3
-3
@@ -7,9 +7,9 @@
|
|||||||
"example": "example"
|
"example": "example"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test-travis": "./node_modules/.bin/karma start ./test/karma.conf.js --single-run --browsers chromeTravisCI && ./node_modules/.bin/eslint ./",
|
"test-travis": "./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --single-run --browsers chromeTravisCI && ./node_modules/.bin/eslint ./",
|
||||||
"test-debug": "(export DEBUG=true && ./node_modules/.bin/karma start ./test/karma.conf.js --no-single-run)",
|
"test-debug": "(export DEBUG=true && ./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --no-single-run)",
|
||||||
"test": "./node_modules/.bin/karma start ./test/karma.conf.js --single-run",
|
"test": "./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --single-run",
|
||||||
"lint": "./node_modules/.bin/eslint ./",
|
"lint": "./node_modules/.bin/eslint ./",
|
||||||
"build": "gulp",
|
"build": "gulp",
|
||||||
"dev": "gulp dev"
|
"dev": "gulp dev"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ var $ = require('../src/scripts/vendor/minified').$;
|
|||||||
var HTML = require('../src/scripts/vendor/minified').HTML;
|
var HTML = require('../src/scripts/vendor/minified').HTML;
|
||||||
var ClayItem = require('../src/scripts/lib/clay-item');
|
var ClayItem = require('../src/scripts/lib/clay-item');
|
||||||
var ClayConfig = require('../src/scripts/lib/clay-config');
|
var ClayConfig = require('../src/scripts/lib/clay-config');
|
||||||
|
var Clay = require('../index');
|
||||||
var components = require('../src/scripts/components');
|
var components = require('../src/scripts/components');
|
||||||
var componentRegistry = require('../src/scripts/lib/component-registry');
|
var componentRegistry = require('../src/scripts/lib/component-registry');
|
||||||
var idCounter = 0;
|
var idCounter = 0;
|
||||||
@@ -74,3 +75,19 @@ module.exports.clayConfig = function(types, build, autoRegister, settings) {
|
|||||||
return build === false ? clayConfig : clayConfig.build();
|
return build === false ? clayConfig : clayConfig.build();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} config - the Clay config
|
||||||
|
* @param {function} [customFn] - Custom code to run from the config page. Will run
|
||||||
|
* with the ClayConfig instance as context
|
||||||
|
* @param {Object} [options] - Additional options to pass to Clay
|
||||||
|
* @param {boolean} [options.autoHandleEvents] - If false, Clay will not
|
||||||
|
* automatically handle the 'showConfiguration' and 'webviewclosed' events
|
||||||
|
* @param {boolean} [destroyLocalStorage=true]
|
||||||
|
* @return {Clay}
|
||||||
|
*/
|
||||||
|
module.exports.clay = function(config, customFn, options, destroyLocalStorage) {
|
||||||
|
if (destroyLocalStorage !== false) {
|
||||||
|
localStorage.removeItem('clay-settings');
|
||||||
|
}
|
||||||
|
return new Clay(config, customFn, options);
|
||||||
|
};
|
||||||
@@ -52,6 +52,7 @@ module.exports = function(config) {
|
|||||||
|
|
||||||
// list of files / patterns to load in the browser
|
// list of files / patterns to load in the browser
|
||||||
files: [
|
files: [
|
||||||
|
'index.js',
|
||||||
'src/scripts/**/*.js',
|
'src/scripts/**/*.js',
|
||||||
'test/spec/**/*.js'
|
'test/spec/**/*.js'
|
||||||
],
|
],
|
||||||
@@ -62,6 +63,7 @@ module.exports = function(config) {
|
|||||||
// preprocess matching files before serving them to the browser
|
// preprocess matching files before serving them to the browser
|
||||||
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
|
||||||
preprocessors: {
|
preprocessors: {
|
||||||
|
'index.js': ['browserify'],
|
||||||
'src/scripts/**/*.js': ['browserify'],
|
'src/scripts/**/*.js': ['browserify'],
|
||||||
'test/spec/**/*.js': ['browserify']
|
'test/spec/**/*.js': ['browserify']
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -0,0 +1,219 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
var fixture = require('../fixture');
|
||||||
|
var Clay = require('../../index');
|
||||||
|
var assert = require('chai').assert;
|
||||||
|
var standardComponents = require('../../src/scripts/components');
|
||||||
|
var sinon = require('sinon');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function stubPebble() {
|
||||||
|
global.Pebble = {
|
||||||
|
addEventListener: sinon.stub(),
|
||||||
|
openURL: sinon.stub(),
|
||||||
|
sendAppMessage: sinon.stub()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Clay', function() {
|
||||||
|
describe('Clay constructor', function() {
|
||||||
|
it('throws if the config is not an array', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
fixture.clay({});
|
||||||
|
}, /must be an Array/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if customFn is not a function', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
fixture.clay([], {});
|
||||||
|
}, /must be a function/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not throw if customFn is undefined or a function', function() {
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
fixture.clay([], function() {});
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
fixture.clay([]).customFn();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('registers the standard components present in the config', function() {
|
||||||
|
var config = fixture.config(
|
||||||
|
['input', 'input', 'select', 'custom', ['color']],
|
||||||
|
false
|
||||||
|
);
|
||||||
|
var clay = fixture.clay(config);
|
||||||
|
assert.deepEqual(clay.components, {
|
||||||
|
input: standardComponents['input'],
|
||||||
|
select: standardComponents['select'],
|
||||||
|
color: standardComponents['color']
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the "showConfiguration" event if autoHandleEvents is not false',
|
||||||
|
function() {
|
||||||
|
stubPebble();
|
||||||
|
var clay = fixture.clay([]);
|
||||||
|
Pebble.addEventListener.withArgs('showConfiguration').callArg(1);
|
||||||
|
|
||||||
|
assert(Pebble.addEventListener.calledWith('showConfiguration'));
|
||||||
|
assert(Pebble.openURL.calledWith(clay.generateUrl()));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles the "webviewclosed" event if autoHandleEvents is not false',
|
||||||
|
function() {
|
||||||
|
stubPebble();
|
||||||
|
fixture.clay([]);
|
||||||
|
var logStub = sinon.stub(console, 'log');
|
||||||
|
Pebble.addEventListener
|
||||||
|
.withArgs('webviewclosed')
|
||||||
|
.callArgWith(1, { response: '%7B%22appKey%22%3A%22value%22%7D' });
|
||||||
|
|
||||||
|
assert(Pebble.addEventListener.calledWith('webviewclosed'));
|
||||||
|
assert(Pebble.sendAppMessage.calledWith({ appKey: 'value' }));
|
||||||
|
|
||||||
|
Pebble.sendAppMessage.callArg(1);
|
||||||
|
assert(logStub.calledWith('Sent config data to Pebble'));
|
||||||
|
|
||||||
|
Pebble.sendAppMessage.callArgWith(2, {some: 'error'});
|
||||||
|
assert(logStub.calledWith('Failed to send config data!'));
|
||||||
|
assert(logStub.calledWith('{"some":"error"}'));
|
||||||
|
|
||||||
|
logStub.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handles an empty response in the "webviewclosed" handler', function() {
|
||||||
|
stubPebble();
|
||||||
|
fixture.clay([]);
|
||||||
|
Pebble.addEventListener.withArgs('webviewclosed').callArgWith(1, undefined);
|
||||||
|
|
||||||
|
assert(Pebble.addEventListener.calledWith('webviewclosed'));
|
||||||
|
assert.strictEqual(Pebble.sendAppMessage.callCount, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not handle the "webviewclosed" or "showConfiguration" events ' +
|
||||||
|
'if autoHandleEvents is false', function() {
|
||||||
|
stubPebble();
|
||||||
|
fixture.clay([], null, { autoHandleEvents: false });
|
||||||
|
assert.strictEqual(Pebble.addEventListener.called, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.registerComponent()', function() {
|
||||||
|
it('adds the component to the this.components', function() {
|
||||||
|
var clay = fixture.clay([]);
|
||||||
|
var customComponent = {
|
||||||
|
name: 'custom',
|
||||||
|
template: '<div></div>',
|
||||||
|
manipulator: 'val'
|
||||||
|
};
|
||||||
|
clay.registerComponent(customComponent);
|
||||||
|
assert.strictEqual(clay.components[customComponent.name], customComponent);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.generateUrl()', function() {
|
||||||
|
it('does not replace $$RETURN_TO$$ if in the emulator', function() {
|
||||||
|
var clay = fixture.clay([]);
|
||||||
|
stubPebble();
|
||||||
|
Pebble.platform = 'pypkjs';
|
||||||
|
var decodedUrl =
|
||||||
|
atob(decodeURIComponent(clay.generateUrl().replace(/^.*?#/, '')));
|
||||||
|
assert.match(decodedUrl, /\$\$RETURN_TO\$\$/);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the emulator URL if inside emulator', function() {
|
||||||
|
var clay = fixture.clay([]);
|
||||||
|
stubPebble();
|
||||||
|
Pebble.platform = 'pypkjs';
|
||||||
|
assert.match(
|
||||||
|
clay.generateUrl(),
|
||||||
|
/^http:\/\/clay\.pebble\.com\.s3-website-us-west-2\.amazonaws.com\/#/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('doesn\'t throw and logs an error if settings in localStorage are broken',
|
||||||
|
function() {
|
||||||
|
var clay = fixture.clay([]);
|
||||||
|
var errorStub = sinon.stub(console, 'error');
|
||||||
|
localStorage.setItem('clay-settings', 'not valid JSON');
|
||||||
|
assert.doesNotThrow(function() {
|
||||||
|
clay.generateUrl();
|
||||||
|
});
|
||||||
|
assert(errorStub.calledWithMatch(/SyntaxError/i));
|
||||||
|
errorStub.restore();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('.getSettings()', function() {
|
||||||
|
it('stores the response to localStorage and returns the decoded data',
|
||||||
|
function() {
|
||||||
|
var clay = fixture.clay([]);
|
||||||
|
var result = clay.getSettings('%7B%22appKey%22%3A%22value%22%7D');
|
||||||
|
assert.equal(localStorage.getItem('clay-settings'), '{"appKey":"value"}');
|
||||||
|
assert.deepEqual(result, {appKey: 'value'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not store the response if it is invalid JSON and logs an error',
|
||||||
|
function() {
|
||||||
|
var clay = fixture.clay([]);
|
||||||
|
localStorage.setItem('clay-settings', '{"appKey":"value"}');
|
||||||
|
|
||||||
|
assert.throws(function() {
|
||||||
|
clay.getSettings('not valid JSON');
|
||||||
|
}, /Not Valid JSON/i);
|
||||||
|
assert.equal(localStorage.getItem('clay-settings'), '{"appKey":"value"}');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Clay.encodeDataUri()', function() {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
function testEncodeDataUri() {
|
||||||
|
it('adds the correct prefix', function() {
|
||||||
|
assert.equal(Clay.encodeDataUri('test', 'prefix:'), 'prefix:dGVzdA%3D%3D');
|
||||||
|
assert.equal(
|
||||||
|
Clay.encodeDataUri('test'),
|
||||||
|
'data:text/html;base64,dGVzdA%3D%3D'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes the data correctly', function() {
|
||||||
|
assert.equal(Clay.encodeDataUri('test', ''), 'dGVzdA%3D%3D');
|
||||||
|
assert.equal(Clay.encodeDataUri('test{2}', ''), 'dGVzdHsyfQ%3D%3D');
|
||||||
|
assert.equal(Clay.encodeDataUri('test{10}', ''), 'dGVzdHsxMH0%3D');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws if the input is invalid', function() {
|
||||||
|
assert.throws(function() {
|
||||||
|
Clay.encodeDataUri('♥');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('native', function() {
|
||||||
|
testEncodeDataUri();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('polyfill', function() {
|
||||||
|
var btoaOriginal = window.btoa;
|
||||||
|
|
||||||
|
before(function() {
|
||||||
|
window.btoa = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
testEncodeDataUri();
|
||||||
|
|
||||||
|
after(function() {
|
||||||
|
window.btoa = btoaOriginal;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in new issue
Block a user