Compare commits

..
25 Commits
Author SHA1 Message Date
keegan-lillo f717661eea Merge pull request #85 from pebble/release/0.1.6
Bump version to 0.1.6
2016-05-30 22:10:12 -07:00
keegan-lillo 35bdeb7519 Bump version to 0.1.6 2016-05-30 22:06:54 -07:00
keegan-lillo 93b927db63 Merge pull request #83 from pebble/#18/show-items-based-on-capability
Add the option to only show items with particular capabilities
2016-05-30 22:00:33 -07:00
Keegan 9afdd2adb9 add warning about capabilities 2016-05-30 21:54:16 -07:00
Keegan 5fe87a04de Add the option to only show items with particular capabilities 2016-05-30 20:30:17 -07:00
keegan-lillo 7e2b75dd58 Merge pull request #82 from pebble/#10/remove-old-settings
Only send settings that are actually in the config back to the watch
2016-05-29 11:41:54 -07:00
Keegan 32e192e938 Only send settings that are actually in the config back to the watch 2016-05-29 11:38:00 -07:00
keegan-lillo 1af2b39db1 Merge pull request #81 from pebble/#72/att-optgroup-to-selects
Add optgroup to selects
2016-05-28 14:57:16 -07:00
Keegan 782eaf82f3 Document optgroups for selects 2016-05-28 14:50:06 -07:00
Keegan 41d38e04a7 Allow optgroups for selects 2016-05-28 14:41:45 -07:00
keegan-lillo 1611599a25 Merge pull request #80 from pebble/#74/fix-select-styling-on-windows
fix styling of selects on windows
2016-05-28 14:19:16 -07:00
keegan-lillo 842cbc9e84 Merge branch 'master' into #74/fix-select-styling-on-windows 2016-05-28 14:15:45 -07:00
keegan-lillo 238d0e6a6c Merge pull request #79 from pebble/#70/fix-time-date-styling-on-ios
fix styling of time and date fields on iOS
2016-05-28 14:14:38 -07:00
Keegan c5af3baaeb fix styling of selects on windows 2016-05-28 14:12:31 -07:00
Keegan 2030aa1af7 fix styling of time and date fields on iOS 2016-05-28 13:52:27 -07:00
keegan-lillo 4445cb98cf Merge pull request #78 from pebble/#76/wipe-page-on-build
#76/wipe page on build
2016-05-28 13:26:22 -07:00
Keegan 12c420d861 make ClayConfig.destroy() return self 2016-05-28 13:11:53 -07:00
Keegan dc33aa0b5d update docs 2016-05-28 13:09:04 -07:00
keegan-lillo f200af909b Merge branch 'master' into #76/wipe-page-on-build 2016-05-28 13:02:34 -07:00
Keegan 6f3873ad6b add tests for new clayConfig.build() behavior 2016-05-28 13:00:55 -07:00
Keegan 70bb2e9d07 Add destroy function 2016-05-26 18:45:22 -07:00
keegan-lillo a9aaf0b940 Merge pull request #68 from ishotjr/ishotjr-appinfo
📝 Update README to include required appinfo.json changes
2016-04-04 08:56:36 -07:00
Ish Ot Jr 80eb1506d8 📝 Update README to include required appinfo.json changes 2016-04-03 12:14:41 -04:00
keegan-lillo 68723d36f7 Merge pull request #67 from gregoiresage/master
Always display all the decimals of the slider's value
2016-03-28 13:34:22 -07:00
Grégoire Sage c937ee735e always display all the decimals of the slider's value
Add test
2016-03-28 22:20:23 +02:00
15 changed files with 741 additions and 39 deletions
+130 -2
View File
@@ -20,7 +20,8 @@ Clay will eventually be built into the Pebble SDK. However, while it is still in
var clayConfig = require('config.json');
var clay = new Clay(clayConfig);
```
6. Next is the fun part - creating your config page. Edit your `config.js` file to build a layout of elements as described in the sections below.
6. Add `configurable` to the `capabilities` array in your `appinfo.json`.
7. Next is the fun part - creating your config page. Edit your `config.js` file to build a layout of elements as described in the sections below.
# Getting Started (CloudPebble)
@@ -82,6 +83,7 @@ Sections help divide up the page into logical groups of items. It is recommended
|----------|------|-------------|
| type | string | Set to `section`. |
| items | array | Array of items to include in this section. |
| capabilities | array | Array of features that the connected watch must have for this section to be present |
##### Example
@@ -124,6 +126,7 @@ Headings can be used in anywhere and can have their size adjusted to suit the co
| appKey | string (unique) | The AppMessage key matching the `appKey` item defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be persisted after the user closes the config page. |
| defaultValue | string/HTML | The heading's text. |
| size | int | Defaults to `4`. An integer from 1 to 6 where 1 is the largest size and 6 is the smallest. (represents HTML `<h1>`, `<h2>`, `<h3>`, etc). |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -153,6 +156,7 @@ Text is used to provide descriptions of sections or to explain complex parts of
| id | string (unique) | Set this to a unique string to allow this item to be looked up using `Clay.getItemsById()` in your [custom function](#custom-function). |
| appKey | string (unique) | The AppMessage key matching the `appKey` item defined in your `appinfo.json`. Set this to a unique string to allow this item to be looked up using `Clay.getItemsByAppKey()` in your custom function. You must set this if you wish for the value of this item to be persisted after the user closes the config page. |
| defaultValue | string/HTML | The content of the text element. |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -183,6 +187,7 @@ Standard text input field.
| defaultValue | string | The default value of the input field. |
| description | string | Optional sub-text to include below the component |
| attributes | object | An object containing HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attributes such as `required` or `type`. |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -221,6 +226,7 @@ Switch for a single item.
| defaultValue | int\|boolean | The default value of the toggle. Defaults to `false` if not specified. |
| description | string | Optional sub-text to include below the component |
| attributes | object | An object containing HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`. |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -257,6 +263,7 @@ A dropdown menu containing multiple options.
| description | string | Optional sub-text to include below the component |
| attributes | object | An object containing HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`. |
| options | array of objects | The options you want to appear in the dropdown menu. Each option is an object with a `label` and `value` property. |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -290,6 +297,60 @@ A dropdown menu containing multiple options.
}
```
If you wish to use optgroups, then use the following format:
```javascript
{
"type": "select",
"appKey": "flavor",
"defaultValue": "grape",
"label": "Favorite Flavor",
"options": [
{
"label": "",
"value": ""
},
{
"label": "Fruits",
"value": [
{
"label": "Berry",
"value": "berry"
},
{
"label": "Grape",
"value": "grape"
},
{
"label": "Banana",
"value": "banana"
}
]
},
{
"label": "Candy",
"value": [
{
"label": "Chocolate",
"value": "chocolate"
},
{
"label": "Cream",
"value": "cream"
},
{
"label": "Lollipop",
"value": "lollipop"
}
]
}
],
"attributes": {
"required": "required"
}
}
```
---
#### Color Picker
@@ -316,6 +377,7 @@ The color picker will automatically show a different layout depending on the wat
| sunlight | boolean | Use the color-corrected sunlight color palette if `true`, else the uncorrected version. Defaults to `true` if not specified. |
| layout | string OR array | Optional. Use a custom layout for the color picker. Defaults to automatically choosing the most appropriate layout for the connected watch. The layout is represented by a two dimensional array. Use `false` to insert blank spaces. You may also use one of the preset layouts by setting `layout` to: `"COLOR"`, `"GRAY"` or `"BLACK_WHITE"` |
| allowGray | boolean | Optional. Set this to `true` to include gray (`#AAAAAA`) in the color picker for aplite running on firmware 3 and above. This is optional because only a subset of the drawing operations support gray on aplite. Defaults to `false` |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -380,6 +442,7 @@ A list of options allowing the user can only choose one option to submit.
| description | string | Optional sub-text to include below the component |
| attributes | object | An object containing HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`. |
| options | array of objects | The options you want to appear in the radio group. Each option is an object with a `label` and `value` property. |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -425,6 +488,7 @@ A list of options where a user may choose more than one option to submit.
| description | string | Optional sub-text to include below the component |
| attributes | object | An object containing HTML attributes to set on the input field. You can add basic HTML5 validation this way by setting attribute such as `required`. |
| options | array of objects | The options you want to appear in the checkbox group. Each option is an object with a `label` and `value` property. |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -466,6 +530,7 @@ A list of options where a user may choose more than one option to submit.
| primary | boolean | If `true` the button will be orange, if `false`, the button will be gray (defaults to `false`)|
| attributes | object | An object containing HTML attributes to set on the input field. |
| description | string | Optional sub-text to include below the component |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -504,6 +569,7 @@ you must just remember to divide the received value on the watch accordingly.
| step | number | The multiple of the values allowed to be set on the slider. The slider will snap to these values. This value also determines the precision used when the value is sent to the watch. Defaults to 1 |
| attributes | object | An object containing HTML attributes to set on the input field. |
| description | string | Optional sub-text to include below the component |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -535,6 +601,7 @@ The submit button for the page. You **MUST** include this component somewhere in
| type | string | Set to `submit`. |
| defaultValue | string | The text displayed on the button. |
| attributes | object | An object containing HTML attributes to set on the input field. |
| capabilities | array | Array of features that the connected watch must have for this item to be present |
##### Example
@@ -554,6 +621,64 @@ The submit button for the page. You **MUST** include this component somewhere in
- Dynamic + draggable list
---
### Showing items for specific platforms and features
If you want particular items or sections to only be present in the config based on the
connected watch's capabilities, you can include the "capabilities" property in the item.
The "capabilities" property is an array of features that the connected watch must have
in order for the item or section to be included in the page. Note: all capabilities in
the array must be present for item/section to be included.
**Warning:** Items that do not satisfy the capabilities will not be included in the page
at all. You will not be able to use methods like `clayConfig.getItemByAppKey()` to
obttain a reference to them. However, this does mean that you will be able to have
multiple items with the same `appKey`as long as they do not both satisfy the
same conditions.
##### Examples
```javascript
{
"type": "text",
"defaultValue": "This item will only be visible for watches that are rectangular and have a microphone"
"capabilities": ["MICROPHONE", "RECT"],
}
```
```javascript
{
"type": "section",
"capabilities": ["COLOR"],
"items": [
{
"type": "text",
"defaultValue": "Only visible for color watches"
}
]
}
```
Below is the full list of capabilities
| Capability | Description |
|------------|-------------|
| APLITE | Running on Pebble/Pebble Steel.|
| BASALT | Running on Pebble Time/Pebble Time Steel. |
| CHALK | Running on Pebble Time Round. |
| DIORITE | Running on Pebble 2 |
| EMERY | Running on Time 2. |
| BW | Running on hardware that supports only black and white. |
| COLOR | Running on hardware that supports 64 colors. |
| MICROPHONE | Running on hardware that includes a microphone. |
| SMARTSTRAP | Running on hardware that includes a smartstrap connector. |
| SMARTSTRAP_POWER | Running on hardware that includes a powered smartstrap connector. |
| HEALTH | Running on hardware that supports Pebble Health and the HealthService API. |
| RECT | Running on hardware with a rectangular display. |
| ROUND | Running on hardware with a round display. |
| DISPLAY_144x168 | Running on hardware with a 144x168 pixels display. |
| DISPLAY_180x180_ROUND | Running on hardware with a 180x180 pixels round display. |
| DISPLAY_200x228 | Running on hardware with a 200x228 pixels display. |
## Manipulators
@@ -853,6 +978,8 @@ This is the main way of talking to your generated config page. An instance of th
|----------|------|-------------|
| `.EVENTS.BEFORE_BUILD` | String | Dispatched prior to building the page. |
| `.EVENTS.AFTER_BUILD` | String | Dispatched after building the page. |
| `.EVENTS.BEFORE_DESTROY` | String | Dispatched prior to destroying the page. |
| `.EVENTS.AFTER_DESTROY` | String | Dispatched after destroying the page. |
| `.config` | Array | Reference to the config passed to the constructor and used for generating the page. |
| `.meta` | Object | Contains information about the current user and watch |
| `.meta.activeWatchInfo` | watchinfo\|null | An object containing information on the currently connected Pebble smartwatch or null if unavailable. Read more [here](https://developer.pebble.com/docs/js/Pebble/#getActiveWatchInfo). |
@@ -870,7 +997,8 @@ This is the main way of talking to your generated config page. An instance of th
| `.getItemById( [string] id )` | `ConfigItem\|undefined` - a single `ConfigItem` that has the provided `id`, otherwise `undefined`. |
| `.getItemsByType( [string] type )` | `Array.<ConfigItem>` - an array of config items that match the provided `type`. |
| `.serialize()` | `Object` - an object representing all items with an `appKey` where the key is the `appKey` and the value is an object with the `value` property set to the result of running `.get()` on the Clay item. If the Clay item has a `precision` property set, it is included in the object |
| `.build()` <br> Builds the config page. Will dispatch the `BEFORE_BUILD` event prior to building the page, then the `AFTER_BUILD` event once it is complete. | `ClayConfig` |
| `.build()` <br> Builds the config page. Will dispatch the `BEFORE_BUILD` event prior to building the page, then the `AFTER_BUILD` event once it is complete. If the config page has already been built, then the `ClayConfig.destroy()` method will be executed prior to building the page again. | `ClayConfig` |
| `.destroy()` <br> Destroys the config page. Will dispatch the `BEFORE_DESTROY` event prior to destroying the page, then the `AFTER_DESTROY` event once it is complete. This method wipes the config page completely, including all existing items. You will need to make sure that you re-attach your event handlers for any items that are replaced | `ClayConfig` |
| `.on( [string] events, [function] handler )` <br> Register an event to the provided handler. The handler will be called with this instance of `ClayConfig` as the context. If you wish to register multiple events to the same handler, then separate the events with a space | `ClayConfig` |
| `.off( [function] handler )` <br> Remove the given event handler. **NOTE:** This will remove the handler from all registered events. | `ClayConfig` |
| `.trigger( [string] name, [object] eventObj={} )` <br> Trigger the provided event and optionally pass extra data to the handler. | `ClayConfig` |
+38
View File
@@ -0,0 +1,38 @@
'use strict';
/* eslint-disable quotes */
module.exports = [
{
"type": "heading",
"id": "main-heading",
"defaultValue": "Clay Test Page",
"size": 1
},
{
"type": "text",
"defaultValue": "This is config 2"
},
{
"type": "section",
"items": [
{
"type": "heading",
"defaultValue": "This is a section"
},
{
"type": "input",
"appKey": "email",
"defaultValue": "",
"label": "Input Field",
"description": "This is a new field",
"attributes": {
"placeholder": "Placeholder set with attributes"
}
}
]
},
{
"type": "submit",
"defaultValue": "Save"
}
];
+38 -1
View File
@@ -13,6 +13,17 @@ module.exports = [
"defaultValue": "Some arbitrary text explaining how this all works. " +
"It's cool if this wraps across multiple lines"
},
{
"type": "section",
"capabilities": ["RECT", "MICROPHONE"],
"items": [
{
"type": "text",
"defaultValue": "This section is only visible for rectangular devices that" +
" have a microphone"
}
]
},
{
"type": "section",
"items": [
@@ -20,6 +31,11 @@ module.exports = [
"type": "heading",
"defaultValue": "This is a section"
},
{
"type": "text",
"defaultValue": "This is only visible for aplite",
"capabilities": ["APLITE"]
},
{
"type": "input",
"appKey": "email",
@@ -31,6 +47,20 @@ module.exports = [
"placeholder": "Placeholder set with attributes"
}
},
{
"type": "input",
"label": "Time Field",
"attributes": {
"type": "time"
}
},
{
"type": "input",
"label": "Date Field",
"attributes": {
"type": "date"
}
},
{
"type": "slider",
"appKey": "slider",
@@ -108,7 +138,14 @@ module.exports = [
{ "label": "", "value": "" },
{ "label": "Berry", "value": "berry" },
{ "label": "This Option is Selected", "value": "grape" },
{ "label": "Banana", "value": "banana" }
{ "label": "Banana", "value": "banana" },
{
"label": "This is an optgroup",
"value": [
{ "label": "Peach", "value": "peach" },
{ "label": "Mango", "value": "mango" }
]
}
],
"attributes": {
"required": "required"
+10
View File
@@ -16,10 +16,20 @@ module.exports = function() {
}
}
/**
* @returns {void}
*/
function handleButtonClick() {
Clay.config = Clay.meta.userData.config2;
Clay.build();
}
Clay.on(Clay.EVENTS.AFTER_BUILD, function() {
var coolStuffToggle = Clay.getItemByAppKey('cool_stuff');
toggleBackground.call(coolStuffToggle);
coolStuffToggle.on('change', toggleBackground);
Clay.getItemById('testButton').on('click', handleButtonClick);
});
console.log('userData: ', Clay.meta.userData);
+15 -1
View File
@@ -6,7 +6,21 @@ window.claySettings = {};
window.customFn = require('./custom-fn.js');
window.clayComponents = require('../src/scripts/components');
window.clayMeta = require('../test/fixture').meta({
userData: {foo: 'bar'}
activeWatchInfo: {
platform: 'basalt',
model: 'qemu_platform_basalt',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 2,
suffix: ''
}
},
userData: {
foo: 'bar',
config2: require('./config-2')
}
});
var platform = window.navigator.userAgent.match(/Android/) ? 'android' : 'ios';
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "pebble-clay",
"version": "0.1.5",
"version": "0.1.6",
"description": "Pebble Config Framework",
"main": "dist/clay.js",
"directories": {
+1 -1
View File
@@ -25,7 +25,7 @@ module.exports = {
* @return {void}
*/
function setValueDisplay() {
var value = self.get();
var value = self.get().toFixed(self.precision);
$value.set('value', value);
$valuePad.set('innerHTML', value);
}
+86 -31
View File
@@ -11,6 +11,7 @@
* @property {Object} [attributes]
* @property {Array} [options]
* @property {Array} [items]
* @property {Array} [capabilities]
*/
var HTML = require('../vendor/minified').HTML;
@@ -33,10 +34,22 @@ function ClayConfig(settings, config, $rootContainer, meta) {
var self = this;
var _settings = _.copyObj(settings);
var _items = [];
var _itemsById = {};
var _itemsByAppKey = {};
var _isBuilt = false;
var _items;
var _itemsById;
var _itemsByAppKey;
var _isBuilt;
/**
* Initialize the item arrays and objects
* @private
* @return {void}
*/
function _initializeItems() {
_items = [];
_itemsById = {};
_itemsByAppKey = {};
_isBuilt = false;
}
/**
* Add item(s) to the config
@@ -49,34 +62,36 @@ function ClayConfig(settings, config, $rootContainer, meta) {
item.forEach(function(item) {
_addItems(item, $container);
});
} else if (item.type === 'section') {
var $wrapper = HTML('<div class="section">');
$container.add($wrapper);
_addItems(item.items, $wrapper);
} else {
var _item = _.copyObj(item);
_item.clayId = _items.length;
} else if (utils.includesCapability(meta.activeWatchInfo, item.capabilities)) {
if (item.type === 'section') {
var $wrapper = HTML('<div class="section">');
$container.add($wrapper);
_addItems(item.items, $wrapper);
} else {
var _item = _.copyObj(item);
_item.clayId = _items.length;
var clayItem = new ClayItem(_item).initialize(self);
var clayItem = new ClayItem(_item).initialize(self);
if (_item.id) {
_itemsById[_item.id] = clayItem;
if (_item.id) {
_itemsById[_item.id] = clayItem;
}
if (_item.appKey) {
_itemsByAppKey[_item.appKey] = clayItem;
}
_items.push(clayItem);
// set the value of the item via the manipulator to ensure consistency
var value = typeof _settings[_item.appKey] !== 'undefined' ?
_settings[_item.appKey] :
(_item.defaultValue || '');
clayItem.set(value);
$container.add(clayItem.$element);
}
if (_item.appKey) {
_itemsByAppKey[_item.appKey] = clayItem;
}
_items.push(clayItem);
// set the value of the item via the manipulator to ensure consistency
var value = typeof _settings[_item.appKey] !== 'undefined' ?
_settings[_item.appKey] :
(_item.defaultValue || '');
clayItem.set(value);
$container.add(clayItem.$element);
}
}
@@ -97,6 +112,7 @@ function ClayConfig(settings, config, $rootContainer, meta) {
}
self.meta = meta;
self.$rootContainer = $rootContainer;
self.EVENTS = {
/**
@@ -111,7 +127,21 @@ function ClayConfig(settings, config, $rootContainer, meta) {
* value set
* @const
*/
AFTER_BUILD: 'AFTER_BUILD'
AFTER_BUILD: 'AFTER_BUILD',
/**
* Called if .build() is executed after the page has already been built and
* before the existing content is destroyed
* @const
*/
BEFORE_DESTROY: 'BEFORE_DESTROY',
/**
* Called if .build() is executed after the page has already been built and after
* the existing content is destroyed
* @const
*/
AFTER_DESTROY: 'AFTER_DESTROY'
};
utils.updateProperties(self.EVENTS, {writable: false});
@@ -157,6 +187,9 @@ function ClayConfig(settings, config, $rootContainer, meta) {
*/
self.serialize = function() {
_checkBuilt('serialize');
_settings = {};
_.eachObj(_itemsByAppKey, function(appKey, item) {
_settings[appKey] = {
value: item.get()
@@ -172,18 +205,40 @@ function ClayConfig(settings, config, $rootContainer, meta) {
// @todo maybe don't do this and force the static method
self.registerComponent = ClayConfig.registerComponent;
/**
* Empties the root container
* @returns {ClayConfig}
*/
self.destroy = function() {
var el = $rootContainer[0];
self.trigger(self.EVENTS.BEFORE_DESTROY);
while (el.firstChild) {
el.removeChild(el.firstChild);
}
_initializeItems();
self.trigger(self.EVENTS.AFTER_DESTROY);
return self;
};
/**
* Build the config page. This must be run before any of the get methods can be run
* If you call this method after the page has already been built, teh page will be
* destroyed and built again.
* @returns {ClayConfig}
*/
self.build = function() {
if (_isBuilt) {
self.destroy();
}
self.trigger(self.EVENTS.BEFORE_BUILD);
_addItems(config, $rootContainer);
_addItems(self.config, $rootContainer);
_isBuilt = true;
self.trigger(self.EVENTS.AFTER_BUILD);
return self;
};
_initializeItems();
// attach event methods
ClayEvents.call(self, $rootContainer);
+109
View File
@@ -17,3 +17,112 @@ module.exports.updateProperties = function(obj, descriptor) {
Object.defineProperty(obj, prop, descriptor);
});
};
module.exports.capabilityMap = {
APLITE: {
platforms: ['aplite'],
minFwMajor: 0,
minFwMinor: 0
},
BASALT: {
platforms: ['basalt'],
minFwMajor: 0,
minFwMinor: 0
},
CHALK: {
platforms: ['chalk'],
minFwMajor: 0,
minFwMinor: 0
},
DIORITE: {
platforms: ['diorite'],
minFwMajor: 0,
minFwMinor: 0
},
EMERY: {
platforms: ['emery'],
minFwMajor: 0,
minFwMinor: 0
},
BW: {
platforms: ['aplite', 'diorite'],
minFwMajor: 0,
minFwMinor: 0
},
COLOR: {
platforms: ['basalt', 'chalk', 'emery'],
minFwMajor: 0,
minFwMinor: 0
},
MICROPHONE: {
platforms: ['basalt', 'chalk', 'diorite', 'emery'],
minFwMajor: 0,
minFwMinor: 0
},
SMARTSTRAP: {
platforms: ['basalt', 'chalk', 'diorite', 'emery'],
minFwMajor: 3,
minFwMinor: 4
},
SMARTSTRAP_POWER: {
platforms: ['basalt', 'chalk', 'emery'],
minFwMajor: 3,
minFwMinor: 4
},
HEALTH: {
platforms: ['basalt', 'chalk', 'diorite', 'emery'],
minFwMajor: 3,
minFwMinor: 10
},
RECT: {
platforms: ['aplite', 'basalt', 'diorite', 'emery'],
minFwMajor: 0,
minFwMinor: 0
},
ROUND: {
platforms: ['chalk'],
minFwMajor: 3,
minFwMinor: 4
},
DISPLAY_144x168: {
platforms: ['aplite', 'basalt', 'diorite'],
minFwMajor: 0,
minFwMinor: 0
},
DISPLAY_180x180_ROUND: {
platforms: ['chalk'],
minFwMajor: 0,
minFwMinor: 0
},
DISPLAY_200x228: {
platforms: ['emery'],
minFwMajor: 0,
minFwMinor: 0
}
};
/**
* Checks if all of the provided capabilities are compatible with the watch
* @param {Object} activeWatchInfo
* @param {Array} [capabilities]
* @return {boolean}
*/
module.exports.includesCapability = function(activeWatchInfo, capabilities) {
if (!capabilities || !capabilities.length) {
return true;
}
for (var i = capabilities.length - 1; i >= 0; i--) {
var capability = capabilities[i];
var mapping = module.exports.capabilityMap[capability];
if (!mapping ||
mapping.platforms.indexOf(activeWatchInfo.platform) === -1 ||
mapping.minFwMajor > activeWatchInfo.firmware.major ||
mapping.minFwMajor === activeWatchInfo.firmware.major &&
mapping.minFwMinor > activeWatchInfo.firmware.minor
) {
return false;
}
}
return true;
};
+2
View File
@@ -31,6 +31,8 @@
vertical-align: baseline;
color: $color-white;
font-size: inherit;
appearance: none;
min-height: $item-spacing-v + ($base-height + 0rem) ;
@include placeholder {
color: $color-gray-8;
-1
View File
@@ -35,7 +35,6 @@
right: 0;
top: 0;
bottom: 0;
background: #000;
width: 100%;
border: none;
margin:0;
+9 -1
View File
@@ -4,7 +4,15 @@
<span class="value"></span>
<select data-manipulator-target {{each key: attributes}}{{key}}="{{this}}"{{/each}}>
{{each options}}
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
{{if Array.isArray(this.value)}}
<optgroup label="{{this.label}}">
{{each this.value}}
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
{{/each}}
</optgroup>
{{else}}
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
{{/if}}
{{/each}}
</select>
</label>
+22
View File
@@ -21,4 +21,26 @@ describe('component - select', function() {
selectItem.set('value-2');
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 2');
});
it('sets the value display to the correct value on change when using optgroups',
function() {
var clayConfig = fixture.clayConfig([
{
type: 'select',
defaultValue: 'value-1',
options: [
{ label: 'label 1', value: 'value-1' },
{ label: 'group', value: [
{ label: 'label 2', value: 'value-2' },
{ label: 'label 3', value: 'value-3' }
]}
]
}
]);
var selectItem = clayConfig.getItemsByType('select')[0];
var $valueDisplay = selectItem.$element.select('.value');
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 1');
selectItem.set('value-2');
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 2');
});
});
+27
View File
@@ -65,4 +65,31 @@ describe('component - slider', function() {
assert.strictEqual(sliderItem.precision, 2);
});
it('always displays decimal when precision is less than 1', function() {
var clayConfig = fixture.clayConfig([{
type: 'slider',
step: 0.05
}]);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $valueDisplayPad = sliderItem.$element.select('.value-pad');
var $slider = sliderItem.$element.select('.slider');
assert.strictEqual($valueDisplay.get('value'), '50.00');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50.00');
assert.strictEqual($slider.get('value'), '50');
sliderItem.set(75);
assert.strictEqual($valueDisplay.get('value'), '75.00');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75.00');
assert.strictEqual($slider.get('value'), '75');
sliderItem.set(33.25);
assert.strictEqual($valueDisplay.get('value'), '33.25');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '33.25');
assert.strictEqual($slider.get('value'), '33.25');
sliderItem.set(17.1);
assert.strictEqual($valueDisplay.get('value'), '17.10');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '17.10');
assert.strictEqual($slider.get('value'), '17.1');
});
});
+253
View File
@@ -27,6 +27,177 @@ describe('ClayConfig', function() {
checkReadOnly(clayConfig, properties);
});
/**
* Tests if the provided capabilities of the platform match
* @param {string} platform
* @param {number} fwMajor
* @param {number} fwMinor
* @param {Array} capabilities
* @param {number} expected
* @return {void}
*/
function testCapabilities(platform, fwMajor, fwMinor, capabilities, expected) {
it(platform + ' is' + (expected ? '' : ' not') + ' included on firmware ' +
fwMajor + '.' + fwMinor + ' for capabilities ' + JSON.stringify(capabilities),
function() {
var item = {
type: 'input',
capabilities: capabilities
};
var meta = {
activeWatchInfo: {
platform: platform,
model: 'qemu_platform_' + platform,
language: 'en_US',
firmware: {
major: fwMajor,
minor: fwMinor,
patch: 0,
suffix: ''
}
}
};
var clayConfig = fixtures.clayConfig([item], true, true, null, meta);
assert.strictEqual(clayConfig.getAllItems().length, expected);
});
}
describe('capability filtering', function() {
testCapabilities('aplite', 2, 9, ['BW'], 1);
testCapabilities('aplite', 2, 9, ['COLOR'], 0);
testCapabilities('aplite', 2, 9, ['MICROPHONE'], 0);
testCapabilities('aplite', 2, 9, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 2, 9, ['SMARTSTRAP_POWER'], 0);
testCapabilities('aplite', 2, 9, ['HEALTH'], 0);
testCapabilities('aplite', 2, 9, ['RECT'], 1);
testCapabilities('aplite', 2, 9, ['ROUND'], 0);
testCapabilities('aplite', 2, 9, ['DISPLAY_144x168'], 1);
testCapabilities('aplite', 2, 9, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('aplite', 2, 9, ['DISPLAY_200x228'], 0);
testCapabilities('aplite', 2, 9, ['APLITE'], 1);
testCapabilities('aplite', 2, 9, ['BASALT'], 0);
testCapabilities('aplite', 2, 9, ['CHALK'], 0);
testCapabilities('aplite', 2, 9, ['DIORITE'], 0);
testCapabilities('aplite', 2, 9, ['EMERY'], 0);
testCapabilities('aplite', 3, 10, ['BW'], 1);
testCapabilities('aplite', 3, 10, ['COLOR'], 0);
testCapabilities('aplite', 3, 10, ['MICROPHONE'], 0);
testCapabilities('aplite', 3, 10, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 3, 4, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('aplite', 3, 10, ['SMARTSTRAP_POWER'], 0);
testCapabilities('aplite', 3, 10, ['HEALTH'], 0);
testCapabilities('aplite', 3, 9, ['HEALTH'], 0);
testCapabilities('aplite', 3, 10, ['RECT'], 1);
testCapabilities('aplite', 3, 10, ['ROUND'], 0);
testCapabilities('aplite', 3, 10, ['DISPLAY_144x168'], 1);
testCapabilities('aplite', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('aplite', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('aplite', 3, 10, ['APLITE'], 1);
testCapabilities('aplite', 3, 10, ['BASALT'], 0);
testCapabilities('aplite', 3, 10, ['CHALK'], 0);
testCapabilities('aplite', 3, 10, ['DIORITE'], 0);
testCapabilities('aplite', 3, 10, ['EMERY'], 0);
testCapabilities('basalt', 3, 10, ['BW'], 0);
testCapabilities('basalt', 3, 10, ['COLOR'], 1);
testCapabilities('basalt', 3, 10, ['MICROPHONE'], 1);
testCapabilities('basalt', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('basalt', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('basalt', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('basalt', 3, 10, ['SMARTSTRAP_POWER'], 1);
testCapabilities('basalt', 3, 4, ['SMARTSTRAP_POWER'], 1);
testCapabilities('basalt', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('basalt', 3, 10, ['HEALTH'], 1);
testCapabilities('basalt', 3, 9, ['HEALTH'], 0);
testCapabilities('basalt', 3, 10, ['RECT'], 1);
testCapabilities('basalt', 3, 10, ['ROUND'], 0);
testCapabilities('basalt', 3, 10, ['DISPLAY_144x168'], 1);
testCapabilities('basalt', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('basalt', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('basalt', 3, 10, ['APLITE'], 0);
testCapabilities('basalt', 3, 10, ['BASALT'], 1);
testCapabilities('basalt', 3, 10, ['CHALK'], 0);
testCapabilities('basalt', 3, 10, ['DIORITE'], 0);
testCapabilities('basalt', 3, 10, ['EMERY'], 0);
testCapabilities('chalk', 3, 10, ['BW'], 0);
testCapabilities('chalk', 3, 10, ['COLOR'], 1);
testCapabilities('chalk', 3, 10, ['MICROPHONE'], 1);
testCapabilities('chalk', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('chalk', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('chalk', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('chalk', 3, 10, ['SMARTSTRAP_POWER'], 1);
testCapabilities('chalk', 3, 4, ['SMARTSTRAP_POWER'], 1);
testCapabilities('chalk', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('chalk', 3, 10, ['HEALTH'], 1);
testCapabilities('chalk', 3, 9, ['HEALTH'], 0);
testCapabilities('chalk', 3, 10, ['RECT'], 0);
testCapabilities('chalk', 3, 10, ['ROUND'], 1);
testCapabilities('chalk', 3, 10, ['DISPLAY_144x168'], 0);
testCapabilities('chalk', 3, 10, ['DISPLAY_180x180_ROUND'], 1);
testCapabilities('chalk', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('chalk', 3, 10, ['APLITE'], 0);
testCapabilities('chalk', 3, 10, ['BASALT'], 0);
testCapabilities('chalk', 3, 10, ['CHALK'], 1);
testCapabilities('chalk', 3, 10, ['DIORITE'], 0);
testCapabilities('chalk', 3, 10, ['EMERY'], 0);
testCapabilities('diorite', 3, 10, ['BW'], 1);
testCapabilities('diorite', 3, 10, ['COLOR'], 0);
testCapabilities('diorite', 3, 10, ['MICROPHONE'], 1);
testCapabilities('diorite', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('diorite', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('diorite', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('diorite', 3, 10, ['SMARTSTRAP_POWER'], 0);
testCapabilities('diorite', 3, 4, ['SMARTSTRAP_POWER'], 0);
testCapabilities('diorite', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('diorite', 3, 10, ['HEALTH'], 1);
testCapabilities('diorite', 3, 9, ['HEALTH'], 0);
testCapabilities('diorite', 3, 10, ['RECT'], 1);
testCapabilities('diorite', 3, 10, ['ROUND'], 0);
testCapabilities('diorite', 3, 10, ['DISPLAY_144x168'], 1);
testCapabilities('diorite', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('diorite', 3, 10, ['DISPLAY_200x228'], 0);
testCapabilities('diorite', 3, 10, ['APLITE'], 0);
testCapabilities('diorite', 3, 10, ['BASALT'], 0);
testCapabilities('diorite', 3, 10, ['CHALK'], 0);
testCapabilities('diorite', 3, 10, ['DIORITE'], 1);
testCapabilities('diorite', 3, 10, ['EMERY'], 0);
testCapabilities('emery', 3, 10, ['BW'], 0);
testCapabilities('emery', 3, 10, ['COLOR'], 1);
testCapabilities('emery', 3, 10, ['MICROPHONE'], 1);
testCapabilities('emery', 3, 10, ['SMARTSTRAP'], 1);
testCapabilities('emery', 3, 4, ['SMARTSTRAP'], 1);
testCapabilities('emery', 3, 3, ['SMARTSTRAP'], 0);
testCapabilities('emery', 3, 10, ['SMARTSTRAP_POWER'], 1);
testCapabilities('emery', 3, 4, ['SMARTSTRAP_POWER'], 1);
testCapabilities('emery', 3, 3, ['SMARTSTRAP_POWER'], 0);
testCapabilities('emery', 3, 10, ['HEALTH'], 1);
testCapabilities('emery', 3, 9, ['HEALTH'], 0);
testCapabilities('emery', 3, 10, ['RECT'], 1);
testCapabilities('emery', 3, 10, ['ROUND'], 0);
testCapabilities('emery', 3, 10, ['DISPLAY_144x168'], 0);
testCapabilities('emery', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
testCapabilities('emery', 3, 10, ['DISPLAY_200x228'], 1);
testCapabilities('emery', 3, 10, ['APLITE'], 0);
testCapabilities('emery', 3, 10, ['BASALT'], 0);
testCapabilities('emery', 3, 10, ['CHALK'], 0);
testCapabilities('emery', 3, 10, ['DIORITE'], 0);
testCapabilities('emery', 3, 10, ['EMERY'], 1);
testCapabilities('aplite', 3, 10, ['BW', 'HEALTH'], 0);
testCapabilities('emery', 3, 10, ['COLOR', 'HEALTH'], 1);
testCapabilities('emery', 3, 10, ['MICROPHONE'], 1);
testCapabilities('chalk', 3, 10, ['APLITE', 'BASALT', 'DIORITE'], 0);
testCapabilities('basalt', 3, 10, ['SMARTSTRAP', 'SMARTSTRAP_POWER'], 1);
testCapabilities('aplite', 3, 10, ['COLOR', 'APLITE'], 0);
testCapabilities('aplite', 3, 10, ['APLITE', 'COLOR'], 0);
});
describe('.meta', function() {
it('populates meta', function() {
var clayConfig = fixtures.clayConfig(['input']);
@@ -159,6 +330,26 @@ describe('ClayConfig', function() {
fixtures.clayConfig(config, true, true, loadedSettings);
});
});
it('only returns the settings present in the config', function() {
var config = [
{type: 'input', appKey: 'test1', defaultValue: 'default val'},
{type: 'select', appKey: 'test2', options: [
{label: 'label-1', value: 'val-1'},
{label: 'label-2', value: 'val-2'}
]}
];
var settings = {
test2: 'val-2',
notInTheConfig: 'Should not exist'
};
var clayConfig = fixtures.clayConfig(config, true, true, settings);
assert.deepEqual(clayConfig.serialize(), {
test1: {value: 'default val'},
test2: {value: 'val-2'}
});
});
});
describe('.registerComponent()', function() {
@@ -254,7 +445,69 @@ describe('ClayConfig', function() {
});
});
describe('.destroy().', function() {
it('Destroys all the items on the page and in the items array', function() {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.build();
clayConfig.destroy();
// this should throw because the config has not been built yet
assert.throws(clayConfig.getAllItems);
// there should be no DOM inside the container
assert.strictEqual(clayConfig.$rootContainer[0].children.length, 0);
});
it('dispatches the BEFORE_DESTROY event at the right time', function(done) {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.on(clayConfig.EVENTS.BEFORE_DESTROY, function() {
assert.strictEqual(clayConfig.getAllItems().length, 3);
done();
});
clayConfig.build();
clayConfig.destroy();
assert.strictEqual(clayConfig.getAllItems().length, 0);
});
it('dispatches the AFTER_DESTROY event at the right time', function(done) {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.on(clayConfig.EVENTS.AFTER_DESTROY, function() {
// this should throw because the config has not been built yet
assert.throws(clayConfig.getAllItems);
done();
});
clayConfig.build();
assert.strictEqual(clayConfig.getAllItems().length, 3);
clayConfig.destroy();
});
});
describe('.build()', function() {
it('Destroys the config if called a consecutive time', function() {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
var destroyHandlerSpy = sinon.spy();
clayConfig.on(clayConfig.EVENTS.AFTER_DESTROY, destroyHandlerSpy);
clayConfig.build();
assert.strictEqual(clayConfig.getAllItems().length, 3);
clayConfig.config = fixtures.config(['select']);
clayConfig.build();
assert.strictEqual(clayConfig.getAllItems().length, 1);
assert(destroyHandlerSpy.calledOnce);
clayConfig.build();
assert(destroyHandlerSpy.calledTwice);
});
it('dispatches the BEFORE_BUILD event at the right time', function(done) {
var clayConfig = fixtures.clayConfig(['input', 'text', 'input'], false);
clayConfig.on(clayConfig.EVENTS.BEFORE_BUILD, function() {