Compare commits

..
195 Commits
Author SHA1 Message Date
keegan-lillo b3963179d1 Merge pull request #105 from pebble/release/v1.0.0
Release 1.0.0
2016-06-16 11:32:22 -07:00
keegan-lillo 6a63c40e08 Release 1.0.0 2016-06-16 11:28:34 -07:00
keegan-lillo f91c525a30 Merge pull request #104 from pebble/#100/migration-guide
Add migration guide
2016-06-16 11:27:46 -07:00
Keegan 19779a627f add li9nk for migration guide 2016-06-16 11:22:31 -07:00
Keegan b19ad435df Add migration guide 2016-06-16 11:21:00 -07:00
keegan-lillo 5627f789a3 Merge pull request #103 from pebble/#90/support-message-keys
#90/support message keys
2016-06-16 00:55:54 -07:00
Keegan 1c9a812009 add build folder to .eslintignore 2016-06-16 00:48:29 -07:00
Keegan 939398774d remove another impossible case 2016-06-16 00:45:52 -07:00
Keegan 361dc99ea5 remove undefined check from Clay.prepareSettingsForAppMessage because it is impossible 2016-06-16 00:40:10 -07:00
keegan-lillo 9f2a89a41e Merge branch 'master' into #90/support-message-keys 2016-06-16 00:35:27 -07:00
Keegan 26df41b25e Document messageKey array syntax 2016-06-16 00:34:42 -07:00
Keegan dd6fe54834 allow array syntax inside of the messageKey property of a config item 2016-06-15 23:58:41 -07:00
Keegan 3cffa3a275 Send messageKeys down to appMessage in the new format 2016-06-15 21:50:36 -07:00
Keegan a0335be710 Checkbox groups now return an array of booleans 2016-06-15 14:20:35 -07:00
Keegan cabee3ae0b Yell at developer if they try and use appKeys 2016-06-15 11:22:16 -07:00
Keegan 940c5e2451 Rename all appKey references to messageKey 2016-06-15 10:58:45 -07:00
keegan-lillo 870436ddd8 Merge pull request #102 from pebble/#96/fix-webviewclosed-data-being-double-decoded
Allow response in webviewclosed event to optionally be URI encoded
2016-06-14 23:02:06 -07:00
Keegan c3c8de77fc Allow response in webviewclosed event to optionally be URI encoded 2016-06-14 22:57:36 -07:00
keegan-lillo 87807f180c Merge pull request #101 from pebble/#98/expose-clay-version
Expose Clay version
2016-06-14 22:04:15 -07:00
Keegan 9e344d527b Expose Clay version 2016-06-14 21:59:10 -07:00
keegan-lillo 2f9e36bdb2 Merge pull request #99 from pebble/#97/pebble-package
Make Clay a Pebble package
2016-06-14 21:49:38 -07:00
Keegan 5038e80bf2 remove "main" property of package.json 2016-06-14 21:30:40 -07:00
Keegan 7a2dbb276f use messageKey instead of appKey and clarify 3.13 gotchas 2016-06-14 21:30:17 -07:00
Keegan 3300e98ce2 add src/js/index.js to .eslintignore 2016-06-14 20:55:13 -07:00
Keegan 693b795c2b Add a few more files to what is included on NPM to make debugging easier 2016-06-14 18:31:11 -07:00
Keegan ef8ab37eb4 Update the readme to include instructions for sdk 3.13 2016-06-14 18:24:43 -07:00
Keegan 118df1d952 Make Clay a Pebble package 2016-06-14 17:02:01 -07:00
Cherie Williams 8d4ff52b0f Merge pull request #91 from pebble/update-readme-for-3.13
Update README for loader.js changes in SDK 3.13
2016-06-09 16:51:57 -07:00
keegan-lillo 613c41fd6e Merge branch 'master' into update-readme-for-3.13 2016-06-09 14:38:06 -07:00
Cherie Williams ecac278f63 Make SDK 3.13 notice a header & add error sample 2016-06-09 14:21:10 -07:00
keegan-lillo 742890ee31 Merge pull request #94 from pebble/#66/update-readme
Add JS settings to getting started
2016-06-09 09:01:27 -07:00
keegan-lillo f08f11fb6f Merge branch 'master' into #66/update-readme 2016-06-08 18:51:20 -07:00
keegan-lillo 859e2f2767 Merge pull request #93 from pebble/#69/remove-attributes-references-in-docs
Remove reference to "attributes" from docs
2016-06-08 18:51:08 -07:00
keegan-lillo 19ad09eb66 Update README.md 2016-06-08 18:49:55 -07:00
keegan-lillo 39056a2868 Remove reference to "attributes" from docs 2016-06-08 18:34:11 -07:00
Cherie Williams 8a2d560482 Update README for loader.js changes in SDK 3.13 2016-06-08 17:56:56 -07:00
keegan-lillo f62f960433 Merge pull request #89 from pebble/release/v0.1.7
Bump version to v1.0.7
2016-05-31 10:20:13 -07:00
keegan-lillo 8fb67ce08d Bump version to v1.0.7 2016-05-31 10:11:55 -07:00
keegan-lillo 65b952d7f8 Merge pull request #88 from pebble/#87/allow-not-prefix-for-capabilities
Allow `NOT_` prefix for capabilities
2016-05-31 10:06:22 -07:00
Keegan 6c2a335b7c Allow NOT_ prefix for capabilities 2016-05-31 09:57:10 -07:00
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
keegan-lillo b553094742 Merge pull request #64 from pebble/release/v0.1.5
Bump Version to v0.1.5
2016-03-21 11:28:57 -07:00
keegan-lillo f31ffed558 Bump Version to v0.1.5 2016-03-21 09:26:59 -07:00
keegan-lillo 3f71f97b8b Merge pull request #62 from pebble/#61/fix-color-picker-default
add default background color to value display in color picker
2016-03-21 09:25:52 -07:00
Keegan 1a6308f621 add default background color to value display in color picker 2016-03-20 11:16:03 -07:00
keegan-lillo 7045c59a72 Merge pull request #60 from pebble/release/v0.1.4
Bump version to v0.1.4
2016-03-16 12:09:29 +11:00
keegan-lillo 4ffb1018f7 Bump version to v0.1.4 2016-03-16 12:04:19 +11:00
keegan-lillo e78c09f5be Merge pull request #54 from pebble/#52/custom-color-picker-layout
#52/custom color picker layout
2016-03-16 12:03:13 +11:00
keegan-lillo d8e73f2674 Merge branch 'master' into #52/custom-color-picker-layout 2016-03-16 11:54:18 +11:00
keegan-lillo 0358e820e6 Merge pull request #59 from pebble/document-pebblejs
Document Pebble.js usage
2016-03-16 11:54:04 +11:00
keegan-lillo 1d821f61c3 Change Settings.data to Settings.option 2016-03-16 11:44:50 +11:00
keegan-lillo e2ffa42595 Document Pebble.js usage 2016-03-16 11:42:39 +11:00
keegan-lillo 3a2dcb187e Merge branch 'master' into #52/custom-color-picker-layout 2016-03-16 09:12:39 +11:00
keegan-lillo 08e3a21429 Merge pull request #56 from pebble/#55/unicode-support
Allow unicode characters in config
2016-03-16 09:12:30 +11:00
keegan-lillo 42a613caf4 Merge branch 'master' into #52/custom-color-picker-layout 2016-03-16 08:25:59 +11:00
keegan-lillo 4ff38eb0ba Merge branch 'master' into #55/unicode-support 2016-03-16 08:25:44 +11:00
keegan-lillo b466c48487 Merge pull request #48 from pebble/#47/range-slider-component
#47/range slider component
2016-03-16 08:25:06 +11:00
keegan-lillo 517d4f84ff Add quotes to slider description 2016-03-16 08:12:13 +11:00
keegan-lillo e04a6c9f87 Merge branch 'master' into #47/range-slider-component 2016-03-15 13:11:32 +11:00
keegan-lillo 1f0867985d Merge branch 'master' into #52/custom-color-picker-layout 2016-03-15 13:11:24 +11:00
keegan-lillo d0e5988024 Merge branch 'master' into #55/unicode-support 2016-03-15 13:11:16 +11:00
keegan-lillo d007d48afc Merge pull request #57 from pebble/document-clayconfig-param-in-component-initialize
Document the clayConfig parameter of custom component initializer
2016-03-15 13:11:09 +11:00
keegan-lillo 07a847457f Document the clayConfig parameter of custom component initializer 2016-03-15 13:07:19 +11:00
Keegan 9347c9f3d2 Allow unicode characters in config 2016-03-15 12:06:04 +11:00
Keegan 4ee5a7361c When setting the value for the color picker, round it to the closest available value in the layout 2016-03-15 04:53:58 +11:00
Keegan dad8b2a025 add allowGray option to color picker 2016-03-14 03:09:23 +11:00
Keegan d52f6d4aa2 Globally increase mocha timeout because Travis struggles sometimes 2016-03-14 01:55:22 +11:00
keegan-lillo a94eae9a16 Clarify acceptable default values for the color picker. 2016-03-14 01:30:41 +11:00
keegan-lillo 317f6fd4e8 Grammar fixes in color picker docs 2016-03-14 01:23:46 +11:00
keegan-lillo b19b07c47d Fix syntax error in Color Picker examples 2016-03-14 01:20:07 +11:00
Keegan 0a2ddab597 Document new color picker functionality 2016-03-14 01:02:40 +11:00
Keegan 005ac939b1 Refine color picker styles 2016-03-14 01:01:58 +11:00
Keegan 12b0ae9bd6 Allow custom layouts in the color picker and automatically choose a layout based on platform 2016-03-14 01:01:13 +11:00
Keegan 6f92c0d267 Merge remote-tracking branch 'origin/master' into #47/range-slider-component
# Conflicts:
#	index.js
2016-03-12 10:27:27 +11:00
keegan-lillo 756cdc0d9e Merge pull request #50 from pebble/#20/allow-user-data
#20/allow user data
2016-03-12 10:25:04 +11:00
Keegan 783bc84779 fix typo in Clay JSdoc 2016-03-12 10:22:01 +11:00
Keegan e621bbf0bd Merge remote-tracking branch 'origin/master' into #20/allow-user-data 2016-03-12 10:16:47 +11:00
keegan-lillo 43c5c3cc9f Merge pull request #51 from pebble/#39/fix-event-targets
Fix events firing on wrong element for button component
2016-03-12 10:15:37 +11:00
Keegan d8738e06b2 fix tests for slider 2016-03-11 11:12:00 +11:00
Keegan 67449ec34e Use number for value display in slider because of vendor implementation inconsistencies 2016-03-11 11:04:52 +11:00
keegan-lillo 5c63b00eda Make slider update value display as user is adjusting it 2016-03-11 10:49:23 +11:00
Keegan 79eef99731 Fix events firing on wrong element for button component 2016-03-11 03:57:30 +11:00
Keegan e5496c62d6 Add developers to send arbitrary data to the config page 2016-03-11 03:36:00 +11:00
Keegan b1a6f184ba Stop exposing config as a reference. resolves #35 2016-03-11 02:25:59 +11:00
Keegan 878fea0340 Allow floats in the slider component and update docs 2016-03-11 01:07:17 +11:00
Keegan 1142d1adee Renamed ClayConfig.getSettings() to ClayConfig.serialize() 2016-03-10 21:00:44 +11:00
Keegan 0873f5eba2 Added Slider component 2016-03-10 04:20:50 +11:00
Keegan 0faa585d5f Clean up manipulators tests 2016-03-10 00:03:50 +11:00
keegan-lillo 3b7c4cda7c Merge pull request #42 from pebble/fix-readme-getsetting-link
Fix link in readme
2016-03-09 15:41:01 +11:00
keegan-lillo 3839a47f5c Merge branch 'master' into fix-readme-getsetting-link 2016-03-09 15:37:16 +11:00
keegan-lillo 85503d2846 Merge pull request #46 from clach04/readme_json_v_js
Clarify config.json versus config.js differences
2016-03-09 15:35:30 +11:00
clach04 d6ae6faef4 Merge branch 'master' into readme_json_v_js 2016-03-08 18:06:23 -08:00
clach04 2c2f003e15 Clarify config.json versus config.js differences
Make example config clear, main impact is for CloudPebble usage where json is not yet an option.
2016-03-08 18:02:49 -08:00
keegan-lillo 2bd9623ab1 Merge pull request #45 from pebble/release/v0.1.3
Bump version to v0.1.3
2016-03-09 12:46:28 +11:00
keegan-lillo 9f3c78de17 Bump version to v0.1.3 2016-03-09 12:38:10 +11:00
keegan-lillo 1af5828b18 Update README.md 2016-03-09 12:32:51 +11:00
keegan-lillo 99dc081b69 Merge pull request #41 from pebble/#40/move-settings-conversion
`Clay.getSettings()` now does type conversion, not `ClayConfg.getSettings()`
2016-03-09 12:30:31 +11:00
Keegan 37c64a6e05 minified does not work in the emulator or iOS so move settings iteration to utils.prepareSettingsForAppMessage() 2016-03-09 12:24:29 +11:00
Keegan 34d83de59e Add deamdify transform to 'clay' task in gulpfile 2016-03-09 11:52:27 +11:00
Keegan e80a1630f1 Don't assume string in checkboxgroup set manipulator 2016-03-09 11:51:49 +11:00
Keegan d0af7b0fac fix typo in readme 2016-03-09 11:28:42 +11:00
Keegan 1af4dfd8ef Change the behavior so that type conversions only run inside of Clay.getSettings no ClayConfig.getSettings 2016-03-09 11:25:13 +11:00
keegan-lillo 42fc8f1da4 Merge pull request #38 from pebble/fix-getsettings-in-readme
Moved the conversion information from `Clay.getSettings` to `ClayConfg.getSettings`
2016-03-08 10:56:48 +11:00
keegan-lillo d469d404ab Moved the conversion information from Clay.getSettings to ClayConfig.getSettings 2016-03-08 10:47:55 +11:00
keegan-lillo ad807020ca Merge pull request #36 from pebble/release/v0.1.2
Bump version to v0.1.2
2016-03-08 08:56:20 +11:00
keegan-lillo bd3e4a55c8 Bump version to v0.1.2 2016-03-08 08:52:06 +11:00
keegan-lillo f50f569c98 Merge pull request #21 from pebble/#13/fix-checkboxgroups-return-value
Separate the results of the checkboxgroup manipulator with a zero.
2016-03-08 08:47:43 +11:00
Keegan be17456f8b Merge remote-tracking branch 'origin/master' into #13/fix-checkboxgroups-return-value
# Conflicts:
#	test/spec/lib/manipulators.js
2016-03-08 08:41:24 +11:00
keegan-lillo 49f92de102 Merge pull request #32 from pebble/#31/generic-btn-component
Add generic button component
2016-03-08 08:38:28 +11:00
Keegan c4f6b17a58 Update button to be compatible with new events behavior. 2016-03-08 08:31:30 +11:00
Keegan 061bb34c91 Merge remote-tracking branch 'origin/master' into #31/generic-btn-component 2016-03-08 08:28:29 +11:00
keegan-lillo 20e7460c2f Merge pull request #33 from pebble/#26/fix-aggressive-events
#26/fix aggressive events
2016-03-08 08:27:55 +11:00
keegan-lillo 6e5c2f5847 Merge branch 'master' into #26/fix-aggressive-events 2016-03-08 08:20:43 +11:00
keegan-lillo 71ea309346 Merge pull request #30 from pebble/config-based-on-platform-readme
Made documentation less ambiguous about classes
2016-03-08 08:20:25 +11:00
Keegan d63c6c832d Change behavior of the toggle to return a boolean again 2016-03-07 13:13:36 +11:00
Keegan de00ac1fd6 update readme to reflect new behavior of ClayConfig.getSettings() 2016-03-07 13:05:38 +11:00
Keegan 271a0d1cfd Change the ClayConfig.getSettings() method to convert data types to be compatible with sendAppMessage 2016-03-07 12:58:46 +11:00
Keegan d6659db3ac Merge remote-tracking branch 'origin/master' into #13/fix-checkboxgroups-return-value 2016-03-07 10:45:38 +11:00
Keegan 264866ce26 update readme to explain new events behavior. 2016-03-06 14:32:53 +11:00
Keegan 069d4a80da only dispatch manipulator events if the state is actually changed. 2016-03-06 14:28:38 +11:00
keegan-lillo c99ad44b5c remove copy-paste error 2016-03-06 13:17:39 +11:00
Keegan 75c846a8ba Fix links to checkboxgroup and radiogroup manipulators in readme 2016-03-06 12:39:03 +11:00
Keegan a1df49623d Add generic button component 2016-03-06 12:31:25 +11:00
keegan-lillo 08e41036da Clarify when Clay.meta is actually available. 2016-03-05 23:08:52 +11:00
keegan-lillo 663f91e9bd Made documentation less ambiguous about classes + added example for loading a different config based on platform 2016-03-05 23:03:39 +11:00
keegan-lillo bdcd820f7c Merge pull request #29 from pebble/release/v0.1.1
Bump version to 0.1.1
2016-03-03 13:25:14 +11:00
Keegan 51a44b18b0 Bump version to 0.1.1 2016-03-03 13:15:23 +11:00
keegan-lillo 64931dabc9 Merge pull request #28 from pebble/fix-dist
Fix release links in readme
2016-03-03 12:17:39 +11:00
keegan-lillo b7be587542 Merge branch 'master' into fix-dist 2016-03-03 11:53:35 +11:00
keegan-lillo 4508c73de2 Fix release links in readme 2016-03-03 11:53:11 +11:00
keegan-lillo f804cb0efd Merge pull request #27 from pebble/fix-dist
Get rid of dist/clay.js and use GitHub releases instead
2016-03-03 11:48:02 +11:00
keegan-lillo 525a78fc53 Update download instructions for CloudPebble 2016-03-03 11:32:31 +11:00
Keegan 05c62d466e Get rid of dist/clay.js and use GitHub releases instead 2016-03-03 11:25:19 +11:00
keegan-lillo dbf798daff Merge pull request #24 from pebble/#11/description-for-input-components
Add description field to all input type components.
2016-03-03 10:54:45 +11:00
Keegan b91b45bd23 Fix padding issue after merge 2016-03-03 10:49:05 +11:00
Keegan 2e447e5cd2 Merge remote-tracking branch 'origin/master' into #11/description-for-input-components
# Conflicts:
#	src/styles/clay/_base.scss
2016-03-03 10:45:20 +11:00
keegan-lillo 9782737256 Merge pull request #25 from pebble/#6/show-hide-items
Add hide/show method to manipulators.
2016-03-03 10:37:03 +11:00
Keegan 3a4d962a37 Update example image with description 2016-03-03 10:36:30 +11:00
Keegan 87d8b91153 Update dev/config.js with more useful examples 2016-03-03 10:27:38 +11:00
Keegan 7bf4117d87 Clean up styles 2016-03-03 10:26:23 +11:00
Keegan 35fc64de99 Document hide and show methods 2016-03-03 10:06:37 +11:00
Keegan de982960e2 fix tests after the input component changed HTML structure. 2016-03-03 01:03:23 +11:00
Keegan 797fa1d9ab make the tap-highlight not highlight the description 2016-03-03 00:49:55 +11:00
Keegan 4fb8db32da Fix section spacing when all items in a section are hidden. 2016-03-03 00:13:51 +11:00
Keegan aa8409912c fix separator visibility when items are hidden 2016-03-02 23:57:24 +11:00
Keegan 8e3cd119b8 use !important for .hide class 2016-03-02 22:11:19 +11:00
Keegan beb623f4fd fix separator visibility on disabled and hidden items 2016-03-02 14:02:18 +11:00
Keegan 226dd682e0 Merge remote-tracking branch 'origin/master' into #11/description-for-input-components
# Conflicts:
#	dist/clay.js
#	src/styles/clay/_base.scss
#	src/templates/components/select.tpl
#	src/templates/components/toggle.tpl
2016-03-02 10:58:17 +11:00
Keegan 011f40ea0d Merge remote-tracking branch 'origin/master' into #6/show-hide-items
# Conflicts:
#	dev/custom-fn.js
#	src/styles/clay/_base.scss
2016-03-02 10:54:20 +11:00
keegan-lillo a98b4e69c1 Merge pull request #23 from pebble/#22/css-issues
#22/css issues
2016-03-02 10:48:59 +11:00
Matthew Tole 0ad931e47c Merge branch 'master' into #22/css-issues 2016-02-29 15:11:19 -08:00
keegan-lillo f7717dc781 Merge pull request #19 from pebble/PBL-31966/include-user-and-watch-info
Pbl 31966/include user and watch info
2016-03-01 08:52:04 +11:00
Keegan 2f5f3cc4e8 Add description field to all input type components. 2016-02-29 23:58:03 +11:00
Keegan 3b9bf842cb Fix value not showing up for select in old Android versions 2016-02-29 23:18:25 +11:00
Keegan eb1d336f8f Center align submit button 2016-02-29 21:54:45 +11:00
Keegan d6461e1fb5 fix missing value display on old android 2016-02-29 15:54:12 +11:00
Keegan fa72cfb3a3 fix autoprefixer not working in dev mode 2016-02-29 15:46:15 +11:00
Keegan b50a7dc72d Fix checkboxgroup/radiogroup weirdness on old android 2016-02-29 15:45:59 +11:00
Keegan 7def2f741d try adding longer timeout to try and make travis stop timing out 2016-02-29 15:19:28 +11:00
Keegan ca498c6e3c fix tap-highlight and missing divider 2016-02-29 14:35:39 +11:00
Keegan 060704cb1a Add hide/show method to manipulators. 2016-02-29 14:24:59 +11:00
Keegan aa30ef7d7a Account for cases where the Pebble global is undefined 2016-02-27 08:51:54 +11:00
Keegan 1cd4590691 Merge remote-tracking branch 'origin/master' into PBL-31966/include-user-and-watch-info
# Conflicts:
#	dist/clay.js
#	test/spec/index.js
2016-02-27 08:35:04 +11:00
keegan-lillo 75aaa07048 Merge pull request #17 from pebble/#15/fix-travis-random-fails
#15/fix travis random fails
2016-02-27 08:31:20 +11:00
Keegan 5b2aba4312 fix table layout in readme 2016-02-26 12:37:48 +11:00
keegan-lillo 4584fe5dc5 Merge branch 'master' into #15/fix-travis-random-fails 2016-02-26 12:13:51 +11:00
keegan-lillo d50ff0a9a4 Merge pull request #14 from pebble/readme-intro-fix
Fix link in intro of readme
2016-02-26 09:47:52 +11:00
Keegan c908e681ad Sub clay.generateUrl() to avoid the very large string comparison 2016-02-25 14:40:40 +11:00
keegan-lillo 8fdd5dad07 Fix link in intro of readme 2016-02-23 00:03:11 -08:00
Keegan 2169d5334f expose meta containing watch and account info in ClayConfig 2016-02-19 19:31:17 -08:00
Keegan 94428b1826 extract watchinfo etc from Pebble object 2016-02-19 15:29:11 -08:00
66 changed files with 3686 additions and 753 deletions
+2
View File
@@ -1,4 +1,6 @@
coverage/
dist/
tmp/
build/
src/scripts/vendor/
src/js/index.js
+1 -1
View File
@@ -9,7 +9,7 @@
},
"rules": {
"new-cap": [0],
"max-params": [2, 4],
"max-params": [2, 5],
"require-jsdoc": [2, {
"require": {
"FunctionDeclaration": true,
+8 -21
View File
@@ -1,29 +1,16 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
/coverage
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
/.lock-waf_darwin_build
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
/node_modules
/npm-debug.log
tmp
/tmp/
/src/js/index.js
/dist.zip
/build/
Executable
+5
View File
@@ -0,0 +1,5 @@
# We ignore everying except for what is actually used by the pebble tool.
/**/*
!/dist.zip
!/src/**/*
!/index.js
+597 -124
View File
File diff suppressed because it is too large Load Diff
+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",
"messageKey": "email",
"defaultValue": "",
"label": "Input Field",
"description": "This is a new field",
"attributes": {
"placeholder": "Placeholder set with attributes"
}
}
]
},
{
"type": "submit",
"defaultValue": "Save"
}
];
+90 -41
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,37 +31,85 @@ module.exports = [
"type": "heading",
"defaultValue": "This is a section"
},
{
"type": "text",
"defaultValue": "This is only visible for aplite",
"capabilities": ["APLITE"]
},
{
"type": "input",
"appKey": "email",
"messageKey": "email",
"defaultValue": "",
"label": "Email",
"label": "Input Field",
"description": "This is a description for the input component. " +
"You can add <strong>html</strong> in here too.",
"attributes": {
"placeholder": "eg: name@domain.com",
"limit": 10,
"required": "required",
type: "email"
"placeholder": "Placeholder set with attributes"
}
},
{
"type": "input",
"label": "Time Field",
"attributes": {
"type": "time"
}
},
{
"type": "input",
"label": "Date Field",
"attributes": {
"type": "date"
}
},
{
"type": "slider",
"messageKey": "slider",
"defaultValue": 15,
"label": "Slider",
"description": "This is the description for the slider",
"min": 10,
"max": 20,
"step": 0.25
},
{
"type": "toggle",
"appKey": "cool_stuff",
"label": "Enable Cool Stuff",
"messageKey": "cool_stuff",
"label": "This is a Toggle",
"defaultValue": false
},
{
"type": "toggle",
"messageKey": "arrayToggle[0]",
"label": "This is a Toggle",
"defaultValue": false
},
{
"type": "toggle",
"messageKey": "arrayToggle[1]",
"label": "This is a Toggle",
"defaultValue": false
},
{
"type": "color",
"appKey": "colorTest",
"messageKey": "colorTest",
"defaultValue": "FF0000",
"label": "Background Color",
"label": "Standard Color Picker",
"sunlight": false
},
{
"type": "color",
"appKey": "sunnyColorTest",
"messageKey": "sunnyColorTest",
"defaultValue": "00FF00",
"label": "Sunny Color",
"label": "Sunny Color Picker",
"sunlight": true
},
{
type: 'button',
id: 'testButton',
primary: false,
defaultValue: 'Generic Button',
description: 'This is a generic button. ' +
'You can listen for standard events like "click"'
}
]
},
@@ -63,48 +122,38 @@ module.exports = [
},
{
"type": "radiogroup",
"appKey": "radiogroup-test",
"label": "Radio test",
"messageKey": "radiogroup-test",
"label": "Radio Group",
"options": [
{ "label": "Test thing one", "value": "one" },
{ "label": "Test thing", "value": "one" },
{ "label": "Another thing", "value": "two" },
{ "label": "One really long thing that would not fit", "value": "three" },
{ "label": "Small", "value": "quote' \"test" },
{ "label": "Final thing", "value": "three" }
{ "label": "Final thing with <em>html</em>", "value": "three" }
]
},
{
"type": "checkboxgroup",
"appKey": "checkboxgroup-test",
"defaultValue": ["quote' \"test", "two"],
"label": "Checkbox test",
"options": [
{ "label": "Test <strong>thing</strong> one", "value": "one" },
{ "label": "Another thing", "value": "two" },
{ "label": "One really long thing that would not fit", "value": "three" },
{ "label": "Small", "value": "quote' \"test" },
{ "label": "Final thing", "value": "three" }
]
},
{
"type": "input",
"appKey": "date",
"defaultValue": "",
"label": "Range",
"attributes": {
type: "range"
}
"messageKey": "checkboxgroup-test",
"defaultValue": [0, 1, 0],
"label": "Checkbox Group",
"options": [ "First thing", "Another thing", "Final thing" ]
},
{
"type": "select",
"appKey": "flavor",
"messageKey": "flavor",
"defaultValue": "grape",
"label": "Favorite Flavor",
"label": "Select Menu",
"options": [
{ "label": "", "value": "" },
{ "label": "Berry", "value": "berry" },
{ "label": "Grape", "value": "grape" },
{ "label": "Banana", "value": "banana" }
{ "label": "This Option is Selected", "value": "grape" },
{ "label": "Banana", "value": "banana" },
{
"label": "This is an optgroup",
"value": [
{ "label": "Peach", "value": "peach" },
{ "label": "Mango", "value": "mango" }
]
}
],
"attributes": {
"required": "required"
+15 -3
View File
@@ -10,15 +10,27 @@ module.exports = function() {
*/
function toggleBackground() {
if (this.get()) {
Clay.getItemByAppKey('background').enable();
Clay.getItemByMessageKey('colorTest').enable();
} else {
Clay.getItemByAppKey('background').disable();
Clay.getItemByMessageKey('colorTest').disable();
}
}
/**
* @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');
var coolStuffToggle = Clay.getItemByMessageKey('cool_stuff');
toggleBackground.call(coolStuffToggle);
coolStuffToggle.on('change', toggleBackground);
Clay.getItemById('testButton').on('click', handleButtonClick);
});
console.log('userData: ', Clay.meta.userData);
};
+17
View File
@@ -5,6 +5,23 @@ window.clayConfig = require('./config.js');
window.claySettings = {};
window.customFn = require('./custom-fn.js');
window.clayComponents = require('../src/scripts/components');
window.clayMeta = require('../test/fixture').meta({
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';
document.documentElement.classList.add('platform-' + platform);
+6 -1
View File
@@ -20,9 +20,14 @@
var returnTo = getQueryParam('return_to');
var data = decodeURIComponent(location.hash.substring(1));
if (data) {
// Check if we dealing with a base64 encoded URI
if (data && data.charAt(0) !== '<') {
data = window.atob(data).replace('$$RETURN_TO$$', returnTo);
window.location.href = 'data:text/html;base64,' + encodeURIComponent(window.btoa(data));
} else if (data) {
data = data.replace('$$RETURN_TO$$', returnTo);
window.location.href = 'data:text/html;charset=utf-8,' + encodeURIComponent(data);
}
</script>
<body>
-3
View File
File diff suppressed because one or more lines are too long
+12 -6
View File
@@ -15,6 +15,7 @@ var uglify = require('gulp-uglify');
var sassify = require('sassify');
var autoprefixify = require('./src/scripts/vendor/autoprefixify');
var insert = require('gulp-insert');
var clayPackage = require('./package.json');
var sassIncludePaths = [].concat(
require('bourbon').includePaths,
@@ -37,7 +38,7 @@ var autoprefixerOptions = {
var stringifyOptions = ['.html', '.tpl'];
var versionMessage = '/* Clay - https://github.com/pebble/clay - Version: ' +
require('./package.json').version +
clayPackage.version +
' - Build Date: ' + new Date().toISOString() + ' */\n';
gulp.task('clean-js', function() {
@@ -84,18 +85,22 @@ gulp.task('inlineHtml', ['js', 'sass'], function() {
gulp.task('clay', ['inlineHtml'], function() {
return browserify('index.js', {
debug: false,
standalone: 'clay'
standalone: clayPackage.name
})
.transform('deamdify')
.transform(stringify(stringifyOptions))
.transform(sassify, sassifyOptions)
.transform(autoprefixify, autoprefixerOptions)
.require(require.resolve('./index'), {expose: 'pebble-clay'})
.require(require.resolve('./index'), {expose: clayPackage.name})
.exclude('message_keys')
.bundle()
.pipe(source('clay.js'))
.pipe(source('index.js'))
.pipe(buffer())
.pipe(uglify())
.pipe(uglify({
preserveComments: 'license'
}))
.pipe(insert.prepend(versionMessage))
.pipe(gulp.dest('./dist/'));
.pipe(gulp.dest('./src/js'));
});
gulp.task('dev-js', ['js', 'sass'], function() {
@@ -103,6 +108,7 @@ gulp.task('dev-js', ['js', 'sass'], function() {
.transform(stringify(stringifyOptions))
.transform('deamdify')
.transform(sassify, sassifyOptions)
.transform(autoprefixify, autoprefixerOptions)
.bundle()
.pipe(source('dev.js'))
.pipe(gulp.dest('./tmp/'));
+200 -52
View File
@@ -3,14 +3,19 @@
var configPageHtml = require('./tmp/config-page.html');
var toSource = require('tosource');
var standardComponents = require('./src/scripts/components');
var deepcopy = require('deepcopy/build/deepcopy.min');
var version = require('./package.json').version;
var messageKeys = require('message_keys');
/**
* @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
* @param {boolean} [options.autoHandleEvents=true] - If false, Clay will not
* automatically handle the 'showConfiguration' and 'webviewclosed' events
* @param {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
* be available as `clayConfig.meta.userData`
* @constructor
*/
function Clay(config, customFn, options) {
@@ -26,14 +31,36 @@ function Clay(config, customFn, options) {
options = options || {};
self.config = config;
self.config = deepcopy(config);
self.customFn = customFn || function() {};
self.components = {};
self.meta = {
activeWatchInfo: null,
accountToken: '',
watchToken: '',
userData: {}
};
self.version = version;
/**
* Populate the meta with data from the Pebble object. Make sure to run this inside
* either the "showConfiguration" or "ready" event handler
* @return {void}
*/
function _populateMeta() {
self.meta = {
activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
accountToken: Pebble.getAccountToken(),
watchToken: Pebble.getWatchToken(),
userData: deepcopy(options.userData || {})
};
}
// Let Clay handle all the magic
if (options.autoHandleEvents !== false && typeof Pebble !== 'undefined') {
Pebble.addEventListener('showConfiguration', function() {
_populateMeta();
Pebble.openURL(self.generateUrl());
});
@@ -49,26 +76,57 @@ function Clay(config, customFn, options) {
console.log(JSON.stringify(error));
});
});
} else if (typeof Pebble !== 'undefined') {
Pebble.addEventListener('ready', function() {
_populateMeta();
});
}
/**
* If this function returns true then the callback will be executed
* @callback _scanConfig_testFn
* @param {Clay~ConfigItem} item
*/
/**
* @callback _scanConfig_callback
* @param {Clay~ConfigItem} item
*/
/**
* Scan over the config and run the callback if the testFn resolves to true
* @private
* @param {Clay~ConfigItem|Array} item
* @param {_scanConfig_testFn} testFn
* @param {_scanConfig_callback} callback
* @return {void}
*/
function _registerStandardComponents(item) {
function _scanConfig(item, testFn, callback) {
if (Array.isArray(item)) {
item.forEach(function(item) {
_registerStandardComponents(item);
_scanConfig(item, testFn, callback);
});
} else if (item.type === 'section') {
_registerStandardComponents(item.items);
} else if (standardComponents[item.type]) {
self.registerComponent(standardComponents[item.type]);
_scanConfig(item.items, testFn, callback);
} else if (testFn(item)) {
callback(item);
}
}
_registerStandardComponents(self.config);
// register standard components
_scanConfig(self.config, function(item) {
return standardComponents[item.type];
}, function(item) {
self.registerComponent(standardComponents[item.type]);
});
// validate config against teh use of appKeys
_scanConfig(self.config, function(item) {
return item.appKey;
}, function() {
throw new Error('appKeys are no longer supported. ' +
'Please follow the migration guide to upgrade your project');
});
}
/**
@@ -107,7 +165,8 @@ Clay.prototype.generateUrl = function() {
.replace('$$CUSTOM_FN$$', toSource(this.customFn))
.replace('$$CONFIG$$', toSource(this.config))
.replace('$$SETTINGS$$', toSource(settings))
.replace('$$COMPONENTS$$', toSource(this.components));
.replace('$$COMPONENTS$$', toSource(this.components))
.replace('$$META$$', toSource(this.meta));
// if we are in the emulator then we need to proxy the data via a webpage to
// obtain the return_to.
@@ -125,76 +184,165 @@ Clay.prototype.generateUrl = function() {
/**
* Parse the response from the webviewclosed event data
* @param {string} response
* @param {boolean} [convert=true]
* @returns {Object}
*/
Clay.prototype.getSettings = function(response) {
Clay.prototype.getSettings = function(response, convert) {
// Decode and parse config data as JSON
var settings = {};
response = response.match(/^\{/) ? response : decodeURIComponent(response);
try {
settings = JSON.parse(decodeURIComponent(response));
settings = JSON.parse(response);
} catch (e) {
throw new Error('The provided response was not valid JSON');
}
localStorage.setItem('clay-settings', JSON.stringify(settings));
return settings;
// flatten the settings for localStorage
var settingsStorage = {};
Object.keys(settings).forEach(function(key) {
if (typeof settings[key] === 'object' && settings[key]) {
settingsStorage[key] = settings[key].value;
} else {
settingsStorage[key] = settings[key];
}
});
localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
return convert === false ?
settings :
Clay.prepareSettingsForAppMessage(settings, messageKeys);
};
/**
* @param {string} input
* @param {string} [prefix]
* @param {string} [prefix='data:text/html;charset=utf-8,']
* @returns {string}
*/
Clay.encodeDataUri = function(input, prefix) {
prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;base64,';
prefix = typeof prefix !== 'undefined' ? prefix : 'data:text/html;charset=utf-8,';
return prefix + encodeURIComponent(input);
};
if (window.btoa) {
return prefix + encodeURIComponent(window.btoa(input));
/**
* Converts the val into a type compatible with Pebble.sendAppMessage().
* - Strings will be returned without modification
* - Numbers will be returned without modification
* - Booleans will be converted to a 0 or 1
* - Arrays that contain strings will be split with a zero.
* eg: ['one', 'two'] becomes ['one', 0, 'two', 0]
* - Arrays that contain numbers will be returned without modification
* eg: [1, 2] becomes [1, 2]
* - Arrays that contain booleans will be converted to a 0 or 1
* eg: [true, false] becomes [1, 0]
* - Arrays must be single dimensional
* - Objects that have a "value" property will apply the above rules to the type of
* value. If the value is a number or an array of numbers and the optional
* property: "precision" is provided, then the number will be multipled by 10 to
* the power of precision (value * 10 ^ precision) and then floored.
* Eg: 1.4567 with a precision set to 3 will become 1456
* @param {number|string|boolean|Array|Object} val
* @param {number|string|boolean|Array} val.value
* @param {number|undefined} [val.precision=0]
* @returns {number|string|Array}
*/
Clay.prepareForAppMessage = function(val) {
/**
* moves the decimal place of a number by precision then drop any remaining decimal
* places.
* @param {number} number
* @param {number} precision - number of decimal places to move
* @returns {number}
* @private
*/
function _normalizeToPrecision(number, precision) {
return Math.floor(number * Math.pow(10, precision || 0));
}
// iOS doesn't have a window so we need to polyfil window.btoa
// extracted from https://github.com/inexorabletash/polyfill
var B64_ALPHABET =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
input = String(input);
var position = 0;
var out = [];
var o1;
var o2;
var o3;
var e1;
var e2;
var e3;
var e4;
var result;
if (/[^\x00-\xFF]/.test(input)) { throw Error('InvalidCharacterError'); }
if (Array.isArray(val)) {
result = [];
val.forEach(function(item, index) {
result[index] = Clay.prepareForAppMessage(item);
});
} else if (typeof val === 'object' && val) {
if (typeof val.value === 'number') {
result = _normalizeToPrecision(val.value, val.precision);
} else if (Array.isArray(val.value)) {
result = val.value.map(function(item) {
if (typeof item === 'number') {
return _normalizeToPrecision(item, val.precision);
}
return item;
});
} else {
result = Clay.prepareForAppMessage(val.value);
}
} else if (typeof val === 'boolean') {
result = val ? 1 : 0;
} else {
result = val;
}
while (position < input.length) {
o1 = input.charCodeAt(position++);
o2 = input.charCodeAt(position++);
o3 = input.charCodeAt(position++);
return result;
};
// 111111 112222 222233 333333
e1 = o1 >> 2;
e2 = ((o1 & 0x3) << 4) | (o2 >> 4);
e3 = ((o2 & 0xf) << 2) | (o3 >> 6);
e4 = o3 & 0x3f;
/**
* Converts a Clay settings dict into one that is compatible with
* Pebble.sendAppMessage(); It also uses the provided messageKeys to correctly
* assign arrays into individual keys
* @see {prepareForAppMessage}
* @param {Object} settings
* @returns {{}}
*/
Clay.prepareSettingsForAppMessage = function(settings) {
if (position === input.length + 2) {
e3 = 64;
e4 = 64;
} else if (position === input.length + 1) {
e4 = 64;
// flatten settings
var flatSettings = {};
Object.keys(settings).forEach(function(key) {
var val = settings[key];
var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);
if (!matches[2]) {
flatSettings[key] = val;
return;
}
out.push(B64_ALPHABET.charAt(e1),
B64_ALPHABET.charAt(e2),
B64_ALPHABET.charAt(e3),
B64_ALPHABET.charAt(e4));
}
var position = parseInt(matches[2], 10);
key = matches[1];
return prefix + encodeURIComponent(out.join(''));
if (typeof flatSettings[key] === 'undefined') {
flatSettings[key] = [];
}
flatSettings[key][position] = val;
});
var result = {};
Object.keys(flatSettings).forEach(function(key) {
var messageKey = messageKeys[key];
var settingArr = Clay.prepareForAppMessage(flatSettings[key]);
settingArr = Array.isArray(settingArr) ? settingArr : [settingArr];
settingArr.forEach(function(setting, index) {
result[messageKey + index] = setting;
});
});
// validate the settings
Object.keys(result).forEach(function(key) {
if (Array.isArray(result[key])) {
throw new Error('Clay does not support 2 dimensional arrays for item ' +
'values. Make sure you are not attempting to use array ' +
'syntax (eg: "myMessageKey[2]") in the messageKey for ' +
'components that return an array, such as a checkboxgroup');
}
});
return result;
};
module.exports = Clay;
+24 -8
View File
@@ -1,32 +1,47 @@
{
"name": "pebble-clay",
"version": "0.1.0",
"version": "1.0.0",
"description": "Pebble Config Framework",
"main": "dist/clay.js",
"directories": {
"example": "example"
},
"scripts": {
"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/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --no-single-run)",
"test": "./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --single-run",
"lint": "./node_modules/.bin/eslint ./",
"build": "gulp",
"dev": "gulp dev"
"dev": "gulp dev",
"pebble-clean": "rm -rf tmp src/js/index.js && pebble clean",
"pebble-publish": "npm run pebble-clean && npm run build && pebble build && pebble package publish && npm run pebble-clean",
"pebble-build": "npm run build && pebble build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/pebble/clay.git"
},
"keywords": [
"Pebble",
"Config"
"pebble",
"config",
"configuration",
"pebble-package"
],
"author": "Pebble Technology",
"license": "MIT",
"bugs": {
"url": "https://github.com/pebble/clay/issues"
},
"pebble": {
"projectType": "package",
"sdkVersion": "3",
"targetPlatforms": [
"aplite",
"basalt",
"chalk",
"diorite",
"emery"
],
"resources": {
"media": []
}
},
"homepage": "https://github.com/pebble/clay#readme",
"devDependencies": {
"autoprefixer": "^6.3.1",
@@ -35,6 +50,7 @@
"browserify-istanbul": "^0.2.1",
"chai": "^3.4.1",
"deamdify": "^0.2.0",
"deepcopy": "^0.6.1",
"del": "^2.0.2",
"eslint": "^1.5.1",
"eslint-config-pebble": "^1.2.0",
+1
View File
@@ -10,6 +10,7 @@
window.claySettings = $$SETTINGS$$;
window.customFn = $$CUSTOM_FN$$;
window.clayComponents = $$COMPONENTS$$;
window.clayMeta = $$META$$;
</script>
</head>
<body>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 135 KiB

+13
View File
@@ -0,0 +1,13 @@
'use strict';
module.exports = {
name: 'button',
template: require('../../templates/components/button.tpl'),
style: require('../../styles/clay/components/button.scss'),
manipulator: 'button',
defaults: {
primary: false,
attributes: {},
description: ''
}
};
+1 -1
View File
@@ -8,6 +8,6 @@ module.exports = {
defaults: {
label: '',
options: [],
attributes: {}
description: ''
}
};
+225 -93
View File
@@ -6,119 +6,228 @@ module.exports = {
style: require('../../styles/clay/components/color.scss'),
manipulator: 'color',
defaults: {
label: ''
label: '',
description: ''
},
initialize: function(minified) {
initialize: function(minified, clay) {
var HTML = minified.HTML;
var self = this;
var useSunlight = self.config.sunlight !== false;
var sunlightColorMap = {
'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'
};
/**
* @param {string|boolean|number} color
* @returns {string}
*/
function cssColor(color) {
if (color === false) { return 'transparent'; }
if (typeof color === 'number') {
color = color.toString(16);
} else if (!color) {
return 'transparent';
}
while (color.length < 6) {
color = '0' + color;
}
color = padColorString(color);
return '#' + (useSunlight ? sunlightColorMap[color] : color);
}
/**
* @param {string} color
* @return {string}
*/
function padColorString(color) {
color = color.toLowerCase();
while (color.length < 6) {
color = '0' + color;
}
return color;
}
/**
* @param {number|string} value
* @returns {string|*}
*/
function normalizeColor(value) {
switch (typeof value) {
case 'number': return padColorString(value.toString(16));
case 'string': return value.replace(/^#|^0x/, '');
default: return value;
}
}
/**
* @param {Array.<Array>} layout
* @returns {Array}
*/
function flattenLayout(layout) {
return layout.reduce(function(a, b) {
return a.concat(b);
}, []);
}
/**
* Convert HEX color to LAB.
* Adapted from: https://github.com/antimatter15/rgb-lab
* @param {string} hex
* @returns {Array} - [l, a, b]
*/
function hex2lab(hex) {
hex = hex.replace(/^#|^0x/, '');
var r = parseInt(hex.slice(0, 2), 16) / 255;
var g = parseInt(hex.slice(2, 4), 16) / 255;
var b = parseInt(hex.slice(4), 16) / 255;
r = (r > 0.04045) ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
g = (g > 0.04045) ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
b = (b > 0.04045) ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;
var x = (r * 0.4124 + g * 0.3576 + b * 0.1805) / 0.95047;
var y = (r * 0.2126 + g * 0.7152 + b * 0.0722) / 1.00000;
var z = (r * 0.0193 + g * 0.1192 + b * 0.9505) / 1.08883;
x = (x > 0.008856) ? Math.pow(x, 1 / 3) : (7.787 * x) + 16 / 116;
y = (y > 0.008856) ? Math.pow(y, 1 / 3) : (7.787 * y) + 16 / 116;
z = (z > 0.008856) ? Math.pow(z, 1 / 3) : (7.787 * z) + 16 / 116;
return [(116 * y) - 16, 500 * (x - y), 200 * (y - z)];
}
/**
* Find the perceptual color distance between two LAB colors
* @param {Array} labA
* @param {Array} labB
* @returns {number}
*/
function deltaE(labA, labB) {
var deltaL = labA[0] - labB[0];
var deltaA = labA[1] - labB[1];
var deltaB = labA[2] - labB[2];
return Math.sqrt(Math.pow(deltaL, 2) +
Math.pow(deltaA, 2) +
Math.pow(deltaB, 2));
}
/**
* Returns the layout based on the connected watch
* @returns {Array}
*/
function autoLayout() {
if (!clay.meta.activeWatchInfo ||
clay.meta.activeWatchInfo.firmware.major === 2 ||
clay.meta.activeWatchInfo.platform === 'aplite' &&
!self.config.allowGray) {
return standardLayouts.BLACK_WHITE;
}
if (clay.meta.activeWatchInfo.platform === 'aplite' && self.config.allowGray) {
return standardLayouts.GRAY;
}
return standardLayouts.COLOR;
}
/**
* @param {number|string} color
* @return {number}
*/
self.roundColorToLayout = function(color) {
var itemValue = normalizeColor(color);
// if the color is not in the layout we will need find the closest match
if (colorList.indexOf(itemValue) === -1) {
var itemValueLAB = hex2lab(itemValue);
var differenceArr = colorList.map(function(color) {
var colorLAB = hex2lab(normalizeColor(color));
return deltaE(itemValueLAB, colorLAB);
});
// Get the lowest number from the differenceArray
var lowest = Math.min.apply(Math, differenceArr);
// Get the index for that lowest number
var index = differenceArr.indexOf(lowest);
itemValue = colorList[index];
}
return parseInt(itemValue, 16);
};
var useSunlight = self.config.sunlight !== false;
var sunlightColorMap = {
'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'
};
/* eslint-disable comma-spacing, no-multi-spaces, max-len,
standard/array-bracket-even-spacing */
var layout = self.config.layout || [
[false , false , '55ff00', 'aaff55', false , 'ffff55', 'ffffaa', false , false ],
[false , 'aaffaa', '55ff55', '00ff00', 'aaff00', 'ffff00', 'ffaa55', 'ffaaaa', false ],
['55ffaa', '00ff55', '00aa00', '55aa00', 'aaaa55', 'aaaa00', 'ffaa00', 'ff5500', 'ff5555'],
['aaffff', '00ffaa', '00aa55', '55aa55', '005500', '555500', 'aa5500', 'ff0000', 'ff0055'],
[false , '55aaaa', '00aaaa', '005555', 'ffffff', '000000', 'aa5555', 'aa0000', false ],
['55ffff', '00ffff', '00aaff', '0055aa', 'aaaaaa', '555555', '550000', 'aa0055', 'ff55aa'],
['55aaff', '0055ff', '0000ff', '0000aa', '000055', '550055', 'aa00aa', 'ff00aa', 'ffaaff'],
[false , '5555aa', '5555ff', '5500ff', '5500aa', 'aa00ff', 'ff00ff', 'ff55ff', false ],
[false , false , false , 'aaaaff', 'aa55ff', 'aa55aa', false , false , false ]
];
standard/array-bracket-even-spacing */
var standardLayouts = {
COLOR: [
[false , false , '55ff00', 'aaff55', false , 'ffff55', 'ffffaa', false , false ],
[false , 'aaffaa', '55ff55', '00ff00', 'aaff00', 'ffff00', 'ffaa55', 'ffaaaa', false ],
['55ffaa', '00ff55', '00aa00', '55aa00', 'aaaa55', 'aaaa00', 'ffaa00', 'ff5500', 'ff5555'],
['aaffff', '00ffaa', '00aa55', '55aa55', '005500', '555500', 'aa5500', 'ff0000', 'ff0055'],
[false , '55aaaa', '00aaaa', '005555', 'ffffff', '000000', 'aa5555', 'aa0000', false ],
['55ffff', '00ffff', '00aaff', '0055aa', 'aaaaaa', '555555', '550000', 'aa0055', 'ff55aa'],
['55aaff', '0055ff', '0000ff', '0000aa', '000055', '550055', 'aa00aa', 'ff00aa', 'ffaaff'],
[false , '5555aa', '5555ff', '5500ff', '5500aa', 'aa00ff', 'ff00ff', 'ff55ff', false ],
[false , false , false , 'aaaaff', 'aa55ff', 'aa55aa', false , false , false ]
],
GRAY: [
['000000', 'aaaaaa', 'ffffff']
],
BLACK_WHITE: [
['000000', 'ffffff']
]
};
/* eslint-enable */
var layout = self.config.layout || autoLayout();
if (typeof layout === 'string') {
layout = standardLayouts[layout];
}
// make sure layout is a 2D array
if (!Array.isArray(layout[0])) {
layout = [layout];
}
var colorList = flattenLayout(layout).map(function(item) {
return normalizeColor(item);
}).filter(function(item) {
return item;
});
var grid = '';
var itemWidth = 100 / layout[0].length;
var itemHeight = 100 / layout.length;
var rows = layout.length;
var cols = 0;
layout.forEach(function(row) {
cols = row.length > cols ? row.length : cols;
});
var itemWidth = 100 / cols;
var itemHeight = 100 / rows;
var $elem = self.$element;
for (var i = 0; i < layout.length; i++) {
for (var j = 0; j < layout[i].length; j++) {
for (var i = 0; i < rows; i++) {
for (var j = 0; j < cols; j++) {
var color = layout[i][j];
var color = normalizeColor(layout[i][j]);
var selectable = (color ? ' selectable' : '');
var roundedTL = (i === 0 && j === 0) || i === 0 && !layout[i][j - 1] ||
@@ -145,7 +254,7 @@ module.exports = {
grid += '<i ' +
'class="color-box ' + selectable + roundedTL + roundedTR + roundedBL +
roundedBR + '" ' +
'data-value="' + parseInt(color, 16) + '" ' +
(color ? 'data-value="' + parseInt(color, 16) + '" ' : '') +
'style="' +
'width:' + itemWidth + '%; ' +
'height:' + itemHeight + '%; ' +
@@ -154,13 +263,34 @@ module.exports = {
}
}
$elem.select('.color-box-container').add(HTML(grid));
// on very small layouts the boxes end up huge. The following adds extra padding
// to prevent them from being so big.
var extraPadding = 0;
if (cols === 3) {
extraPadding = 5;
}
if (cols === 2) {
extraPadding = 8;
}
var vPadding = (extraPadding * itemWidth / itemHeight) + '%';
var hPadding = extraPadding + '%';
$elem.select('.color-box-container')
.add(HTML(grid))
.set('$paddingTop', vPadding)
.set('$paddingRight', hPadding)
.set('$paddingBottom', vPadding)
.set('$paddingLeft', hPadding);
$elem.select('.color-box-wrap').set(
'$paddingBottom',
(itemWidth / itemHeight * 100) + '%'
);
var $valueDisplay = $elem.select('.value');
var $picker = $elem.select('.picker-wrap');
var disabled = self.$manipulatorTarget.get('disabled');
$elem.select('label').on('click', function(ev) {
$elem.select('label').on('click', function() {
if (!disabled) {
$picker.set('show'); // toggle visibility
}
@@ -190,5 +320,7 @@ module.exports = {
disabled = false;
});
// expose layout for testing
self._layout = layout;
}
};
+3 -1
View File
@@ -10,5 +10,7 @@ module.exports = {
text: require('./text'),
toggle: require('./toggle'),
radiogroup: require('./radiogroup'),
checkboxgroup: require('./checkboxgroup')
checkboxgroup: require('./checkboxgroup'),
button: require('./button'),
slider: require('./slider')
};
+1
View File
@@ -7,6 +7,7 @@ module.exports = {
manipulator: 'val',
defaults: {
label: '',
description: '',
attributes: {}
}
};
+1
View File
@@ -8,6 +8,7 @@ module.exports = {
defaults: {
label: '',
options: [],
description: '',
attributes: {}
}
};
+13 -3
View File
@@ -8,6 +8,7 @@ module.exports = {
defaults: {
label: '',
options: [],
description: '',
attributes: {}
},
initialize: function() {
@@ -15,9 +16,18 @@ module.exports = {
var $value = self.$element.select('.value');
self.on('change', function(ev) {
var value = self.$manipulatorTarget.select('option:checked').get('innerHTML');
/**
* Updates the HTML value of the component to match the slected option's label
* @return {void}
*/
function setValueDisplay() {
var selectedIndex = self.$manipulatorTarget.get('selectedIndex');
var $options = self.$manipulatorTarget.select('option');
var value = $options[selectedIndex] && $options[selectedIndex].innerHTML;
$value.set('innerHTML', value);
});
}
setValueDisplay();
self.on('change', setValueDisplay);
}
};
+50
View File
@@ -0,0 +1,50 @@
'use strict';
module.exports = {
name: 'slider',
template: require('../../templates/components/slider.tpl'),
style: require('../../styles/clay/components/slider.scss'),
manipulator: 'slider',
defaults: {
label: '',
description: '',
min: 0,
max: 100,
step: 1,
attributes: {}
},
initialize: function() {
var self = this;
var $value = self.$element.select('.value');
var $valuePad = self.$element.select('.value-pad');
var $slider = self.$manipulatorTarget;
/**
* Sets the value display
* @return {void}
*/
function setValueDisplay() {
var value = self.get().toFixed(self.precision);
$value.set('value', value);
$valuePad.set('innerHTML', value);
}
var step = $slider.get('step');
step = step.toString(10).split('.')[1];
self.precision = step ? step.length : 0;
self.on('change', setValueDisplay);
$slider.on('|input', setValueDisplay);
setValueDisplay();
$value.on('|input', function() {
$valuePad.set('innerHTML', this.get('value'));
});
$value.on('|change', function() {
self.set(this.get('value'));
setValueDisplay();
});
}
};
+2 -1
View File
@@ -3,7 +3,8 @@
module.exports = {
name: 'submit',
template: require('../../templates/components/submit.tpl'),
manipulator: 'html',
style: require('../../styles/clay/components/submit.scss'),
manipulator: 'button',
defaults: {
attributes: {}
}
+1
View File
@@ -7,6 +7,7 @@ module.exports = {
manipulator: 'checked',
defaults: {
label: '',
description: '',
attributes: {}
}
};
+3 -2
View File
@@ -11,6 +11,7 @@ var settings = _.extend({}, window.claySettings || {});
var returnTo = window.returnTo || 'pebblejs://close#';
var customFn = window.customFn || function() {};
var clayComponents = window.clayComponents || {};
var clayMeta = window.clayMeta || {};
var platform = window.navigator.userAgent.match(/android/i) ? 'android' : 'ios';
document.documentElement.classList.add('platform-' + platform);
@@ -21,13 +22,13 @@ _.eachObj(clayComponents, function(key, component) {
});
var $mainForm = $('#main-form');
var clayConfig = new ClayConfig(settings, config, $mainForm);
var clayConfig = new ClayConfig(settings, config, $mainForm, clayMeta);
// add listeners here
$mainForm.on('submit', function() {
// Set the return URL depending on the runtime environment
location.href = returnTo +
encodeURIComponent(JSON.stringify(clayConfig.getSettings()));
encodeURIComponent(JSON.stringify(clayConfig.serialize()));
});
// Run the custom function in the context of the ClayConfig
+105 -42
View File
@@ -5,12 +5,13 @@
* @typedef {Object} Clay~ConfigItem
* @property {string} type
* @property {string|boolean|number} defaultValue
* @property {string} [appKey]
* @property {string} [messageKey]
* @property {string} [id]
* @property {string} [label]
* @property {Object} [attributes]
* @property {Array} [options]
* @property {Array} [items]
* @property {Array} [capabilities]
*/
var HTML = require('../vendor/minified').HTML;
@@ -26,16 +27,29 @@ var manipulators = require('./manipulators');
* @param {Object} settings - setting that were set from a previous session
* @param {Array|Object} config
* @param {M} $rootContainer
* @param {Object} meta
* @constructor
*/
function ClayConfig(settings, config, $rootContainer) {
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 _itemsByMessageKey;
var _isBuilt;
/**
* Initialize the item arrays and objects
* @private
* @return {void}
*/
function _initializeItems() {
_items = [];
_itemsById = {};
_itemsByMessageKey = {};
_isBuilt = false;
}
/**
* Add item(s) to the config
@@ -48,34 +62,36 @@ function ClayConfig(settings, config, $rootContainer) {
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.messageKey) {
_itemsByMessageKey[_item.messageKey] = clayItem;
}
_items.push(clayItem);
// set the value of the item via the manipulator to ensure consistency
var value = typeof _settings[_item.messageKey] !== 'undefined' ?
_settings[_item.messageKey] :
(_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);
}
}
@@ -95,6 +111,9 @@ function ClayConfig(settings, config, $rootContainer) {
return true;
}
self.meta = meta;
self.$rootContainer = $rootContainer;
self.EVENTS = {
/**
* Called before framework has initialized. This is when you would attach your
@@ -108,7 +127,21 @@ function ClayConfig(settings, config, $rootContainer) {
* 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});
@@ -121,12 +154,12 @@ function ClayConfig(settings, config, $rootContainer) {
};
/**
* @param {string} appKey
* @param {string} messageKey
* @returns {ClayItem}
*/
self.getItemByAppKey = function(appKey) {
_checkBuilt('getItemByAppKey');
return _itemsByAppKey[appKey];
self.getItemByMessageKey = function(messageKey) {
_checkBuilt('getItemByMessageKey');
return _itemsByMessageKey[messageKey];
};
/**
@@ -152,10 +185,19 @@ function ClayConfig(settings, config, $rootContainer) {
/**
* @returns {Object}
*/
self.getSettings = function() {
_checkBuilt('getSettings');
_.eachObj(_itemsByAppKey, function(appKey, item) {
_settings[appKey] = item.get();
self.serialize = function() {
_checkBuilt('serialize');
_settings = {};
_.eachObj(_itemsByMessageKey, function(messageKey, item) {
_settings[messageKey] = {
value: item.get()
};
if (item.precision) {
_settings[messageKey].precision = item.precision;
}
});
return _settings;
};
@@ -163,18 +205,40 @@ function ClayConfig(settings, config, $rootContainer) {
// @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);
@@ -183,7 +247,6 @@ function ClayConfig(settings, config, $rootContainer) {
// expose the config to allow developers to update it before the build is run
self.config = config;
}
/**
+2 -2
View File
@@ -29,7 +29,7 @@ function ClayItem(config) {
self.id = config.id || null;
/** @type {string|null} */
self.appKey = config.appKey || null;
self.messageKey = config.messageKey || null;
/** @type {Object} */
self.config = config;
@@ -59,7 +59,7 @@ function ClayItem(config) {
};
// attach event methods
ClayEvents.call(self, self.$element);
ClayEvents.call(self, self.$manipulatorTarget);
// attach the manipulator methods to the clayItem
_.eachObj(_component.manipulator, function(methodName, method) {
+100 -24
View File
@@ -1,104 +1,180 @@
'use strict';
var _ = require('../vendor/minified')._;
/**
* @returns {ClayEvents}
* @returns {ClayItem|ClayEvents}
* @extends {ClayItem}
*/
function disable() {
if (this.$manipulatorTarget.get('disabled')) { return this; }
this.$element.set('+disabled');
this.$manipulatorTarget.set('disabled', true);
return this.trigger('disabled');
}
/**
* @returns {ClayEvents}
* @returns {ClayItem|ClayEvents}
* @extends {ClayItem}
*/
function enable() {
if (!this.$manipulatorTarget.get('disabled')) { return this; }
this.$element.set('-disabled');
this.$manipulatorTarget.set('disabled', false);
return this.trigger('enabled');
}
/**
* @returns {ClayItem|ClayEvents}
* @extends {ClayItem}
*/
function hide() {
if (this.$element[0].classList.contains('hide')) { return this; }
this.$element.set('+hide');
return this.trigger('hide');
}
/**
* @returns {ClayItem|ClayEvents}
* @extends {ClayItem}
*/
function show() {
if (!this.$element[0].classList.contains('hide')) { return this; }
this.$element.set('-hide');
return this.trigger('show');
}
module.exports = {
html: {
get: function() {
return this.$manipulatorTarget.get('innerHTML');
},
set: function(value) {
if (this.get() === value.toString(10)) { return this; }
this.$manipulatorTarget.set('innerHTML', value);
return this.trigger('change');
}
},
hide: hide,
show: show
},
button: {
get: function() {
return this.$manipulatorTarget.get('innerHTML');
},
set: function(value) {
if (this.get() === value.toString(10)) { return this; }
this.$manipulatorTarget.set('innerHTML', value);
return this.trigger('change');
},
disable: disable,
enable: enable,
hide: hide,
show: show
},
val: {
get: function() {
return this.$manipulatorTarget.get('value');
},
set: function(value) {
if (this.get() === value.toString(10)) { return this; }
this.$manipulatorTarget.set('value', value);
return this.trigger('change');
},
disable: disable,
enable: enable
enable: enable,
hide: hide,
show: show
},
slider: {
get: function() {
return parseFloat(this.$manipulatorTarget.get('value'));
},
set: function(value) {
var initVal = this.get();
this.$manipulatorTarget.set('value', value);
if (this.get() === initVal) { return this; }
return this.trigger('change');
},
disable: disable,
enable: enable,
hide: hide,
show: show
},
checked: {
get: function() {
return this.$manipulatorTarget.get('checked') ? 1 : 0;
return this.$manipulatorTarget.get('checked');
},
set: function(value) {
if (!this.get() === !value) { return this; }
this.$manipulatorTarget.set('checked', !!value);
return this.trigger('change');
},
disable: disable,
enable: enable
enable: enable,
hide: hide,
show: show
},
radiogroup: {
get: function() {
return this.$element.select('input:checked').get('value');
},
set: function(value) {
if (this.get() === value.toString(10)) { return this; }
this.$element
.select('input[value="' + value.replace('"', '\\"') + '"]')
.set('checked', true);
return this.trigger('change');
},
disable: disable,
enable: enable
enable: enable,
hide: hide,
show: show
},
checkboxgroup: {
get: function() {
var result = [];
this.$element.select('input:checked').each(function(item) {
result.push(item.value);
this.$element.select('input').each(function(item) {
result.push(!!item.checked);
});
return result;
},
set: function(values) {
var self = this;
self.$element.select('input').set('checked', false);
values = values || [];
values.map(function(value) {
self.$element
.select('input[value="' + value.replace('"', '\\"') + '"]')
.set('checked', true);
});
values = Array.isArray(values) ? values : [];
while (values.length < this.get().length) {
values.push(false);
}
if (_.equals(this.get(), values)) { return this; }
self.$element.select('input')
.set('checked', false)
.each(function(item, index) {
item.checked = !!values[index];
});
return self.trigger('change');
},
disable: disable,
enable: enable
enable: enable,
hide: hide,
show: show
},
color: {
get: function() {
return parseInt(this.$manipulatorTarget.get('value'), 16);
return parseInt(this.$manipulatorTarget.get('value'), 10) || 0;
},
set: function(value) {
switch (typeof value) {
case 'number': value = value.toString(16); break;
case 'string': value = value.replace(/^#|^0x/, ''); break;
}
value = this.roundColorToLayout(value || 0);
this.$manipulatorTarget.set('value', value || '000000');
if (this.get() === value) { return this; }
this.$manipulatorTarget.set('value', value);
return this.trigger('change');
},
disable: disable,
enable: enable
enable: enable,
hide: hide,
show: show
}
};
+116
View File
@@ -17,3 +17,119 @@ module.exports.updateProperties = function(obj, descriptor) {
Object.defineProperty(obj, prop, descriptor);
});
};
module.exports.capabilityMap = {
PLATFORM_APLITE: {
platforms: ['aplite'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_BASALT: {
platforms: ['basalt'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_CHALK: {
platforms: ['chalk'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_DIORITE: {
platforms: ['diorite'],
minFwMajor: 0,
minFwMinor: 0
},
PLATFORM_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: 0,
minFwMinor: 0
},
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<string>} [capabilities]
* @return {boolean}
*/
module.exports.includesCapability = function(activeWatchInfo, capabilities) {
var notRegex = /^NOT_/;
var result = [];
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.replace(notRegex, '')];
if (!mapping ||
mapping.platforms.indexOf(activeWatchInfo.platform) === -1 ||
mapping.minFwMajor > activeWatchInfo.firmware.major ||
mapping.minFwMajor === activeWatchInfo.firmware.major &&
mapping.minFwMinor > activeWatchInfo.firmware.minor
) {
result.push(!!capability.match(notRegex));
} else {
result.push(!capability.match(notRegex));
}
}
return result.indexOf(false) === -1;
};
+53 -3
View File
@@ -1,8 +1,8 @@
// minified.js config start -- use this comment to re-create a configuration in the Builder
// - Only sections add, always, amdsupport, copyobj, dollardollar,
// - each, eachobj, error, extend, find, format, formathtml, get, ht, html,
// - isobject, off, on, ready, request, select, set, template, trigger, underscore,
// - wait.
// - each, eachobj, equals, error, extend, find, format, formathtml, get, ht,
// - html, isobject, off, on, ready, request, select, set, template, trigger,
// - underscore, wait.
// WARNING! This file is autogenerated from minified-master.js and others.
@@ -1550,6 +1550,53 @@ define('minified', function() {
*/
'each': listBind(each),
/*$
* @id equals
* @group LIST
* @requires
* @configurable default
* @name .equals()
* @altname _.equals()
* @syntax list.equals(otherObject)
* @syntax _.equals(thisObject, otherObject)
* @module UTIL
* Checks whether two values, lists or objects are equal in a deep comparison.
*
* First <var>equals()</var> checks whether it got a function as parameter.
* If yes, it will be invoked without arguments and <var>equals()</var> calls itself recursively with the function's result.
*
* Once both values are no functions anymore, the values will be evaluated, If the first value is...
* <ul><li>...<var>null</var> or <var>undefined</var>, they are only equal if the other one is also either <var>null</var> or <var>undefined</var>.</li>
* <li>...a value as defined by ##_.isValue(), but not a Date, they are equal if the other value is the same type and is equal according to the '==' operator.</li>
* <li>...a Date, they are equal if the other value is a Date representing the same time.</li>
* <li>...a list or array, they are equal if the other value is also either a list or an array, has the same number of items and all items equal the items of the other
* list at the same position. The equality of list items is determined recursively using the same rules, so you can also nest lists.</li>
* <li>...a function, it will be invoked without arguments and its return value is evaluated using these rules as if the value has been passed. </li>
* <li>...any other object, they are equal if they contain exactly the same keys (as defined by ##_.eachObj()) and all values are equal as determined using these rules
* recursively.</li>
* </ul>
*
* Please note that, according to the rules, a ##list#Minified list## is equal to an array, as long as their content is equal. <var>equals</var> does not
* differentiate between <var>null</var> and <var>undefined</var>.
*
* <var>equals</var> is commutative. If you swap the parameters, the result is the same as long as no functions are involved.
*
* @example Compare a list and an array:
* <pre>
* _.equals([1, 2, 3], _(1, 2, 3)); // returns true
* </pre>
*
* @example Same result, but with a list method:
* <pre>
* _(1, 2, 3).equals([1, 2, 3]); // returns true
* </pre>
*
* @param thisObject The first reference to evaluate.
* @param otherObject The second reference to evaluate.
* @return true if both references are equal. False otherwise.
*/
'equals': listBind(equals),
/*$
* @id find
* @group LIST
@@ -2626,6 +2673,9 @@ define('minified', function() {
// @condblock find
'find': find,
// @condend
// @condblock equals
'equals': equals,
// @condend
/*$
* @id copyobj
+74 -71
View File
@@ -17,7 +17,24 @@ html, body {
body {
background-color: $color-gray-2;
padding: $item-spacing-v $item-spacing-h;
padding: 0 $item-spacing-h $item-spacing-v;
}
em {
font-style: italic;
}
strong {
@include font-pfdin(medium);
color: $color-orange;
}
a {
color: $color-gray-8;
&:hover {
color: inherit;
}
}
h1, h2, h3, h4 {
@@ -50,10 +67,17 @@ h6 {
@include font-size(0.8);
}
input {
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
label {
display: flex;
justify-content: space-between;
align-items: center;
padding: $item-spacing-v $item-spacing-h;
.input {
white-space: nowrap;
@@ -83,35 +107,44 @@ label {
}
}
.component {
padding-bottom: $item-spacing-v;
.hide {
display: none !important;
}
.tap-highlight {
@include tap-highlight();
border-radius: $border-radius;
}
.component {
padding-top: $item-spacing-v;
&.disabled {
opacity: 0.25;
pointer-events: none;
> * {
opacity: 0.25;
}
}
}
.section {
background: $color-gray-4;
border-radius: 0.25rem;
margin-bottom: 1rem;
box-shadow: $color-gray-1 0 0.15rem 0.25rem;
.component {
padding-top: $item-spacing-v;
> .component {
padding-bottom: $item-spacing-v;
padding-right: $item-spacing-h;
padding-left: $item-spacing-h;
position: relative;
margin-top: 1rem;
&:last-child,
&:first-child {
padding-bottom: $item-spacing-v;
&:after {
display: none;
}
&:not(.hide) ~ .component {
margin-top: 0;
}
&:first-child:after {
display: none;
}
&:after {
@@ -119,7 +152,7 @@ label {
background: $color-gray-6;
display: block;
position: absolute;
bottom: 0;
top: 0;
left: $item-spacing-h / 2;
right: $item-spacing-h / 2;
height: 1px;
@@ -127,72 +160,42 @@ label {
}
}
.component-heading:first-child {
// don't show the separator for the first non-hidden item
> .component:not(.hide):after {
display: none;
}
> .component:not(.hide) ~ .component:not(.hide):after {
display: block;
}
> .component-heading:first-child {
background: $color-gray-3;
border-radius: $border-radius $border-radius 0 0;
// don't show the separator for the first non-hidden item after the heading
&:after,
~ .component:not(.hide):after {
display: none;
}
~ .component:not(.hide) ~ .component:not(.hide):after {
display: block;
}
}
}
strong {
@include font-pfdin(medium);
color: $color-orange;
}
a {
color: $color-gray-8;
&:hover {
color: inherit;
}
.description {
padding: 0 $item-spacing-h $item-spacing-v ;
@include font-size(0.9);
color: $color-gray-9;
text-align: left;
}
.inputs {
display: block;
width:100%;
border-collapse: collapse;
.section & .component {
display: table;
width:100%;
.label,
.input {
display: table-cell;
}
.input {
text-align: right;
}
&.invalid {
.input:after {
content: "!";
display: inline-block;
color: $color-white;
background: $color-orange;
border-radius: $border-radius;
width: 1.2em;
text-align: center;
height: 1.2em;
font-size: 1em;
vertical-align: middle;
line-height: 1.3em;
margin-left: -0.7em;
position:absolute;
@include font-pfdin(regular);
}
.input input {
color: $color-orange;
padding-right: 1em;
@include placeholder {
color: $color-orange;
}
}
}
}
}
+4 -12
View File
@@ -11,8 +11,6 @@ $item-spacing-v: $base-height / 2;
$item-spacing-h: 0.75rem;
$small-component-width: $base-height * $golden;
// ---- Colors ------
$color-orange: #ff4700;
$color-orange-dark: #993d19;
@@ -34,14 +32,8 @@ $color-gray-9: #a4a4a4 ;
$color-gray-10: #ececec;
$color-gray-11: #f2f2f2;
$button-padding: 0.6rem;
$button-padding-ios: 0.5rem;
$global-margin: 55px;
$form-margin: 25px;
$button-padding: 0.7rem;
$button-padding-ios: 0.6rem;
$button-line-height: 13px;
$footer-height-large: 90px;
$box-shadow-small-components: $color-gray-1 0 0.1rem 0.1rem;
$box-shadow-small-components: 0 0.1rem 0.1rem $color-gray-1;
$box-shadow-large-components: 0 rem(3px) rem(15px) rgba(0,0,0,0.4);
+12
View File
@@ -0,0 +1,12 @@
.component-button {
text-align: center;
.section & {
padding-bottom: 0;
}
.description {
padding-left: 0;
padding-right: 0;
}
}
+11 -5
View File
@@ -3,9 +3,11 @@
.component-checkbox {
@include tap-highlight(rgba(0,0,0,0));
display: block;
.section & {
padding-right: $item-spacing-h / 2;
}
> .label {
display: block;
@@ -14,14 +16,14 @@
.checkbox-group {
padding-bottom: $item-spacing-v / 2;
label {
padding: $item-spacing-v / 2 0;
@include tap-highlight();
padding: $item-spacing-v / 2 $item-spacing-h / 2;
}
.label {
font-size: 0.9em;
padding: 0 $item-spacing-h / 2;
}
input {
@@ -39,7 +41,7 @@
flex-shrink: 0;
}
input:checked ~ i {
input:checked + i {
border-color: $color-orange;
background: $color-orange;
@@ -59,6 +61,10 @@
}
}
}
.description {
padding-left: 0;
padding-right: 0;
}
}
+20 -7
View File
@@ -2,11 +2,17 @@
.component-color {
.section & {
padding: 0;
}
.value {
width: $small-component-width;
height: $base-height;
border-radius: $base-height / 2;
box-shadow: $box-shadow-small-components;
display: block;
background: #000;
}
.picker-wrap {
@@ -15,17 +21,25 @@
right: 0;
bottom: 0;
position: fixed;
padding: 1rem;
background: rgba(0,0,0,0.7);
padding: $item-spacing-v $item-spacing-h /2;
background: rgba(0, 0, 0, 0.65);
opacity: 0;
transition: opacity 70ms ease-in 175ms;
transition: opacity 100ms ease-in 175ms;
pointer-events: none;
z-index: 100;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.picker {
padding: 1rem;
background: $color-gray-11;
padding: $item-spacing-v $item-spacing-h;
background: $color-gray-4;
box-shadow: $box-shadow-large-components;
border-radius: $border-radius;
width: 100%;
max-width: 26rem;
overflow: auto;
}
&.show {
@@ -41,7 +55,6 @@
height: 0;
width: 100%;
padding: 0 0 100% 0; // overridden with inline style
margin: 0.6em 0 0;
.color-box-container {
position: absolute;
@@ -72,7 +85,7 @@
}
&.selected {
transform: scale(1.15);
transform: scale(1.1);
border-radius: $border-radius;
box-shadow: #111 0 0 0.24rem;
position: relative;
+9 -1
View File
@@ -2,7 +2,13 @@
.component-input {
display: block;
.section & {
padding: 0;
}
label {
display: block;
}
.label {
padding-bottom: $item-spacing-v;
@@ -25,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;
+12 -5
View File
@@ -3,9 +3,11 @@
.component-radio {
@include tap-highlight(rgba(0,0,0,0));
display: block;
.section & {
padding-right: $item-spacing-h / 2;
}
> .label {
display: block;
@@ -14,14 +16,14 @@
.radio-group {
padding-bottom: $item-spacing-v / 2;
label {
padding: $item-spacing-v / 2 0;
@include tap-highlight();
padding: $item-spacing-v / 2 $item-spacing-h / 2;
}
.label {
font-size: 0.9em;
padding: 0 $item-spacing-h / 2;
}
input {
@@ -39,7 +41,7 @@
flex-shrink: 0;
}
input:checked ~ i {
input:checked + i {
border-color: $color-orange;
&:after {
@@ -58,4 +60,9 @@
}
}
.description {
padding-left: 0;
padding-right: 0;
}
}
+10 -3
View File
@@ -2,12 +2,20 @@
@import "clay";
.component-select {
position: relative;
.section & {
padding: 0;
}
label {
position: relative;
}
.value {
$triangle-size: 0.85rem;
position: relative;
padding-right: $triangle-size + 0.25rem;
display: block;
&:after {
content: "";
@@ -15,7 +23,7 @@
right: 0;
top: 50%;
margin-top: -0.1rem;
@include triangle($triangle-size, $color-gray-10, down);
@include triangle($triangle-size, $color-orange, down);
}
}
@@ -27,7 +35,6 @@
right: 0;
top: 0;
bottom: 0;
background: #000;
width: 100%;
border: none;
margin:0;
+127
View File
@@ -0,0 +1,127 @@
@import "clay";
.component-slider {
.section & {
padding: 0;
}
label {
display: block;
}
.label-container {
display: flex;
align-items: center;
width: 100%;
padding-bottom: $item-spacing-v;
}
.label {
flex: 1;
min-width: 1rem;
display: block;
padding-right: $item-spacing-h;
}
.value-wrap {
display: block;
position: relative;
}
.value,
.value-pad {
display: block;
background: $color-gray-2;
border-radius: $border-radius;
padding: $item-spacing-v / 2 $item-spacing-h / 2;
border: none;
vertical-align: baseline;
color: $color-white;
text-align: right;
margin: 0;
min-width: 1rem;
}
.value-pad {
visibility: hidden;
&:before {
content: ' ';
display: inline-block;
}
}
.value {
max-width: 100%;
position: absolute;
left:0;
top:0
}
.input-wrap {
padding: 0 $item-spacing-h $item-spacing-v;
}
.input {
$track-height: rem(3px);
display: block;
position: relative;
min-width: 100%;
height: $base-height;
overflow: hidden;
margin-left: 0;
&:before {
content: '';
display: block;
position: absolute;
height: $track-height;
background: $color-gray-6;
width: 100%;
top: $base-height / 2 - $track-height / 2;
}
.slider {
display: block;
width: 100%;
appearance: none;
position: relative;
height: $base-height;
margin: 0;
background-color: transparent;
&:focus {
outline: none;
}
&::-webkit-slider-runnable-track {
border: none;
height: $base-height;
width: 100%;
background-color: transparent;
}
&::-webkit-slider-thumb {
appearance: none;
position: relative;
height: $base-height;
width: $base-height;
background-color: $color-orange;
border-radius: 50%;
&:before {
content: "";
position: absolute;
left: -1000px;
top: $base-height / 2 - $track-height / 2;
height: $track-height;
width: 1001px;
background: $color-orange;
}
}
}
}
}
+3
View File
@@ -0,0 +1,3 @@
.component-submit {
text-align: center;
}
+4
View File
@@ -6,6 +6,10 @@
$slide-width: $small-component-width;
$marker-diameter: $base-height;
.section & {
padding: 0;
}
input {
display: none; // @todo make sure this doesn't mess up interactivity on iOS
}
+8 -30
View File
@@ -3,52 +3,30 @@
button,
.button {
@include font-pfdin(medium);
@include font-size(1);
text-transform: uppercase;
background-color: $color-gray-4;
background-color: $color-gray-7;
border-radius: $border-radius;
font-size: 1rem;
line-height: 1;
border: none;
display: block;
display: inline-block;
color: $color-white;
min-width: 12rem;
text-align: center;
margin: 0 auto;
-webkit-tap-highlight-color: rgba(0,0,0,0);
margin: 0 auto $item-spacing-v;
padding: $button-padding;
@include tap-highlight($color-gray-8);
.platform-ios & {
padding: $button-padding-ios;
}
&:disabled {
background-color: $color-gray-3;
color: $color-gray-8;
}
&:not(:disabled):active {
background-color: $color-gray-8;
}
&.orange, &[type="submit"] {
&.primary, &[type="submit"] {
background-color: $color-orange;
&:disabled {
background-color: $color-orange-dark;
color: $color-gray-3;
}
&:not(:disabled):active {
background-color: $color-red;
}
@include tap-highlight($color-red);
}
&.light-grey {
background-color: $color-gray-5;
}
}
a.button {
+11
View File
@@ -0,0 +1,11 @@
<div class="component component-button">
<button
type="button"
data-manipulator-target
class="{{primary ? 'primary' : ''}}"
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
></button>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+6 -9
View File
@@ -2,17 +2,14 @@
<span class="label">{{{label}}}</span>
<div class="checkbox-group">
{{each options}}
<label>
<span class="label">{{{this.label}}}</span>
<input
type="checkbox"
value="{{this.value}}"
name="clay-{{clayId}}"
data-manipulator-target
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
<label class="tap-highlight">
<span class="label">{{{this}}}</span>
<input type="checkbox" value="1" name="clay-{{clayId}}" />
<i></i>
</label>
{{/each}}
</div>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+4 -1
View File
@@ -1,5 +1,5 @@
<div class="component component-color">
<label>
<label class="tap-highlight">
<input
data-manipulator-target
type="hidden"
@@ -7,6 +7,9 @@
<span class="label">{{{label}}}</span>
<span class="value"></span>
</label>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
<div class="picker-wrap">
<div class="picker">
<div class="color-box-wrap">
+13 -7
View File
@@ -1,9 +1,15 @@
<label class="component component-input">
<span class="label">{{{label}}}</span>
<span class="input">
<input
<div class="component component-input">
<label class="tap-highlight">
<span class="label">{{{label}}}</span>
<span class="input">
<input
data-manipulator-target
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
</span>
</label>
</span>
</label>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+4 -2
View File
@@ -2,17 +2,19 @@
<span class="label">{{{label}}}</span>
<div class="radio-group">
{{each options}}
<label>
<label class="tap-highlight">
<span class="label">{{{this.label}}}</span>
<input
type="radio"
value="{{this.value}}"
name="clay-{{clayId}}"
data-manipulator-target
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
<i></i>
</label>
{{/each}}
</div>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+22 -9
View File
@@ -1,9 +1,22 @@
<label class="component component-select">
<span class="label">{{{label}}}</span>
<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>
{{/each}}
</select>
</label>
<div class="component component-select">
<label class="tap-highlight">
<span class="label">{{{label}}}</span>
<span class="value"></span>
<select data-manipulator-target {{each key: attributes}}{{key}}="{{this}}"{{/each}}>
{{each options}}
{{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>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+25
View File
@@ -0,0 +1,25 @@
<div class="component component-slider">
<label class="tap-highlight">
<span class="label-container">
<span class="label">{{{label}}}</span>
<span class="value-wrap">
<span class="value-pad"></span>
<input type="text" class="value" />
</span>
</span>
<span class="input">
<input
data-manipulator-target
class="slider"
type="range"
min="{{min}}"
max="{{max}}"
step="{{step}}"
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
</span>
</label>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+1 -1
View File
@@ -1,4 +1,4 @@
<div class="component component-button">
<div class="component component-submit">
<button
data-manipulator-target
type="submit"
+18 -13
View File
@@ -1,14 +1,19 @@
<label class="component component-toggle">
<span class="label">{{{label}}}</span>
<span class="input">
<input
data-manipulator-target
type="checkbox"
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
<span class="graphic">
<span class="slide"></span>
<span class="marker"></span>
<div class="component component-toggle">
<label class="tap-highlight">
<span class="label">{{{label}}}</span>
<span class="input">
<input
data-manipulator-target
type="checkbox"
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
/>
<span class="graphic">
<span class="slide"></span>
<span class="marker"></span>
</span>
</span>
</span>
</label>
</label>
{{if description}}
<div class="description">{{{description}}}</div>
{{/if}}
</div>
+92 -3
View File
@@ -10,6 +10,37 @@ var components = require('../src/scripts/components');
var componentRegistry = require('../src/scripts/lib/component-registry');
var idCounter = 0;
/**
* @param {Object} [extra] - add/replace keys in the meta
* @returns {{accountToken: string, watchToken: string, activeWatchInfo: {platform:
* string, model: string, language: string, firmware: {major: number, minor:
* number, patch: number, suffix: string}}, userData: {}}}
*/
module.exports.meta = function(extra) {
var result = {
accountToken: '0123456789abcdef0123456789abcdef',
watchToken: '0123456789abcdef0123456789abcdef',
activeWatchInfo: {
platform: 'chalk',
model: 'qemu_platform_chalk',
language: 'en_US',
firmware: {
major: 3,
minor: 3,
patch: 2,
suffix: ''
}
},
userData: {}
};
_.eachObj(extra || {}, function(key, val) {
result[key] = val;
});
return result;
};
/**
* @param {string|Object} config
* @param {boolean} [autoRegister=true]
@@ -22,7 +53,7 @@ module.exports.configItem = function(config, autoRegister) {
var result = _.extend({}, {
label: config.type + '-label',
appKey: 'appKey-' + idCounter,
messageKey: 'messageKey-' + idCounter,
id: 'id-' + idCounter
}, config);
@@ -64,13 +95,15 @@ module.exports.config = function(types, autoRegister) {
* @param {boolean} [build=true] - run the build method on the result
* @param {boolean} [autoRegister=true]
* @param {Object} [settings] - settings to pass to constructor
* @param {Object} [meta] - override the meta
* @returns {ClayConfig}
*/
module.exports.clayConfig = function(types, build, autoRegister, settings) {
module.exports.clayConfig = function(types, build, autoRegister, settings, meta) {
var clayConfig = new ClayConfig(
settings || {},
module.exports.config(types, autoRegister),
$(HTML('<div>'))
$(HTML('<div>')),
module.exports.meta(meta)
);
return build === false ? clayConfig : clayConfig.build();
};
@@ -82,6 +115,8 @@ module.exports.clayConfig = function(types, build, autoRegister, settings) {
* @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 {*} [options.userData={}] - Arbitrary data to pass to the config page. Will
* be available as `clayConfig.meta.userData`
* @param {boolean} [destroyLocalStorage=true]
* @return {Clay}
*/
@@ -91,3 +126,57 @@ module.exports.clay = function(config, customFn, options, destroyLocalStorage) {
}
return new Clay(config, customFn, options);
};
/**
* @param {Array} keys
* @return {Object}
*/
module.exports.messageKeys = function(keys) {
var counter = 10000;
var result = {};
keys.forEach(function(key) {
var matches = key.match(/(.+?)(?:\[(\d*)\])?$/);
var parsedKey = matches[1];
var length = parseInt(matches[2] || 1, 10);
result[parsedKey] = counter;
counter += length;
});
return result;
};
/**
* @param {Object} obj
* @returns {Array}
*/
module.exports.messageKeysObjToArray = function(obj) {
return Object.keys(obj).map(function(key) {
return Array.isArray(obj[key]) ?
key + '[' + obj[key].length + ']' :
key;
});
};
/**
* @param {Object} expected
* @return {Object}
*/
module.exports.messageKeysExpected = function(expected) {
var result = {};
var messageKeys = module.exports.messageKeysObjToArray(expected);
messageKeys = module.exports.messageKeys(messageKeys);
Object.keys(expected).forEach(function(key) {
var expectedVal = Array.isArray(expected[key]) ? expected[key] : [expected[key]];
expectedVal.forEach(function(val, index) {
if (typeof val !== 'undefined') {
result[messageKeys[key] + index] = val;
}
});
});
return result;
};
+12 -1
View File
@@ -12,6 +12,12 @@ module.exports = function(config) {
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['browserify', 'source-map-support', 'mocha'],
client: {
mocha: {
timeout: 10000 // 10 sec because Travis struggles sometimes.
}
},
browserify: {
debug: true,
transform: [
@@ -47,7 +53,12 @@ module.exports = function(config) {
]
}
]
]
],
configure: function(bundle) {
bundle.on('prebundle', function() {
bundle.ignore('message_keys');
});
}
},
// list of files / patterns to load in the browser
+3
View File
@@ -1,5 +1,8 @@
{
"env": {
"mocha": true
},
"rules": {
"max-params": [0]
}
}
+372
View File
@@ -3,6 +3,48 @@
var assert = require('chai').assert;
var fixture = require('../../fixture');
var sunlightColorMap = {
'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'
};
/* eslint-disable comma-spacing, no-multi-spaces, max-len,
standard/array-bracket-even-spacing */
var standardLayouts = {
COLOR: [
[false , false , '55ff00', 'aaff55', false , 'ffff55', 'ffffaa', false , false ],
[false , 'aaffaa', '55ff55', '00ff00', 'aaff00', 'ffff00', 'ffaa55', 'ffaaaa', false ],
['55ffaa', '00ff55', '00aa00', '55aa00', 'aaaa55', 'aaaa00', 'ffaa00', 'ff5500', 'ff5555'],
['aaffff', '00ffaa', '00aa55', '55aa55', '005500', '555500', 'aa5500', 'ff0000', 'ff0055'],
[false , '55aaaa', '00aaaa', '005555', 'ffffff', '000000', 'aa5555', 'aa0000', false ],
['55ffff', '00ffff', '00aaff', '0055aa', 'aaaaaa', '555555', '550000', 'aa0055', 'ff55aa'],
['55aaff', '0055ff', '0000ff', '0000aa', '000055', '550055', 'aa00aa', 'ff00aa', 'ffaaff'],
[false , '5555aa', '5555ff', '5500ff', '5500aa', 'aa00ff', 'ff00ff', 'ff55ff', false ],
[false , false , false , 'aaaaff', 'aa55ff', 'aa55aa', false , false , false ]
],
GRAY: [
['000000', 'aaaaaa', 'ffffff']
],
BLACK_WHITE: [
['000000', 'ffffff']
]
};
/* eslint-enable */
/**
* @param {ClayItem} colorItem
* @returns {void}
@@ -11,6 +53,182 @@ function openPicker(colorItem) {
colorItem.$element.select('label')[0].click();
}
/**
* @param {string} hex
* @returns {string} - eg: "rgb(1, 2, 3)"
*/
function hexToRgb(hex) {
var result = /^(?:#|0x)?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ?
'rgb(' +
parseInt(result[1], 16) + ', ' +
parseInt(result[2], 16) + ', ' +
parseInt(result[3], 16) + ')' :
hex;
}
/**
* @param {number|string} value
* @returns {string}
*/
function normalizeColor(value) {
switch (typeof value) {
case 'number': value = value.toString(16); break;
case 'string': value = value.replace(/^#|^0x/, ''); break;
}
return value || '000000';
}
/**
* @param {Array.<Array>} layout
* @returns {Array}
*/
function flattenLayout(layout) {
return layout.reduce(function(a, b) {
return a.concat(b);
}, []);
}
/**
* @param {Array|string} [layout=color]
* @param {Array} [expectedColors]
* @return {void}
*/
function testCustomLayout(layout, expectedColors) {
describe('layout: ' + JSON.stringify(layout || 'default'), function() {
describe('Normal', function() {
testColors(false, layout, expectedColors);
});
describe('Sunlight', function() {
testColors(true, layout, expectedColors);
});
});
}
/**
* @param {string} platform
* @param {Array} layout
* @param {boolean} allowGray
* @param {string} desc
* @param {Object} activeWatchInfo
* @return {void}
*/
function testAutoLayout(platform, layout, allowGray, desc, activeWatchInfo) {
it('chooses the best layout for the ' + (desc || platform) +
' platform when allowGray is ' + allowGray,
function() {
var clayConfig = fixture.clayConfig(
[{type: 'color', allowGray: allowGray}],
true,
true,
{},
{
activeWatchInfo: activeWatchInfo
}
);
var colorItem = clayConfig.getItemsByType('color')[0];
assert.deepEqual(colorItem._layout, layout);
});
}
/**
* @param {number|string} input
* @param {number} expected
* @param {array|string} layout
* @return {void}
*/
function testClosestToLayout(input, expected, layout) {
it('rounds ' + input + ' to ' + expected.toString(16) +
' in layout: ' + JSON.stringify(layout),
function() {
var clayConfig;
var colorItem;
clayConfig = fixture.clayConfig([{
type: 'color',
sunlight: false,
layout: layout
}]);
colorItem = clayConfig.getItemsByType('color')[0];
colorItem.set(input);
assert.strictEqual(colorItem.get(), expected);
clayConfig = fixture.clayConfig([{
type: 'color',
sunlight: false,
layout: layout
}]);
colorItem = clayConfig.getItemsByType('color')[0];
colorItem.set(input);
assert.strictEqual(colorItem.get(), expected);
});
}
/**
* @param {boolean} sunlight
* @param {Array|string} [layout=color]
* @param {Array} [expectedColors]
* @return {void}
*/
function testColors(sunlight, layout, expectedColors) {
var clayConfig;
var colorItem;
var $colorBoxes;
var colors;
beforeEach(function() {
clayConfig = fixture.clayConfig([
{
type: 'color',
layout: layout,
sunlight: sunlight
}
]);
colorItem = clayConfig.getItemsByType('color')[0];
layout = layout || 'COLOR';
if (typeof layout === 'string') {
layout = standardLayouts[layout];
}
colors = expectedColors || flattenLayout(layout);
$colorBoxes = colorItem.$element.select('.color-box');
});
it('Has the correct items in the layout', function() {
colors.forEach(function(color, index) {
if (color === false) {
assert.strictEqual($colorBoxes[index].dataset.value, undefined);
} else {
assert.strictEqual(
parseInt($colorBoxes[index].dataset.value, 10),
parseInt(color || 0, 16)
);
}
});
});
it('sets the color correctly', function() {
colors.forEach(function(color, index) {
if (color === false) {
assert.strictEqual(
$colorBoxes[index].style.backgroundColor,
'transparent'
);
} else {
var normalizedColor = normalizeColor(color);
assert.strictEqual(
$colorBoxes[index].style.backgroundColor,
hexToRgb(sunlight ? sunlightColorMap[normalizedColor] : normalizedColor)
);
}
});
});
}
describe('component - color', function() {
it('shows the sunlit colors in the value display', function() {
var clayConfig = fixture.clayConfig([
@@ -105,4 +323,158 @@ describe('component - color', function() {
$picker[0].click();
assert.strictEqual($picker.get('classList').contains('show'), false);
});
describe('color rounding', function() {
var layout = 'GRAY';
testClosestToLayout('000000', 0x000000, layout);
testClosestToLayout('005500', 0x000000, layout);
testClosestToLayout('55aa55', 0xaaaaaa, layout);
testClosestToLayout('ffaaff', 0xaaaaaa, layout);
testClosestToLayout('ffffaa', 0xffffff, layout);
testClosestToLayout('ffffff', 0xffffff, layout);
layout = 'BLACK_WHITE';
testClosestToLayout('000000', 0x000000, layout);
testClosestToLayout('005500', 0x000000, layout);
testClosestToLayout('55aa55', 0xffffff, layout);
testClosestToLayout('ffaaff', 0xffffff, layout);
testClosestToLayout('ffffaa', 0xffffff, layout);
testClosestToLayout('ffffff', 0xffffff, layout);
layout = 'COLOR';
testClosestToLayout('000000', 0x000000, layout);
testClosestToLayout('005500', 0x005500, layout);
testClosestToLayout('55bb55', 0x55aa55, layout);
testClosestToLayout('ff99ff', 0xffaaff, layout);
testClosestToLayout('ffffee', 0xffffff, layout);
testClosestToLayout('ffffff', 0xffffff, layout);
layout = ['ff0000', 'ff5500', 'ff00ff', '00ff00', '555555'];
testClosestToLayout('000000', 0x555555, layout);
testClosestToLayout('ff0000', 0xff0000, layout);
testClosestToLayout('ff1111', 0xff0000, layout);
testClosestToLayout('00aa00', 0x00ff00, layout);
testClosestToLayout('ff55ff', 0xff00ff, layout);
testClosestToLayout('aaaaaa', 0x555555, layout);
testClosestToLayout('ffffff', 0x555555, layout);
});
describe('layouts', function() {
// allow gray does nothing for 2.x
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, true, 'aplite (2.x)',
null
);
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, true, 'aplite (2.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 2,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, false, 'aplite (2.x)',
null
);
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, false, 'aplite (2.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 2,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('aplite', standardLayouts.BLACK_WHITE, false, 'aplite (3.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('aplite', standardLayouts.GRAY, true, 'aplite (3.x)', {
platform: 'aplite',
model: 'qemu_platform_aplite',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('basalt', standardLayouts.COLOR, true, '', {
platform: 'basalt',
model: 'qemu_platform_basalt',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testAutoLayout('chalk', standardLayouts.COLOR, true, '', {
platform: 'chalk',
model: 'qemu_platform_chalk',
language: 'en_US',
firmware: {
major: 3,
minor: 10,
patch: 0,
suffix: ''
}
});
testCustomLayout();
testCustomLayout('COLOR');
testCustomLayout('GRAY');
testCustomLayout('BLACK_WHITE');
testCustomLayout(['aaffaa', '55ff55', '00ff00']);
testCustomLayout([false, '55ff55', '00ff00']);
testCustomLayout([['aaffaa', '55ff55', '00ff00']]);
testCustomLayout(
[['AAFFAA', '#55ff55', '0x00ff00', 0xff5500, null]],
['aaffaa', '55ff55', '00ff00', 'ff5500', false]
);
testCustomLayout([
['aaffaa', '55ff55', '00ff00'],
['005555', 'ffffff', '000000']]
);
testCustomLayout([
['aaffaa', '55ff55', '00ff00'],
[false, 'ffffff', false]]
);
testCustomLayout(
[
['aaffaa', '55ff55', '00ff00'],
[false, 'ffffff']
],
['aaffaa', '55ff55', '00ff00', false, 'ffffff', false]
);
testCustomLayout(
[
['aaffaa', '55ff55', false],
[false, 'ffffff']
],
['aaffaa', '55ff55', false, false, 'ffffff', false]
);
testCustomLayout(
[
['aaffaa', '55ff55'],
[false, 'ffffff', '000000']
],
['aaffaa', '55ff55', false, false, 'ffffff', '000000']
);
});
});
+15
View File
@@ -7,6 +7,7 @@ var HTML = require('../../../src/scripts/vendor/minified').HTML;
var components = require('../../../src/scripts/components');
var manipulators = require('../../../src/scripts/lib/manipulators');
var fixture = require('../../fixture');
var sinon = require('sinon');
var componentSchema = Joi.object().keys({
name: Joi.string().required(),
@@ -39,6 +40,20 @@ describe('components', function() {
it('is able to be passed to ClayConfig', function() {
fixture.clayConfig([component.name]);
});
it('only has one $manipulatorTarget', function() {
var configItem = fixture.clayConfig([component.name]).getAllItems()[0];
assert.strictEqual(configItem.$manipulatorTarget.length, 1);
});
it('only dispatches change events once', function() {
var configItem = fixture.clayConfig([component.name]).getAllItems()[0];
var handler = sinon.spy();
configItem.on('change', handler);
configItem.trigger('change');
assert.strictEqual(handler.callCount, 1);
});
});
});
});
+23 -1
View File
@@ -3,7 +3,7 @@
var assert = require('chai').assert;
var fixture = require('../../fixture');
describe('component - color', function() {
describe('component - select', function() {
it('sets the value display to the correct value on change', function() {
var clayConfig = fixture.clayConfig([
{
@@ -21,4 +21,26 @@ describe('component - color', 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');
});
});
+95
View File
@@ -0,0 +1,95 @@
'use strict';
var assert = require('chai').assert;
var fixture = require('../../fixture');
describe('component - slider', function() {
it('sets the value display to the correct value on set', function() {
var clayConfig = fixture.clayConfig(['slider']);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $valueDisplayPad = sliderItem.$element.select('.value-pad');
assert.strictEqual($valueDisplay.get('value'), '50');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50');
sliderItem.set(75);
assert.strictEqual($valueDisplay.get('value'), '75');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75');
});
it('sets the value display to the correct value on slider change', function() {
var clayConfig = fixture.clayConfig(['slider']);
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');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50');
$slider.set('value', 75).trigger('change');
assert.strictEqual($valueDisplay.get('value'), '75');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75');
});
it('sets the value-pad to the same value as what the user is typing', function() {
var clayConfig = fixture.clayConfig(['slider']);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $valueDisplayPad = sliderItem.$element.select('.value-pad');
assert.strictEqual($valueDisplay.get('value'), '50');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '50');
$valueDisplay.set('value', 75).trigger('input');
assert.strictEqual($valueDisplay.get('value'), '75');
assert.strictEqual($valueDisplayPad.get('innerHTML'), '75');
});
it('sets the slider to the correct value on user input', function() {
var clayConfig = fixture.clayConfig(['slider']);
var sliderItem = clayConfig.getItemsByType('slider')[0];
var $valueDisplay = sliderItem.$element.select('.value');
var $slider = sliderItem.$element.select('.slider');
assert.strictEqual($valueDisplay.get('value'), '50');
assert.strictEqual($slider.get('value'), '50');
$valueDisplay.set('value', 75).trigger('change');
assert.strictEqual($slider.get('value'), '75');
});
it('sets the precision correctly', function() {
var clayConfig = fixture.clayConfig([{
type: 'slider',
step: 0.25
}]);
var sliderItem = clayConfig.getItemsByType('slider')[0];
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');
});
});
+419 -54
View File
@@ -5,18 +5,42 @@ var Clay = require('../../index');
var assert = require('chai').assert;
var standardComponents = require('../../src/scripts/components');
var sinon = require('sinon');
var toSource = require('tosource');
/**
* @return {void}
*/
function stubPebble() {
var meta = fixture.meta();
global.Pebble = {
addEventListener: sinon.stub(),
openURL: sinon.stub(),
sendAppMessage: sinon.stub()
sendAppMessage: sinon.stub(),
getActiveWatchInfo: sinon.stub().returns(meta.activeWatchInfo),
getAccountToken: sinon.stub().returns(meta.accountToken),
getWatchToken: sinon.stub().returns(meta.watchToken)
};
}
/**
* @param {Array} keys
* @return {Object}
*/
function stubMessageKeys(keys) {
var messageKeys = require('message_keys');
Object.keys(messageKeys).forEach(function(key) {
delete messageKeys[key];
});
var newKeys = fixture.messageKeys(keys);
Object.keys(newKeys).forEach(function(key) {
messageKeys[key] = newKeys[key];
});
return messageKeys;
}
describe('Clay', function() {
describe('Clay constructor', function() {
it('throws if the config is not an array', function() {
@@ -25,6 +49,15 @@ describe('Clay', function() {
}, /must be an Array/i);
});
it('throws if the config contains appKeys', function() {
assert.throws(function() {
fixture.clay([
{type: 'input', appKey: 'foo'},
{type: 'someCustomComponent', appKey: 'bar'}
]);
}, /appKeys are no longer supported/i);
});
it('throws if customFn is not a function', function() {
assert.throws(function() {
fixture.clay([], {});
@@ -58,10 +91,15 @@ describe('Clay', function() {
function() {
stubPebble();
var clay = fixture.clay([]);
// we stub the generateUrl method to avoid very large string comparisons.
var generateUrlStub = sinon.stub(clay, 'generateUrl');
generateUrlStub.returns('data:text/html;base64,PGh0bWw%2BVEVTVDwvaHRtbD4%3D');
Pebble.addEventListener.withArgs('showConfiguration').callArg(1);
assert(Pebble.addEventListener.calledWith('showConfiguration'));
assert(Pebble.openURL.calledWith(clay.generateUrl()));
generateUrlStub.restore();
});
it('handles the "webviewclosed" event if autoHandleEvents is not false',
@@ -69,12 +107,16 @@ describe('Clay', function() {
stubPebble();
fixture.clay([]);
var logStub = sinon.stub(console, 'log');
var expected = {someSetting: 'value'};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
Pebble.addEventListener
.withArgs('webviewclosed')
.callArgWith(1, { response: '%7B%22appKey%22%3A%22value%22%7D' });
.callArgWith(1, {response: encodeURIComponent(JSON.stringify(expected))});
var expectedAppMessage = fixture.messageKeysExpected(expected);
assert(Pebble.addEventListener.calledWith('webviewclosed'));
assert(Pebble.sendAppMessage.calledWith({ appKey: 'value' }));
assert(Pebble.sendAppMessage.calledWith(expectedAppMessage));
Pebble.sendAppMessage.callArg(1);
assert(logStub.calledWith('Sent config data to Pebble'));
@@ -99,7 +141,31 @@ describe('Clay', function() {
'if autoHandleEvents is false', function() {
stubPebble();
fixture.clay([], null, { autoHandleEvents: false });
assert.strictEqual(Pebble.addEventListener.called, false);
assert.strictEqual(
Pebble.addEventListener.withArgs('webviewclosed').called,
false
);
assert.strictEqual(
Pebble.addEventListener.withArgs('showConfiguration').called,
false
);
});
});
describe('.version', function() {
it('has the correct version', function() {
var config = fixture.config(['input', 'text', 'color']);
var clay = fixture.clay(config);
assert.strictEqual(clay.version, require('../../package.json').version);
});
});
describe('.config', function() {
it('is a copy not a reference', function() {
var config = fixture.config(['input', 'text', 'color']);
var clay = fixture.clay(config);
assert.notStrictEqual(clay.config, config);
assert.deepEqual(clay.config, config);
});
});
@@ -117,13 +183,67 @@ describe('Clay', function() {
});
describe('.generateUrl()', function() {
/**
* Decode the generated data URI into just the HTML portion
* @param {string} url
* @returns {string}
*/
function decodeUrl(url) {
return decodeURIComponent(url.replace(/^.*?[#,]/, ''));
}
describe('string substitutions', function() {
var customFn = function() { this.getAllItems(); };
var config = fixture.config(['input', 'color']);
var settings = { messageKey: 'value' };
var clay;
var html;
before(function() {
stubPebble();
clay = fixture.clay(config, customFn);
Pebble.addEventListener.withArgs('showConfiguration').callArg(1);
localStorage.setItem('clay-settings', JSON.stringify(settings));
html = decodeUrl(clay.generateUrl());
});
it('Substitutes $$RETURN_TO$$ with the pebblejs://close#', function() {
assert.notInclude(html, '$$RETURN_TO$$');
assert.include(html, 'window.returnTo="pebblejs://close#"');
});
it('Substitutes $$CUSTOM_FN$$ with the customFn', function() {
assert.notInclude(html, '$$CUSTOM_FN$$');
assert.include(html, 'window.customFn=' + toSource(customFn));
});
it('Substitutes $$CONFIG$$ with the config', function() {
assert.notInclude(html, '$$CONFIG$$');
assert.include(html, 'window.clayConfig=' + toSource(config));
});
it('Substitutes $$SETTINGS$$ with the config', function() {
assert.notInclude(html, '$$SETTINGS$$');
assert.include(html, 'window.claySettings=' + toSource(settings));
});
it('Substitutes $$COMPONENTS$$ with the config', function() {
assert.notInclude(html, '$$COMPONENTS$$');
assert.include(html, 'window.clayComponents=' + toSource(clay.components));
});
it('Substitutes $$META$$ with the config', function() {
assert.notInclude(html, '$$META$$');
assert.include(html, 'window.clayMeta=' + toSource(clay.meta));
});
});
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\$\$/);
assert.match(decodeUrl(clay.generateUrl()), /\$\$RETURN_TO\$\$/);
});
it('returns the emulator URL if inside emulator', function() {
@@ -149,71 +269,316 @@ describe('Clay', function() {
});
});
describe('.getSettings()', function() {
it('stores the response to localStorage and returns the decoded data',
describe('.getSettings', function() {
it('it writes to localStorage and returns the data when input is encoded',
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'});
var settings = encodeURIComponent(JSON.stringify({
key1: 'value1',
key2: {value: 'value2'}
}));
var expected = {
key1: 'value1',
key2: 'value2'
};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
var result = clay.getSettings(settings);
assert.equal(
localStorage.getItem('clay-settings'),
JSON.stringify(expected)
);
assert.deepEqual(result, fixture.messageKeysExpected(expected));
});
it('it writes to localStorage and returns the data when input is not encoded',
function() {
var clay = fixture.clay([]);
var settings = JSON.stringify({
key1: 'value1',
key2: {value: 'value2%7Dbreaks'}
});
var expected = {
key1: 'value1',
key2: 'value2%7Dbreaks'
};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
var result = clay.getSettings(settings);
assert.equal(
localStorage.getItem('clay-settings'),
JSON.stringify(expected)
);
assert.deepEqual(result, fixture.messageKeysExpected(expected));
});
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"}');
localStorage.setItem('clay-settings', '{"messageKey":"value"}');
assert.throws(function() {
clay.getSettings('not valid JSON');
}, /Not Valid JSON/i);
assert.equal(localStorage.getItem('clay-settings'), '{"appKey":"value"}');
assert.equal(localStorage.getItem('clay-settings'), '{"messageKey":"value"}');
});
it('Prepares the settings for sendAppMessage', function() {
var clay = fixture.clay([]);
var response = encodeURIComponent(JSON.stringify({
test1: false,
test2: 'val-2',
test3: true,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [true, false, true],
test8: {
precision: 2,
value: 12.34
},
test9: {
precision: 1,
value: [1, 2, 3, 4]
}
}));
var expected = {
test1: 0,
test2: 'val-2',
test3: 1,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [1, 0, 1],
test8: 1234,
test9: [10, 20, 30, 40]
};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
assert.deepEqual(
clay.getSettings(response),
fixture.messageKeysExpected(expected)
);
});
it('does not prepare the settings for sendAppMessage if convert is false',
function() {
var clay = fixture.clay([]);
var settings = {
test1: false,
test2: 'val-2',
test3: true,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [true, false, true],
test8: {
precision: 2,
value: 12.34
}
};
var response = encodeURIComponent(JSON.stringify(settings));
assert.deepEqual(clay.getSettings(response, false), settings);
});
});
describe('.meta', function() {
var emptyMeta = {
activeWatchInfo: null,
accountToken: '',
watchToken: '',
userData: {}
};
it('populates the meta in the showConfiguration handler', function() {
stubPebble();
var userData = {foo: 'bar'};
var clay = fixture.clay([], null, {userData: userData});
// meta only gets populated after showConfiguration happens
assert.deepEqual(clay.meta, emptyMeta);
Pebble.addEventListener.withArgs('showConfiguration').callArg(1);
assert.deepEqual(clay.meta, fixture.meta({userData: userData}));
});
it('populates the meta in the ready handler', function() {
stubPebble();
var userData = {foo: 'bar'};
var clay = fixture.clay([], null, {
autoHandleEvents: false,
userData: userData
});
// meta only gets populated after ready happens
assert.deepEqual(clay.meta, emptyMeta);
Pebble.addEventListener.withArgs('ready').callArg(1);
assert.deepEqual(clay.meta, fixture.meta({userData: userData}));
});
it('populates the meta with with empty values when there is no Pebble global',
function() {
delete global.Pebble;
var clay = fixture.clay([], null, {autoHandleEvents: false});
assert.deepEqual(clay.meta, emptyMeta);
});
});
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();
it('adds the correct prefix', function() {
assert.equal(Clay.encodeDataUri('<test>', 'prefix:'), 'prefix:%3Ctest%3E');
assert.equal(
Clay.encodeDataUri('<test>'),
'data:text/html;charset=utf-8,%3Ctest%3E'
);
});
describe('polyfill', function() {
var btoaOriginal = window.btoa;
before(function() {
window.btoa = undefined;
});
testEncodeDataUri();
after(function() {
window.btoa = btoaOriginal;
});
it('encodes the data correctly', function() {
assert.equal(Clay.encodeDataUri('test', ''), 'test');
assert.equal(Clay.encodeDataUri('test{2}', ''), 'test%7B2%7D');
assert.equal(Clay.encodeDataUri('test{10}', ''), 'test%7B10%7D');
});
it('does not throw if unicode character is provided', function() {
assert.doesNotThrow(function() {
Clay.encodeDataUri('♥');
});
});
});
describe('.prepareForAppMessage', function() {
it('converts an array correctly when array contains strings', function() {
assert.deepEqual(
Clay.prepareForAppMessage(['one', 'two', 'three']),
['one', 'two', 'three']
);
assert.deepEqual(
Clay.prepareForAppMessage([{value: 'one'}, 'two', 'three']),
['one', 'two', 'three']);
});
it('converts an array correctly when array contains numbers', function() {
assert.deepEqual(Clay.prepareForAppMessage([1, 2, 3]), [1, 2, 3]);
assert.deepEqual(Clay.prepareForAppMessage(
{value: [1.5, 2.34, 3], precision: 2}),
[150, 234, 300]
);
});
it('converts an array correctly when array contains booleans', function() {
assert.deepEqual(Clay.prepareForAppMessage([true, false, true]), [1, 0, 1]);
assert.deepEqual(
Clay.prepareForAppMessage([{value: true}, false, true]),
[1, 0, 1]);
});
it('converts booleans to ints', function() {
assert.strictEqual(Clay.prepareForAppMessage(false), 0);
assert.strictEqual(Clay.prepareForAppMessage(true), 1);
assert.strictEqual(Clay.prepareForAppMessage({value: false}), 0);
assert.strictEqual(Clay.prepareForAppMessage({value: true}), 1);
});
it('leaves strings alone', function() {
assert.strictEqual(Clay.prepareForAppMessage('test'), 'test');
assert.strictEqual(Clay.prepareForAppMessage({value: 'test'}), 'test');
});
it('Multiples the number by the precision', function() {
assert.strictEqual(Clay.prepareForAppMessage(123), 123);
assert.strictEqual(Clay.prepareForAppMessage({
value: 1.23,
precision: 3
}), 1230);
assert.strictEqual(Clay.prepareForAppMessage({
value: 123,
precision: 2
}), 12300);
assert.strictEqual(Clay.prepareForAppMessage({
value: 1.23456,
precision: 4
}), 12345);
});
it('ignores precision for anything but numbers', function() {
assert.strictEqual(Clay.prepareForAppMessage({
value: 'not a number',
precision: 3
}), 'not a number');
assert.deepEqual(Clay.prepareForAppMessage({
value: ['not', 'a', 'number'],
precision: 3
}), ['not', 'a', 'number']);
});
it('treats an undefined precision as 0', function() {
assert.strictEqual(Clay.prepareForAppMessage({
value: 12.45,
precision: undefined
}), 12);
});
it('Handles sparse arrays', function() {
var sparseArr = [];
sparseArr[1] = 'two';
sparseArr[2] = 'three';
assert.deepEqual(Clay.prepareForAppMessage(sparseArr), sparseArr);
});
});
describe('.prepareSettingsForAppMessage', function() {
it('converts the settings correctly', function() {
var settings = {
test1: false,
'test2[0]': 'val-1',
'test2[1]': 'val-2',
test3: true,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [true, false, true],
test8: {
precision: 2,
value: 12.34
},
'test9[1]': 'foo'
};
var expected = {
test1: 0,
test2: ['val-1', 'val-2'],
test3: 1,
test4: ['cb-1', 'cb-3'],
test5: 12345,
test6: [1, 2, 3, 4],
test7: [1, 0, 1],
test8: 1234,
test9: [undefined, 'foo']
};
stubMessageKeys(fixture.messageKeysObjToArray(expected));
var result = Clay.prepareSettingsForAppMessage(settings);
assert.deepEqual(result, fixture.messageKeysExpected(expected));
});
it('throws if a 2 dimension array is present', function() {
var settings = {
'test1[1]': ['bad', 'developer!']
};
stubMessageKeys(['test1[2]']);
assert.throws(function() {
Clay.prepareSettingsForAppMessage(settings);
}, /2 dimensional array/i);
});
});
});
+338 -33
View File
@@ -12,26 +12,219 @@ describe('ClayConfig', function() {
it('defines read-only properties', function() {
var properties = [
'EVENTS',
'getItemByAppKey',
'getItemByMessageKey',
'getItemById',
'getItemsByType',
'getSettings',
'serialize',
'registerComponent',
'build',
'on',
'off',
'trigger'
'trigger',
'meta'
];
var clayConfig = fixtures.clayConfig(['input']);
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
* @param {number} [notExpected] - defaults to the inverse of `expected`
* @return {void}
*/
function testCapabilities(platform, fwMajor, fwMinor, capabilities, expected,
notExpected) {
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);
// now we test the NOT_ prefix
item.capabilities = item.capabilities.map(function(capability) {
return 'NOT_' + capability.replace(/^NOT_/, '');
});
notExpected = typeof notExpected !== 'undefined' ? notExpected : ~expected + 2;
clayConfig = fixtures.clayConfig([item], true, true, null, meta);
assert.strictEqual(clayConfig.getAllItems().length, notExpected);
});
}
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, ['PLATFORM_APLITE'], 1);
testCapabilities('aplite', 2, 9, ['PLATFORM_BASALT'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_CHALK'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_DIORITE'], 0);
testCapabilities('aplite', 2, 9, ['PLATFORM_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, ['PLATFORM_APLITE'], 1);
testCapabilities('aplite', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_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, ['PLATFORM_APLITE'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_BASALT'], 1);
testCapabilities('basalt', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('basalt', 3, 10, ['PLATFORM_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, ['PLATFORM_APLITE'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_CHALK'], 1);
testCapabilities('chalk', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('chalk', 3, 10, ['PLATFORM_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, ['PLATFORM_APLITE'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('diorite', 3, 10, ['PLATFORM_DIORITE'], 1);
testCapabilities('diorite', 3, 10, ['PLATFORM_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, ['PLATFORM_APLITE'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_BASALT'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_CHALK'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_DIORITE'], 0);
testCapabilities('emery', 3, 10, ['PLATFORM_EMERY'], 1);
testCapabilities('aplite', 3, 10, ['BW', 'HEALTH'], 0, 0);
testCapabilities('emery', 3, 10, ['COLOR', 'HEALTH'], 1);
testCapabilities('emery', 3, 10, ['MICROPHONE'], 1);
testCapabilities('chalk', 3, 10, ['PLATFORM_APLITE', 'PLATFORM_BASALT'], 0);
testCapabilities('basalt', 3, 10, ['SMARTSTRAP', 'SMARTSTRAP_POWER'], 1);
testCapabilities('aplite', 3, 10, ['COLOR', 'PLATFORM_APLITE'], 0, 0);
testCapabilities('aplite', 3, 10, ['PLATFORM_APLITE', 'COLOR'], 0, 0);
testCapabilities('aplite', 3, 10, ['BW', 'NOT_COLOR'], 1, 0);
testCapabilities('aplite', 3, 10, ['NOT_BASALT', 'RECT'], 1, 0);
testCapabilities('aplite', 3, 10, ['NOT_CHALK', 'BW', 'PLATFORM_APLITE'], 1, 0);
});
describe('.meta', function() {
it('populates meta', function() {
var clayConfig = fixtures.clayConfig(['input']);
assert.deepEqual(clayConfig.meta, fixtures.meta());
});
});
describe('throws when trying to run methods before being built', function() {
[
'getItemByAppKey',
'getItemByMessageKey',
'getItemById',
'getItemsByType',
'getSettings'
'serialize'
].forEach(function(method) {
it('.' + method + '()', function() {
var clayConfig = fixtures.clayConfig(['input', 'text'], false);
@@ -56,14 +249,17 @@ describe('ClayConfig', function() {
});
});
describe('.getItemByAppKey()', function() {
describe('.getItemByMessageKey()', function() {
it('it returns the correct item', function() {
var config = fixtures.config([
{type: 'input', appKey: 'test-app-key', clayId: 0},
{type: 'input', appKey: undefined, clayId: 1}
{type: 'input', messageKey: 'test-app-key', clayId: 0},
{type: 'input', messageKey: undefined, clayId: 1}
]);
var clayConfig = fixtures.clayConfig(config);
assert.deepEqual(clayConfig.getItemByAppKey('test-app-key').config, config[0]);
assert.deepEqual(
clayConfig.getItemByMessageKey('test-app-key').config,
config[0]
);
});
});
@@ -91,37 +287,84 @@ describe('ClayConfig', function() {
});
});
describe('.getSettings()', function() {
describe('.serialize()', function() {
it('returns the correct settings', function() {
var clayConfig = fixtures.clayConfig(
[
{type: 'input', appKey: 'test1', defaultValue: 'default val'},
{type: 'select', appKey: 'test2', options: [
{label: 'label-1', value: 'val-1'},
{label: 'label-2', value: 'val-2'}
]},
{type: 'toggle', appKey: 'test3'}
],
true,
true,
{
test2: 'val-2' // set one of the values via settings
}
);
var config = [
{type: 'input', messageKey: 'test1', defaultValue: 'default val'},
{type: 'select', messageKey: 'test2', options: [
{label: 'label-1', value: 'val-1'},
{label: 'label-2', value: 'val-2'}
]},
{type: 'toggle', messageKey: 'test3'},
{type: 'checkboxgroup', messageKey: 'test4', options: [
'label-1',
'label-2',
'label-3'
]},
{type: 'slider', messageKey: 'test5', step: 0.05, defaultValue: 12.5}
];
var settings = {
test2: 'val-2'
};
assert.deepEqual(clayConfig.getSettings(), {
test1: 'default val',
test2: 'val-2',
test3: 0
var clayConfig = fixtures.clayConfig(config, true, true, settings);
assert.deepEqual(clayConfig.serialize(), {
test1: {value: 'default val'},
test2: {value: 'val-2'},
test3: {value: false},
test4: {value: [false, false, false]},
test5: {value: 12.5, precision: 2}
});
clayConfig.getItemByAppKey('test1').set('val-1');
clayConfig.getItemByAppKey('test3').set(true);
clayConfig.getItemByMessageKey('test1').set('val-1');
clayConfig.getItemByMessageKey('test3').set(true);
clayConfig.getItemByMessageKey('test4').set([true, false, true]);
assert.deepEqual(clayConfig.getSettings(), {
assert.deepEqual(clayConfig.serialize(), {
test1: {value: 'val-1'},
test2: {value: 'val-2'},
test3: {value: true},
test4: {value: [true, false, true]},
test5: {value: 12.5, precision: 2}
});
// make sure the result of serialize() can actually be fed back in to
// a new instance of ClayConfig
var clay = fixtures.clay(config);
var response = encodeURIComponent(JSON.stringify(clayConfig.serialize()));
clay.getSettings(response);
var loadedSettings = JSON.parse(localStorage.getItem('clay-settings'));
assert.deepEqual(loadedSettings, {
test1: 'val-1',
test2: 'val-2',
test3: 1
test3: true,
test4: [true, false, true],
test5: 12.5
});
assert.doesNotThrow(function() {
fixtures.clayConfig(config, true, true, loadedSettings);
});
});
it('only returns the settings present in the config', function() {
var config = [
{type: 'input', messageKey: 'test1', defaultValue: 'default val'},
{type: 'select', messageKey: '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'}
});
});
});
@@ -219,7 +462,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() {
+13 -11
View File
@@ -12,7 +12,7 @@ describe('ClayItem', function() {
it('defines read-only properties', function() {
var properties = [
'id',
'appKey',
'messageKey',
'config',
'$element',
'$manipulatorTarget',
@@ -53,31 +53,32 @@ describe('ClayItem', function() {
});
});
describe('.appKey', function() {
it('sets appKey correctly', function() {
describe('.messageKey', function() {
it('sets messageKey correctly', function() {
var config = fixture.configItem('input');
var clayItem = new ClayItem(config);
assert.strictEqual(clayItem.appKey, config.appKey);
assert.strictEqual(clayItem.messageKey, config.messageKey);
});
it('sets appKey to null if there is no appKey in the config', function() {
var clayItem = fixture.clayItem({type: 'input', appKey: undefined});
assert.strictEqual(clayItem.appKey, null);
it('sets messageKey to null if there is no messageKey in the config',
function() {
var clayItem = fixture.clayItem({type: 'input', messageKey: undefined});
assert.strictEqual(clayItem.messageKey, null);
});
});
describe('.config', function() {
it('sets appKey correctly', function() {
it('sets messageKey correctly', function() {
var config = fixture.configItem('input');
var clayItem = new ClayItem(config);
assert.strictEqual(clayItem.appKey, config.appKey);
assert.strictEqual(clayItem.messageKey, config.messageKey);
});
});
describe('.$element', function() {
it('sets $element correctly', function() {
var clayItem = fixture.clayItem('input');
assert.strictEqual(clayItem.$element[0].tagName, 'LABEL');
assert.strictEqual(clayItem.$element[0].classList.contains('component'), true);
});
});
@@ -104,7 +105,8 @@ describe('ClayItem', function() {
it('returns itself for chaining', function() {
var clayItem = fixture.clayItem('select');
assert.strictEqual(clayItem.initialize(), clayItem);
var clayConfig = fixture.clayConfig([]);
assert.strictEqual(clayItem.initialize(clayConfig), clayItem);
});
it('does nothing if there is no initialize function', function() {
+152 -42
View File
@@ -7,7 +7,7 @@ var fixture = require('../../fixture');
describe('manipulators', function() {
/**
* @param {string|Clay~ConfigItem} itemType
* @param {string|Object} itemType
* @param {*} value
* @param {*} [expected]
* @return {void}
@@ -19,9 +19,10 @@ describe('manipulators', function() {
it('sets: "' + value + '" and gets: "' + expected + '" then triggers "change"',
function() {
var handlerSpy = sinon.spy();
var clayItem = fixture.clayItem(itemType);
var clayItem = fixture.clayConfig([itemType]).getAllItems()[0];
clayItem.on('change', handlerSpy);
clayItem.set(value);
clayItem.set(value);
assert.deepEqual(clayItem.get(), expected);
assert.strictEqual(handlerSpy.callCount, 1, 'handler not called once');
@@ -31,7 +32,7 @@ describe('manipulators', function() {
}
/**
* @param {string|Clay~ConfigItem} itemType
* @param {string|Object} itemType
* @return {void}
*/
function testDisable(itemType) {
@@ -45,6 +46,7 @@ describe('manipulators', function() {
false
);
clayItem.disable();
clayItem.disable();
assert.strictEqual(
clayItem.$element[0].classList.contains('disabled'),
true
@@ -57,7 +59,7 @@ describe('manipulators', function() {
}
/**
* @param {string} itemType
* @param {string|Object} itemType
* @return {void}
*/
function testEnable(itemType) {
@@ -73,6 +75,7 @@ describe('manipulators', function() {
true
);
clayItem.enable();
clayItem.enable();
assert.strictEqual(
clayItem.$element[0].classList.contains('disabled'),
false
@@ -84,27 +87,123 @@ describe('manipulators', function() {
});
}
/**
* @param {string|Object} itemType
* @return {void}
*/
function testHide(itemType) {
describe('.hide()', function() {
it('hides the field then triggers a "hide" event', function() {
var handlerSpy = sinon.spy();
var clayItem = fixture.clayItem(itemType);
clayItem.on('hide', handlerSpy);
assert.strictEqual(
clayItem.$element[0].classList.contains('hide'),
false
);
clayItem.hide();
clayItem.hide();
assert.strictEqual(
clayItem.$element[0].classList.contains('hide'),
true
);
assert.strictEqual(handlerSpy.callCount, 1, 'handler not called once');
assert(handlerSpy.calledOn(clayItem), 'handler not called on clayItem');
});
});
}
/**
* @param {string|Object} itemType
* @return {void}
*/
function testShow(itemType) {
describe('.show()', function() {
it('shows the field then triggers a "show" event', function() {
var handlerSpy = sinon.spy();
var clayItem = fixture.clayItem(itemType);
clayItem.on('show', handlerSpy);
clayItem.hide();
assert.strictEqual(
clayItem.$element[0].classList.contains('hide'),
true
);
clayItem.show();
clayItem.show();
assert.strictEqual(
clayItem.$element[0].classList.contains('hide'),
false
);
assert.strictEqual(handlerSpy.callCount, 1, 'handler not called once');
assert(handlerSpy.calledOn(clayItem), 'handler not called on clayItem');
});
});
}
describe('html', function() {
testSetGet('footer', 'test123');
var type = 'text';
testSetGet(type, 'test123');
testSetGet(type, '<span>some HTML</span>');
testShow(type);
testHide(type);
});
describe('button', function() {
var type = 'button';
testSetGet(type, 'test123');
testSetGet(type, '<span>some HTML</span>');
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('val', function() {
testSetGet('input', 'test321');
testDisable('input');
testEnable('input');
var type = 'input';
testSetGet(type, 'test321');
testSetGet(type, 1234, '1234');
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('slider', function() {
var type = {
type: 'slider',
min: 0,
max: 100,
step: 0.1
};
testSetGet(type, '12', 12);
testSetGet(type, 12);
testSetGet(type, '12.3', 12.3);
testSetGet(type, 12.3);
testSetGet(type, 12.34, 12.3);
testSetGet(type, '12.34', 12.3);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('checked', function() {
testSetGet('toggle', true, 1);
testSetGet('toggle', 1);
testSetGet('toggle', false, 0);
testSetGet('toggle', 0);
testDisable('toggle');
testEnable('toggle');
var type = 'toggle';
testSetGet({type: type, defaultValue: false}, 1, true);
testSetGet({type: type, defaultValue: false}, true);
testSetGet({type: type, defaultValue: true}, 0, false);
testSetGet({type: type, defaultValue: true}, false);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('radiogroup', function() {
var item = {
var type = {
type: 'radiogroup',
clayId: 1,
options: [
@@ -113,41 +212,52 @@ describe('manipulators', function() {
{ label: '3', value: 'three "quote' }
]
};
testSetGet(item, 'one');
testSetGet(item, 'two');
testSetGet(item, 'three "quote');
testDisable(item);
testEnable(item);
testSetGet(type, 'one');
testSetGet(type, 'two');
testSetGet(type, 'three "quote');
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('checkboxgroup', function() {
var item = {
var type = {
type: 'checkboxgroup',
clayId: 1,
options: [
{ label: '1', value: 'one' },
{ label: '2', value: 'two' },
{ label: '3', value: 'three "quote' }
]
defaultValue: [true, true, true],
options: ['First', 'Second', 'Third']
};
testSetGet(item, ['one', 'two']);
testSetGet(item, ['three "quote']);
testSetGet(item, []);
testSetGet(item, false, []);
testDisable(item);
testEnable(item);
testSetGet(type, [false, false, true]);
testSetGet(type, [true, false], [true, false, false]);
testSetGet(type, [1, 0], [true, false, false]);
testSetGet(type, [true], [true, false, false]);
testSetGet(type, [1], [true, false, false]);
testSetGet(type, [], [false, false, false]);
// any non-array values should result in all false
testSetGet(type, false, [false, false, false]);
testSetGet(type, true, [false, false, false]);
testSetGet(type, null, [false, false, false]);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
describe('color', function() {
testSetGet('color', 'FF0000', 0xff0000);
testSetGet('color', '#FF0000', 0xff0000);
testSetGet('color', '0xFF0000', 0xff0000);
testSetGet('color', '#ff0000', 0xff0000);
testSetGet('color', 0xff0000, 0xff0000);
testSetGet('color', '', 0x000000);
testSetGet('color', false, 0x000000);
testSetGet('color', undefined, 0x000000);
testDisable('color');
testEnable('color');
var type = 'color';
testSetGet(type, 'FF0000', 0xff0000);
testSetGet(type, '#FF0000', 0xff0000);
testSetGet(type, '0xFF0000', 0xff0000);
testSetGet(type, '#ff0000', 0xff0000);
testSetGet(type, 0xff0000, 0xff0000);
testSetGet({type: type, defaultValue: 0x00ff00}, '', 0x000000);
testSetGet({type: type, defaultValue: 0x00ff00}, false, 0x000000);
testSetGet({type: type, defaultValue: 0x00ff00}, undefined, 0x000000);
testDisable(type);
testEnable(type);
testShow(type);
testHide(type);
});
});
+1
View File
@@ -27,3 +27,4 @@ describe('Utils', function() {
});
});
});
+48
View File
@@ -0,0 +1,48 @@
#
# This file is the default set of rules to compile a Pebble project.
#
# Feel free to customize this to your needs.
#
import os
import shutil
import waflib
top = '.'
out = 'build'
def distclean(ctx):
if os.path.exists('dist.zip'):
os.remove('dist.zip')
if os.path.exists('dist'):
shutil.rmtree('dist')
waflib.Scripting.distclean(ctx)
def options(ctx):
ctx.load('pebble_sdk_lib')
def configure(ctx):
ctx.load('pebble_sdk_lib')
def build(ctx):
ctx.load('pebble_sdk_lib')
cached_env = ctx.env
for platform in ctx.env.TARGET_PLATFORMS:
ctx.env = ctx.all_envs[platform]
ctx.set_group(ctx.env.PLATFORM_NAME)
lib_name = '{}/{}'.format(ctx.env.BUILD_DIR, ctx.env.PROJECT_INFO['name'])
ctx.pbl_build(source=ctx.path.ant_glob('src/c/**/*.c'), target=lib_name, bin_type='lib')
ctx.env = cached_env
ctx.set_group('bundle')
ctx.pbl_bundle(includes=ctx.path.ant_glob('include/**/*.h'),
js=ctx.path.ant_glob(['src/js/**/*.js', 'src/js/**/*.json']),
bin_type='lib')
if ctx.cmd == 'clean':
for n in ctx.path.ant_glob(['dist/**/*', 'dist.zip'], quiet=True):
n.delete()