Browse code

fixed untracked files

louis.jonget authored on24/01/2023 10:30:34
Showing1 changed files
1 1
old mode 100755
2 2
new mode 100644
Browse code

add handlerconfig

Louis authored on27/10/2021 21:00:13
Showing1 changed files
... ...
@@ -4,7 +4,9 @@ var configPageHtml = require('./tmp/config-page.html');
4 4
 var toSource = require('tosource');
5 5
 var standardComponents = require('./src/scripts/components');
6 6
 var deepcopy = require('deepcopy/build/deepcopy.min');
7
-var clayVersion = require('./package.json').version;
7
+var version = require('./package.json').version;
8
+var messageKeys = require('message_keys');
9
+
8 10
 /**
9 11
  * @param {Array} config - the Clay config
10 12
  * @param {function} [customFn] - Custom code to run from the config page. Will run
... ...
@@ -19,8 +21,6 @@ var clayVersion = require('./package.json').version;
19 21
 function Clay(config, customFn, options) {
20 22
   var self = this;
21 23
 
22
-  self.version = clayVersion;
23
-
24 24
   if (!Array.isArray(config)) {
25 25
     throw new Error('config must be an Array');
26 26
   }
... ...
@@ -40,6 +40,7 @@ function Clay(config, customFn, options) {
40 40
     watchToken: '',
41 41
     userData: {}
42 42
   };
43
+  self.version = version;
43 44
 
44 45
   /**
45 46
    * Populate the meta with data from the Pebble object. Make sure to run this inside
... ...
@@ -82,23 +83,50 @@ function Clay(config, customFn, options) {
82 83
   }
83 84
 
84 85
   /**
86
+   * If this function returns true then the callback will be executed
87
+   * @callback _scanConfig_testFn
88
+   * @param {Clay~ConfigItem} item
89
+   */
90
+
91
+  /**
92
+   * @callback _scanConfig_callback
93
+   * @param {Clay~ConfigItem} item
94
+   */
95
+
96
+  /**
97
+   * Scan over the config and run the callback if the testFn resolves to true
85 98
    * @private
86 99
    * @param {Clay~ConfigItem|Array} item
100
+   * @param {_scanConfig_testFn} testFn
101
+   * @param {_scanConfig_callback} callback
87 102
    * @return {void}
88 103
    */
89
-  function _registerStandardComponents(item) {
104
+  function _scanConfig(item, testFn, callback) {
90 105
     if (Array.isArray(item)) {
91 106
       item.forEach(function(item) {
92
-        _registerStandardComponents(item);
107
+        _scanConfig(item, testFn, callback);
93 108
       });
94 109
     } else if (item.type === 'section') {
95
-      _registerStandardComponents(item.items);
96
-    } else if (standardComponents[item.type]) {
97
-      self.registerComponent(standardComponents[item.type]);
110
+      _scanConfig(item.items, testFn, callback);
111
+    } else if (testFn(item)) {
112
+      callback(item);
98 113
     }
99 114
   }
100 115
 
101
-  _registerStandardComponents(self.config);
116
+  // register standard components
117
+  _scanConfig(self.config, function(item) {
118
+    return standardComponents[item.type];
119
+  }, function(item) {
120
+    self.registerComponent(standardComponents[item.type]);
121
+  });
122
+
123
+  // validate config against teh use of appKeys
124
+  _scanConfig(self.config, function(item) {
125
+    return item.appKey;
126
+  }, function() {
127
+    throw new Error('appKeys are no longer supported. ' +
128
+                    'Please follow the migration guide to upgrade your project');
129
+  });
102 130
 }
103 131
 
104 132
 /**
... ...
@@ -162,9 +190,10 @@ Clay.prototype.generateUrl = function() {
162 190
 Clay.prototype.getSettings = function(response, convert) {
163 191
   // Decode and parse config data as JSON
164 192
   var settings = {};
193
+  response = response.match(/^\{/) ? response : decodeURIComponent(response);
165 194
 
166 195
   try {
167
-    settings = JSON.parse(decodeURIComponent(response));
196
+    settings = JSON.parse(response);
168 197
   } catch (e) {
169 198
     throw new Error('The provided response was not valid JSON');
170 199
   }
... ...
@@ -184,6 +213,39 @@ Clay.prototype.getSettings = function(response, convert) {
184 213
   return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings);
185 214
 };
186 215
 
216
+/**
217
+ * Updates the settings with the given value(s).
218
+ *
219
+ * @signature `clay.setSettings(key, value)`
220
+ * @param {String} key - The property to set.
221
+ * @param {*} value - the value assigned to _key_.
222
+ * @return {undefined}
223
+ *
224
+ * @signature `clay.setSettings(settings)`
225
+ * @param {Object} settings - an object containing the key/value pairs to be set.
226
+ * @return {undefined}
227
+ */
228
+Clay.prototype.setSettings = function(key, value) {
229
+  var settingsStorage = {};
230
+
231
+  try {
232
+    settingsStorage = JSON.parse(localStorage.getItem('clay-settings')) || {};
233
+  } catch (e) {
234
+    console.error(e.toString());
235
+  }
236
+
237
+  if (typeof key === 'object') {
238
+    var settings = key;
239
+    Object.keys(settings).forEach(function(key) {
240
+      settingsStorage[key] = settings[key];
241
+    });
242
+  } else {
243
+    settingsStorage[key] = value;
244
+  }
245
+
246
+  localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
247
+};
248
+
187 249
 /**
188 250
  * @param {string} input
189 251
  * @param {string} [prefix='data:text/html;charset=utf-8,']
... ...
@@ -199,8 +261,8 @@ Clay.encodeDataUri = function(input, prefix) {
199 261
  *  - Strings will be returned without modification
200 262
  *  - Numbers will be returned without modification
201 263
  *  - Booleans will be converted to a 0 or 1
202
- *  - Arrays that contain strings will be split with a zero.
203
- *    eg: ['one', 'two'] becomes ['one', 0, 'two', 0]
264
+ *  - Arrays that contain strings will be returned without modification
265
+ *    eg: ['one', 'two'] becomes ['one', 'two']
204 266
  *  - Arrays that contain numbers will be returned without modification
205 267
  *    eg: [1, 2] becomes [1, 2]
206 268
  *  - Arrays that contain booleans will be converted to a 0 or 1
... ...
@@ -208,12 +270,12 @@ Clay.encodeDataUri = function(input, prefix) {
208 270
  *  - Arrays must be single dimensional
209 271
  *  - Objects that have a "value" property will apply the above rules to the type of
210 272
  *    value. If the value is a number or an array of numbers and the optional
211
- *    property: "precision" is provided, then the number will be multipled by 10 to
273
+ *    property: "precision" is provided, then the number will be multiplied by 10 to
212 274
  *    the power of precision (value * 10 ^ precision) and then floored.
213 275
  *    Eg: 1.4567 with a precision set to 3 will become 1456
214 276
  * @param {number|string|boolean|Array|Object} val
215 277
  * @param {number|string|boolean|Array} val.value
216
- * @param {number} [val.precision=0]
278
+ * @param {number|undefined} [val.precision=0]
217 279
  * @returns {number|string|Array}
218 280
  */
219 281
 Clay.prepareForAppMessage = function(val) {
... ...
@@ -234,12 +296,8 @@ Clay.prepareForAppMessage = function(val) {
234 296
 
235 297
   if (Array.isArray(val)) {
236 298
     result = [];
237
-    val.forEach(function(item) {
238
-      var itemConverted = Clay.prepareForAppMessage(item);
239
-      result.push(itemConverted);
240
-      if (typeof itemConverted === 'string') {
241
-        result.push(0);
242
-      }
299
+    val.forEach(function(item, index) {
300
+      result[index] = Clay.prepareForAppMessage(item);
243 301
     });
244 302
   } else if (typeof val === 'object' && val) {
245 303
     if (typeof val.value === 'number') {
... ...
@@ -265,16 +323,56 @@ Clay.prepareForAppMessage = function(val) {
265 323
 
266 324
 /**
267 325
  * Converts a Clay settings dict into one that is compatible with
268
- * Pebble.sendAppMessage();
326
+ * Pebble.sendAppMessage(); It also uses the provided messageKeys to correctly
327
+ * assign arrays into individual keys
269 328
  * @see {prepareForAppMessage}
270 329
  * @param {Object} settings
271 330
  * @returns {{}}
272 331
  */
273 332
 Clay.prepareSettingsForAppMessage = function(settings) {
274
-  var result = {};
333
+
334
+  // flatten settings
335
+  var flatSettings = {};
275 336
   Object.keys(settings).forEach(function(key) {
276
-    result[key] = Clay.prepareForAppMessage(settings[key]);
337
+    var val = settings[key];
338
+    var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);
339
+
340
+    if (!matches[2]) {
341
+      flatSettings[key] = val;
342
+      return;
343
+    }
344
+
345
+    var position = parseInt(matches[2], 10);
346
+    key = matches[1];
347
+
348
+    if (typeof flatSettings[key] === 'undefined') {
349
+      flatSettings[key] = [];
350
+    }
351
+
352
+    flatSettings[key][position] = val;
277 353
   });
354
+
355
+  var result = {};
356
+  Object.keys(flatSettings).forEach(function(key) {
357
+    var messageKey = messageKeys[key];
358
+    var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
359
+    settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];
360
+
361
+    settingArr.forEach(function(setting, index) {
362
+      result[messageKey + index] = setting;
363
+    });
364
+  });
365
+
366
+  // validate the settings
367
+  Object.keys(result).forEach(function(key) {
368
+    if (Array.isArray(result[key])) {
369
+      throw new Error('Clay does not support 2 dimensional arrays for item ' +
370
+                      'values. Make sure you are not attempting to use array ' +
371
+                      'syntax (eg: "myMessageKey[2]") in the messageKey for ' +
372
+                      'components that return an array, such as a checkboxgroup');
373
+    }
374
+  });
375
+
278 376
   return result;
279 377
 };
280 378
 
Browse code

pebble-clay

Louis authored on26/10/2021 23:21:03
Showing1 changed files
... ...
@@ -4,9 +4,7 @@ var configPageHtml = require('./tmp/config-page.html');
4 4
 var toSource = require('tosource');
5 5
 var standardComponents = require('./src/scripts/components');
6 6
 var deepcopy = require('deepcopy/build/deepcopy.min');
7
-var version = require('./package.json').version;
8
-var messageKeys = require('message_keys');
9
-
7
+var clayVersion = require('./package.json').version;
10 8
 /**
11 9
  * @param {Array} config - the Clay config
12 10
  * @param {function} [customFn] - Custom code to run from the config page. Will run
... ...
@@ -21,6 +19,8 @@ var messageKeys = require('message_keys');
21 19
 function Clay(config, customFn, options) {
22 20
   var self = this;
23 21
 
22
+  self.version = clayVersion;
23
+
24 24
   if (!Array.isArray(config)) {
25 25
     throw new Error('config must be an Array');
26 26
   }
... ...
@@ -40,7 +40,6 @@ function Clay(config, customFn, options) {
40 40
     watchToken: '',
41 41
     userData: {}
42 42
   };
43
-  self.version = version;
44 43
 
45 44
   /**
46 45
    * Populate the meta with data from the Pebble object. Make sure to run this inside
... ...
@@ -83,50 +82,23 @@ function Clay(config, customFn, options) {
83 82
   }
84 83
 
85 84
   /**
86
-   * If this function returns true then the callback will be executed
87
-   * @callback _scanConfig_testFn
88
-   * @param {Clay~ConfigItem} item
89
-   */
90
-
91
-  /**
92
-   * @callback _scanConfig_callback
93
-   * @param {Clay~ConfigItem} item
94
-   */
95
-
96
-  /**
97
-   * Scan over the config and run the callback if the testFn resolves to true
98 85
    * @private
99 86
    * @param {Clay~ConfigItem|Array} item
100
-   * @param {_scanConfig_testFn} testFn
101
-   * @param {_scanConfig_callback} callback
102 87
    * @return {void}
103 88
    */
104
-  function _scanConfig(item, testFn, callback) {
89
+  function _registerStandardComponents(item) {
105 90
     if (Array.isArray(item)) {
106 91
       item.forEach(function(item) {
107
-        _scanConfig(item, testFn, callback);
92
+        _registerStandardComponents(item);
108 93
       });
109 94
     } else if (item.type === 'section') {
110
-      _scanConfig(item.items, testFn, callback);
111
-    } else if (testFn(item)) {
112
-      callback(item);
95
+      _registerStandardComponents(item.items);
96
+    } else if (standardComponents[item.type]) {
97
+      self.registerComponent(standardComponents[item.type]);
113 98
     }
114 99
   }
115 100
 
116
-  // register standard components
117
-  _scanConfig(self.config, function(item) {
118
-    return standardComponents[item.type];
119
-  }, function(item) {
120
-    self.registerComponent(standardComponents[item.type]);
121
-  });
122
-
123
-  // validate config against teh use of appKeys
124
-  _scanConfig(self.config, function(item) {
125
-    return item.appKey;
126
-  }, function() {
127
-    throw new Error('appKeys are no longer supported. ' +
128
-                    'Please follow the migration guide to upgrade your project');
129
-  });
101
+  _registerStandardComponents(self.config);
130 102
 }
131 103
 
132 104
 /**
... ...
@@ -190,10 +162,9 @@ Clay.prototype.generateUrl = function() {
190 162
 Clay.prototype.getSettings = function(response, convert) {
191 163
   // Decode and parse config data as JSON
192 164
   var settings = {};
193
-  response = response.match(/^\{/) ? response : decodeURIComponent(response);
194 165
 
195 166
   try {
196
-    settings = JSON.parse(response);
167
+    settings = JSON.parse(decodeURIComponent(response));
197 168
   } catch (e) {
198 169
     throw new Error('The provided response was not valid JSON');
199 170
   }
... ...
@@ -213,39 +184,6 @@ Clay.prototype.getSettings = function(response, convert) {
213 184
   return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings);
214 185
 };
215 186
 
216
-/**
217
- * Updates the settings with the given value(s).
218
- *
219
- * @signature `clay.setSettings(key, value)`
220
- * @param {String} key - The property to set.
221
- * @param {*} value - the value assigned to _key_.
222
- * @return {undefined}
223
- *
224
- * @signature `clay.setSettings(settings)`
225
- * @param {Object} settings - an object containing the key/value pairs to be set.
226
- * @return {undefined}
227
- */
228
-Clay.prototype.setSettings = function(key, value) {
229
-  var settingsStorage = {};
230
-
231
-  try {
232
-    settingsStorage = JSON.parse(localStorage.getItem('clay-settings')) || {};
233
-  } catch (e) {
234
-    console.error(e.toString());
235
-  }
236
-
237
-  if (typeof key === 'object') {
238
-    var settings = key;
239
-    Object.keys(settings).forEach(function(key) {
240
-      settingsStorage[key] = settings[key];
241
-    });
242
-  } else {
243
-    settingsStorage[key] = value;
244
-  }
245
-
246
-  localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
247
-};
248
-
249 187
 /**
250 188
  * @param {string} input
251 189
  * @param {string} [prefix='data:text/html;charset=utf-8,']
... ...
@@ -261,8 +199,8 @@ Clay.encodeDataUri = function(input, prefix) {
261 199
  *  - Strings will be returned without modification
262 200
  *  - Numbers will be returned without modification
263 201
  *  - Booleans will be converted to a 0 or 1
264
- *  - Arrays that contain strings will be returned without modification
265
- *    eg: ['one', 'two'] becomes ['one', 'two']
202
+ *  - Arrays that contain strings will be split with a zero.
203
+ *    eg: ['one', 'two'] becomes ['one', 0, 'two', 0]
266 204
  *  - Arrays that contain numbers will be returned without modification
267 205
  *    eg: [1, 2] becomes [1, 2]
268 206
  *  - Arrays that contain booleans will be converted to a 0 or 1
... ...
@@ -270,12 +208,12 @@ Clay.encodeDataUri = function(input, prefix) {
270 208
  *  - Arrays must be single dimensional
271 209
  *  - Objects that have a "value" property will apply the above rules to the type of
272 210
  *    value. If the value is a number or an array of numbers and the optional
273
- *    property: "precision" is provided, then the number will be multiplied by 10 to
211
+ *    property: "precision" is provided, then the number will be multipled by 10 to
274 212
  *    the power of precision (value * 10 ^ precision) and then floored.
275 213
  *    Eg: 1.4567 with a precision set to 3 will become 1456
276 214
  * @param {number|string|boolean|Array|Object} val
277 215
  * @param {number|string|boolean|Array} val.value
278
- * @param {number|undefined} [val.precision=0]
216
+ * @param {number} [val.precision=0]
279 217
  * @returns {number|string|Array}
280 218
  */
281 219
 Clay.prepareForAppMessage = function(val) {
... ...
@@ -296,8 +234,12 @@ Clay.prepareForAppMessage = function(val) {
296 234
 
297 235
   if (Array.isArray(val)) {
298 236
     result = [];
299
-    val.forEach(function(item, index) {
300
-      result[index] = Clay.prepareForAppMessage(item);
237
+    val.forEach(function(item) {
238
+      var itemConverted = Clay.prepareForAppMessage(item);
239
+      result.push(itemConverted);
240
+      if (typeof itemConverted === 'string') {
241
+        result.push(0);
242
+      }
301 243
     });
302 244
   } else if (typeof val === 'object' && val) {
303 245
     if (typeof val.value === 'number') {
... ...
@@ -323,56 +265,16 @@ Clay.prepareForAppMessage = function(val) {
323 265
 
324 266
 /**
325 267
  * Converts a Clay settings dict into one that is compatible with
326
- * Pebble.sendAppMessage(); It also uses the provided messageKeys to correctly
327
- * assign arrays into individual keys
268
+ * Pebble.sendAppMessage();
328 269
  * @see {prepareForAppMessage}
329 270
  * @param {Object} settings
330 271
  * @returns {{}}
331 272
  */
332 273
 Clay.prepareSettingsForAppMessage = function(settings) {
333
-
334
-  // flatten settings
335
-  var flatSettings = {};
336
-  Object.keys(settings).forEach(function(key) {
337
-    var val = settings[key];
338
-    var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);
339
-
340
-    if (!matches[2]) {
341
-      flatSettings[key] = val;
342
-      return;
343
-    }
344
-
345
-    var position = parseInt(matches[2], 10);
346
-    key = matches[1];
347
-
348
-    if (typeof flatSettings[key] === 'undefined') {
349
-      flatSettings[key] = [];
350
-    }
351
-
352
-    flatSettings[key][position] = val;
353
-  });
354
-
355 274
   var result = {};
356
-  Object.keys(flatSettings).forEach(function(key) {
357
-    var messageKey = messageKeys[key];
358
-    var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
359
-    settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];
360
-
361
-    settingArr.forEach(function(setting, index) {
362
-      result[messageKey + index] = setting;
363
-    });
364
-  });
365
-
366
-  // validate the settings
367
-  Object.keys(result).forEach(function(key) {
368
-    if (Array.isArray(result[key])) {
369
-      throw new Error('Clay does not support 2 dimensional arrays for item ' +
370
-                      'values. Make sure you are not attempting to use array ' +
371
-                      'syntax (eg: "myMessageKey[2]") in the messageKey for ' +
372
-                      'components that return an array, such as a checkboxgroup');
373
-    }
275
+  Object.keys(settings).forEach(function(key) {
276
+    result[key] = Clay.prepareForAppMessage(settings[key]);
374 277
   });
375
-
376 278
   return result;
377 279
 };
378 280
 
Louis authored on25/08/2018 23:51:28
Showing1 changed files
1 1
new file mode 100755
... ...
@@ -0,0 +1,379 @@
1
+'use strict';
2
+
3
+var configPageHtml = require('./tmp/config-page.html');
4
+var toSource = require('tosource');
5
+var standardComponents = require('./src/scripts/components');
6
+var deepcopy = require('deepcopy/build/deepcopy.min');
7
+var version = require('./package.json').version;
8
+var messageKeys = require('message_keys');
9
+
10
+/**
11
+ * @param {Array} config - the Clay config
12
+ * @param {function} [customFn] - Custom code to run from the config page. Will run
13
+ *   with the ClayConfig instance as context
14
+ * @param {Object} [options] - Additional options to pass to Clay
15
+ * @param {boolean} [options.autoHandleEvents=true] - If false, Clay will not
16
+ *   automatically handle the 'showConfiguration' and 'webviewclosed' events
17
+ * @param {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
18
+ *   be available as `clayConfig.meta.userData`
19
+ * @constructor
20
+ */
21
+function Clay(config, customFn, options) {
22
+  var self = this;
23
+
24
+  if (!Array.isArray(config)) {
25
+    throw new Error('config must be an Array');
26
+  }
27
+
28
+  if (customFn && typeof customFn !== 'function') {
29
+    throw new Error('customFn must be a function or "null"');
30
+  }
31
+
32
+  options = options || {};
33
+
34
+  self.config = deepcopy(config);
35
+  self.customFn = customFn || function() {};
36
+  self.components = {};
37
+  self.meta = {
38
+    activeWatchInfo: null,
39
+    accountToken: '',
40
+    watchToken: '',
41
+    userData: {}
42
+  };
43
+  self.version = version;
44
+
45
+  /**
46
+   * Populate the meta with data from the Pebble object. Make sure to run this inside
47
+   * either the "showConfiguration" or "ready" event handler
48
+   * @return {void}
49
+   */
50
+  function _populateMeta() {
51
+    self.meta = {
52
+      activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
53
+      accountToken: Pebble.getAccountToken(),
54
+      watchToken: Pebble.getWatchToken(),
55
+      userData: deepcopy(options.userData || {})
56
+    };
57
+  }
58
+
59
+  // Let Clay handle all the magic
60
+  if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {
61
+
62
+    Pebble.addEventListener('showConfiguration', function() {
63
+      _populateMeta();
64
+      Pebble.openURL(self.generateUrl());
65
+    });
66
+
67
+    Pebble.addEventListener('webviewclosed', function(e) {
68
+
69
+      if (!e || !e.response) { return; }
70
+
71
+      // Send settings to Pebble watchapp
72
+      Pebble.sendAppMessage(self.getSettings(e.response), function() {
73
+        console.log('Sent config data to Pebble');
74
+      }, function(error) {
75
+        console.log('Failed to send config data!');
76
+        console.log(JSON.stringify(error));
77
+      });
78
+    });
79
+  } else if (typeof Pebble !== 'undefined') {
80
+    Pebble.addEventListener('ready', function() {
81
+      _populateMeta();
82
+    });
83
+  }
84
+
85
+  /**
86
+   * If this function returns true then the callback will be executed
87
+   * @callback _scanConfig_testFn
88
+   * @param {Clay~ConfigItem} item
89
+   */
90
+
91
+  /**
92
+   * @callback _scanConfig_callback
93
+   * @param {Clay~ConfigItem} item
94
+   */
95
+
96
+  /**
97
+   * Scan over the config and run the callback if the testFn resolves to true
98
+   * @private
99
+   * @param {Clay~ConfigItem|Array} item
100
+   * @param {_scanConfig_testFn} testFn
101
+   * @param {_scanConfig_callback} callback
102
+   * @return {void}
103
+   */
104
+  function _scanConfig(item, testFn, callback) {
105
+    if (Array.isArray(item)) {
106
+      item.forEach(function(item) {
107
+        _scanConfig(item, testFn, callback);
108
+      });
109
+    } else if (item.type === 'section') {
110
+      _scanConfig(item.items, testFn, callback);
111
+    } else if (testFn(item)) {
112
+      callback(item);
113
+    }
114
+  }
115
+
116
+  // register standard components
117
+  _scanConfig(self.config, function(item) {
118
+    return standardComponents[item.type];
119
+  }, function(item) {
120
+    self.registerComponent(standardComponents[item.type]);
121
+  });
122
+
123
+  // validate config against teh use of appKeys
124
+  _scanConfig(self.config, function(item) {
125
+    return item.appKey;
126
+  }, function() {
127
+    throw new Error('appKeys are no longer supported. ' +
128
+                    'Please follow the migration guide to upgrade your project');
129
+  });
130
+}
131
+
132
+/**
133
+ * Register a component to Clay.
134
+ * @param {Object} component - the clay component to register
135
+ * @param {string} component.name - the name of the component
136
+ * @param {string} component.template - HTML template to use for the component
137
+ * @param {string|Object} component.manipulator - methods to attach to the component
138
+ * @param {function} component.manipulator.set - set manipulator method
139
+ * @param {function} component.manipulator.get - get manipulator method
140
+ * @param {Object} [component.defaults] - template defaults
141
+ * @param {function} [component.initialize] - method to scaffold the component
142
+ * @return {boolean} - Returns true if component was registered correctly
143
+ */
144
+Clay.prototype.registerComponent = function(component) {
145
+  this.components[component.name] = component;
146
+};
147
+
148
+/**
149
+ * Generate the Data URI used by the config Page with settings injected
150
+ * @return {string}
151
+ */
152
+Clay.prototype.generateUrl = function() {
153
+  var settings = {};
154
+  var emulator = !Pebble || Pebble.platform === 'pypkjs';
155
+  var returnTo = emulator ? '$$$RETURN_TO$$$' : 'pebblejs://close#';
156
+
157
+  try {
158
+    settings = JSON.parse(localStorage.getItem('clay-settings')) || {};
159
+  } catch (e) {
160
+    console.error(e.toString());
161
+  }
162
+
163
+  var compiledHtml = configPageHtml
164
+    .replace('$$RETURN_TO$$', returnTo)
165
+    .replace('$$CUSTOM_FN$$', toSource(this.customFn))
166
+    .replace('$$CONFIG$$', toSource(this.config))
167
+    .replace('$$SETTINGS$$', toSource(settings))
168
+    .replace('$$COMPONENTS$$', toSource(this.components))
169
+    .replace('$$META$$', toSource(this.meta));
170
+
171
+  // if we are in the emulator then we need to proxy the data via a webpage to
172
+  // obtain the return_to.
173
+  // @todo calculate this from the Pebble object or something
174
+  if (emulator) {
175
+    return Clay.encodeDataUri(
176
+      compiledHtml,
177
+      'http://clay.pebble.com.s3-website-us-west-2.amazonaws.com/#'
178
+    );
179
+  }
180
+
181
+  return Clay.encodeDataUri(compiledHtml);
182
+};
183
+
184
+/**
185
+ * Parse the response from the webviewclosed event data
186
+ * @param {string} response
187
+ * @param {boolean} [convert=true]
188
+ * @returns {Object}
189
+ */
190
+Clay.prototype.getSettings = function(response, convert) {
191
+  // Decode and parse config data as JSON
192
+  var settings = {};
193
+  response = response.match(/^\{/) ? response : decodeURIComponent(response);
194
+
195
+  try {
196
+    settings = JSON.parse(response);
197
+  } catch (e) {
198
+    throw new Error('The provided response was not valid JSON');
199
+  }
200
+
201
+  // flatten the settings for localStorage
202
+  var settingsStorage = {};
203
+  Object.keys(settings).forEach(function(key) {
204
+    if (typeof settings[key] === 'object' && settings[key]) {
205
+      settingsStorage[key] = settings[key].value;
206
+    } else {
207
+      settingsStorage[key] = settings[key];
208
+    }
209
+  });
210
+
211
+  localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
212
+
213
+  return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings);
214
+};
215
+
216
+/**
217
+ * Updates the settings with the given value(s).
218
+ *
219
+ * @signature `clay.setSettings(key, value)`
220
+ * @param {String} key - The property to set.
221
+ * @param {*} value - the value assigned to _key_.
222
+ * @return {undefined}
223
+ *
224
+ * @signature `clay.setSettings(settings)`
225
+ * @param {Object} settings - an object containing the key/value pairs to be set.
226
+ * @return {undefined}
227
+ */
228
+Clay.prototype.setSettings = function(key, value) {
229
+  var settingsStorage = {};
230
+
231
+  try {
232
+    settingsStorage = JSON.parse(localStorage.getItem('clay-settings')) || {};
233
+  } catch (e) {
234
+    console.error(e.toString());
235
+  }
236
+
237
+  if (typeof key === 'object') {
238
+    var settings = key;
239
+    Object.keys(settings).forEach(function(key) {
240
+      settingsStorage[key] = settings[key];
241
+    });
242
+  } else {
243
+    settingsStorage[key] = value;
244
+  }
245
+
246
+  localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
247
+};
248
+
249
+/**
250
+ * @param {string} input
251
+ * @param {string} [prefix='data:text/html;charset=utf-8,']
252
+ * @returns {string}
253
+ */
254
+Clay.encodeDataUri = function(input, prefix) {
255
+  prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;charset=utf-8,';
256
+  return prefix + encodeURIComponent(input);
257
+};
258
+
259
+/**
260
+ * Converts the val into a type compatible with Pebble.sendAppMessage().
261
+ *  - Strings will be returned without modification
262
+ *  - Numbers will be returned without modification
263
+ *  - Booleans will be converted to a 0 or 1
264
+ *  - Arrays that contain strings will be returned without modification
265
+ *    eg: ['one', 'two'] becomes ['one', 'two']
266
+ *  - Arrays that contain numbers will be returned without modification
267
+ *    eg: [1, 2] becomes [1, 2]
268
+ *  - Arrays that contain booleans will be converted to a 0 or 1
269
+ *    eg: [true, false] becomes [1, 0]
270
+ *  - Arrays must be single dimensional
271
+ *  - Objects that have a "value" property will apply the above rules to the type of
272
+ *    value. If the value is a number or an array of numbers and the optional
273
+ *    property: "precision" is provided, then the number will be multiplied by 10 to
274
+ *    the power of precision (value * 10 ^ precision) and then floored.
275
+ *    Eg: 1.4567 with a precision set to 3 will become 1456
276
+ * @param {number|string|boolean|Array|Object} val
277
+ * @param {number|string|boolean|Array} val.value
278
+ * @param {number|undefined} [val.precision=0]
279
+ * @returns {number|string|Array}
280
+ */
281
+Clay.prepareForAppMessage = function(val) {
282
+
283
+  /**
284
+   * moves the decimal place of a number by precision then drop any remaining decimal
285
+   * places.
286
+   * @param {number} number
287
+   * @param {number} precision - number of decimal places to move
288
+   * @returns {number}
289
+   * @private
290
+   */
291
+  function _normalizeToPrecision(number, precision) {
292
+    return Math.floor(number * Math.pow(10, precision || 0));
293
+  }
294
+
295
+  var result;
296
+
297
+  if (Array.isArray(val)) {
298
+    result = [];
299
+    val.forEach(function(item, index) {
300
+      result[index] = Clay.prepareForAppMessage(item);
301
+    });
302
+  } else if (typeof val === 'object' && val) {
303
+    if (typeof val.value === 'number') {
304
+      result = _normalizeToPrecision(val.value, val.precision);
305
+    } else if (Array.isArray(val.value)) {
306
+      result = val.value.map(function(item) {
307
+        if (typeof item === 'number') {
308
+          return _normalizeToPrecision(item, val.precision);
309
+        }
310
+        return item;
311
+      });
312
+    } else {
313
+      result = Clay.prepareForAppMessage(val.value);
314
+    }
315
+  } else if (typeof val === 'boolean') {
316
+    result = val ? 1 : 0;
317
+  } else {
318
+    result = val;
319
+  }
320
+
321
+  return result;
322
+};
323
+
324
+/**
325
+ * Converts a Clay settings dict into one that is compatible with
326
+ * Pebble.sendAppMessage(); It also uses the provided messageKeys to correctly
327
+ * assign arrays into individual keys
328
+ * @see {prepareForAppMessage}
329
+ * @param {Object} settings
330
+ * @returns {{}}
331
+ */
332
+Clay.prepareSettingsForAppMessage = function(settings) {
333
+
334
+  // flatten settings
335
+  var flatSettings = {};
336
+  Object.keys(settings).forEach(function(key) {
337
+    var val = settings[key];
338
+    var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);
339
+
340
+    if (!matches[2]) {
341
+      flatSettings[key] = val;
342
+      return;
343
+    }
344
+
345
+    var position = parseInt(matches[2], 10);
346
+    key = matches[1];
347
+
348
+    if (typeof flatSettings[key] === 'undefined') {
349
+      flatSettings[key] = [];
350
+    }
351
+
352
+    flatSettings[key][position] = val;
353
+  });
354
+
355
+  var result = {};
356
+  Object.keys(flatSettings).forEach(function(key) {
357
+    var messageKey = messageKeys[key];
358
+    var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
359
+    settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];
360
+
361
+    settingArr.forEach(function(setting, index) {
362
+      result[messageKey + index] = setting;
363
+    });
364
+  });
365
+
366
+  // validate the settings
367
+  Object.keys(result).forEach(function(key) {
368
+    if (Array.isArray(result[key])) {
369
+      throw new Error('Clay does not support 2 dimensional arrays for item ' +
370
+                      'values. Make sure you are not attempting to use array ' +
371
+                      'syntax (eg: "myMessageKey[2]") in the messageKey for ' +
372
+                      'components that return an array, such as a checkboxgroup');
373
+    }
374
+  });
375
+
376
+  return result;
377
+};
378
+
379
+module.exports = Clay;