mirror of
https://github.com/pebble-dev/clay.git
synced 2026-08-29 13:26:59 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c1bcbdb7d | ||
|
|
c5e29ce638 | ||
|
|
7512b6436b | ||
|
|
1b362a366c | ||
|
|
1a8301b0cc | ||
|
|
79c64e5777 | ||
|
|
8ba0a2caee | ||
|
|
904d6bca55 | ||
|
|
94f4770731 | ||
|
|
fa0e243bf1 | ||
|
|
c7bb67f2ef | ||
|
|
2d1a355b23 | ||
|
|
b606e8f87a | ||
|
|
98fc4a41eb | ||
|
|
7836694b53 | ||
|
|
0866527ea0 | ||
|
|
c1b87080f1 | ||
|
|
b2e9ed2392 | ||
|
|
75548a1c04 | ||
|
|
900b03de1d | ||
|
|
cc0e88598f | ||
|
|
078186cd25 | ||
|
|
b55a4c6c61 | ||
|
|
4dad63ea90 | ||
|
|
6335169e9a | ||
|
|
1bf6db0809 | ||
|
|
4b53d920c7 | ||
|
|
fa3406a809 | ||
|
|
46c0adb62c | ||
|
|
d18170337d | ||
|
|
9c7238d578 | ||
|
|
1a63761e43 | ||
|
|
9c6bb48c7a | ||
|
|
2173ad7c71 | ||
|
|
b8db6c6a90 | ||
|
|
62f0337198 | ||
|
|
eb89496733 | ||
|
|
5d6e070e01 | ||
|
|
ae31f9737f | ||
|
|
6e42537aa1 | ||
|
|
2b14076171 | ||
|
|
55ac8681af | ||
|
|
b5d2615307 | ||
|
|
6742ec84cf | ||
|
|
61d735662e | ||
|
|
050b89f82f | ||
|
|
1b179a603a | ||
|
|
4a6382aa7f | ||
|
|
f0c44c5719 | ||
|
|
2c2076bf69 | ||
|
|
5a67fec99e | ||
|
|
7cadd5fba4 | ||
|
|
6b59844084 | ||
|
|
b3963179d1 | ||
|
|
6a63c40e08 | ||
|
|
f91c525a30 | ||
|
|
19779a627f | ||
|
|
b19ad435df | ||
|
|
5627f789a3 | ||
|
|
1c9a812009 | ||
|
|
939398774d | ||
|
|
361dc99ea5 | ||
|
|
9f2a89a41e | ||
|
|
26df41b25e | ||
|
|
dd6fe54834 | ||
|
|
3cffa3a275 | ||
|
|
a0335be710 | ||
|
|
cabee3ae0b | ||
|
|
940c5e2451 | ||
|
|
870436ddd8 | ||
|
|
c3c8de77fc | ||
|
|
87807f180c | ||
|
|
9e344d527b | ||
|
|
2f9e36bdb2 | ||
|
|
5038e80bf2 | ||
|
|
7a2dbb276f | ||
|
|
3300e98ce2 | ||
|
|
693b795c2b | ||
|
|
ef8ab37eb4 | ||
|
|
118df1d952 | ||
|
|
8d4ff52b0f | ||
|
|
613c41fd6e | ||
|
|
ecac278f63 | ||
|
|
742890ee31 | ||
|
|
f08f11fb6f | ||
|
|
859e2f2767 | ||
|
|
19ad09eb66 | ||
|
|
39056a2868 | ||
|
|
8a2d560482 | ||
|
|
f62f960433 | ||
|
|
8fb67ce08d | ||
|
|
65b952d7f8 | ||
|
|
6c2a335b7c | ||
|
|
f717661eea | ||
|
|
35bdeb7519 | ||
|
|
93b927db63 | ||
|
|
9afdd2adb9 | ||
|
|
5fe87a04de | ||
|
|
7e2b75dd58 | ||
|
|
32e192e938 | ||
|
|
1af2b39db1 | ||
|
|
782eaf82f3 | ||
|
|
41d38e04a7 | ||
|
|
1611599a25 | ||
|
|
842cbc9e84 | ||
|
|
238d0e6a6c | ||
|
|
c5af3baaeb | ||
|
|
2030aa1af7 | ||
|
|
4445cb98cf | ||
|
|
12c420d861 | ||
|
|
dc33aa0b5d | ||
|
|
f200af909b | ||
|
|
6f3873ad6b | ||
|
|
70bb2e9d07 | ||
|
|
a9aaf0b940 | ||
|
|
80eb1506d8 | ||
|
|
68723d36f7 | ||
|
|
c937ee735e | ||
|
|
b553094742 | ||
|
|
f31ffed558 | ||
|
|
3f71f97b8b | ||
|
|
1a6308f621 | ||
|
|
7045c59a72 | ||
|
|
4ffb1018f7 | ||
|
|
e78c09f5be | ||
|
|
d8e73f2674 | ||
|
|
0358e820e6 | ||
|
|
1d821f61c3 | ||
|
|
e2ffa42595 | ||
|
|
3a2dcb187e | ||
|
|
08e3a21429 | ||
|
|
42a613caf4 | ||
|
|
4ff38eb0ba | ||
|
|
b466c48487 | ||
|
|
517d4f84ff | ||
|
|
e04a6c9f87 | ||
|
|
1f0867985d | ||
|
|
d0e5988024 | ||
|
|
d007d48afc | ||
|
|
07a847457f | ||
|
|
9347c9f3d2 | ||
|
|
4ee5a7361c | ||
|
|
dad8b2a025 | ||
|
|
d52f6d4aa2 | ||
|
|
a94eae9a16 | ||
|
|
317f6fd4e8 | ||
|
|
b19b07c47d | ||
|
|
0a2ddab597 | ||
|
|
005ac939b1 | ||
|
|
12b0ae9bd6 | ||
|
|
6f92c0d267 | ||
|
|
756cdc0d9e | ||
|
|
783bc84779 | ||
|
|
e621bbf0bd | ||
|
|
43c5c3cc9f | ||
|
|
d8738e06b2 | ||
|
|
67449ec34e | ||
|
|
5c63b00eda | ||
|
|
79eef99731 | ||
|
|
e5496c62d6 | ||
|
|
b1a6f184ba | ||
|
|
878fea0340 | ||
|
|
1142d1adee | ||
|
|
0873f5eba2 | ||
|
|
0faa585d5f | ||
|
|
3b7c4cda7c | ||
|
|
3839a47f5c | ||
|
|
85503d2846 | ||
|
|
d6ae6faef4 | ||
|
|
2c2f003e15 | ||
|
|
1af5828b18 |
@@ -1,4 +1,6 @@
|
||||
coverage/
|
||||
dist/
|
||||
tmp/
|
||||
build/
|
||||
src/scripts/vendor/
|
||||
src/js/index.js
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
},
|
||||
"rules": {
|
||||
"new-cap": [0],
|
||||
"max-params": [2, 4],
|
||||
"max-params": [2, 5],
|
||||
"require-jsdoc": [2, {
|
||||
"require": {
|
||||
"FunctionDeclaration": true,
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
# To get started with Dependabot version updates, you'll need to specify which
|
||||
# package ecosystems to update and where the package manifests are located.
|
||||
# Please see the documentation for all configuration options:
|
||||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "npm" # See documentation for possible values
|
||||
directory: "/" # Location of package manifests
|
||||
schedule:
|
||||
interval: "daily"
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Build CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/pebble-dev/pebbleos-docker:v1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
- run: uv tool install pebble-tool
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: pebble build
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: clay
|
||||
path: dist.zip
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
name: Lint CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm run lint
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
name: Node.js Package
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
publish-npm:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
container:
|
||||
image: ghcr.io/pebble-dev/pebbleos-docker:v1
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up uv
|
||||
uses: astral-sh/setup-uv@v7
|
||||
- run: uv tool install pebble-tool
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- run: pebble sdk install 4.9.127
|
||||
- run: pebble build
|
||||
- name: Update npm
|
||||
run: npm install -g npm@latest
|
||||
- run: npm publish
|
||||
@@ -0,0 +1,34 @@
|
||||
name: Test CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- name: Setup Chrome
|
||||
uses: browser-actions/setup-chrome@v2
|
||||
id: setup-chrome
|
||||
with:
|
||||
install-dependencies: true
|
||||
- run: npm install
|
||||
- run: npm run build
|
||||
- name: Run headless test
|
||||
uses: coactions/setup-xvfb@v1
|
||||
with:
|
||||
run: npm run test-travis
|
||||
env:
|
||||
CHROME_BIN: ${{ steps.setup-chrome.outputs.chrome-path }}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
name: Typecheck CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
|
||||
jobs:
|
||||
typecheck:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: 'npm'
|
||||
- run: npm install
|
||||
- run: npm run check:types
|
||||
+10
-23
@@ -1,31 +1,18 @@
|
||||
# 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_*_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
|
||||
|
||||
dist
|
||||
/tmp/
|
||||
/src/js/index.js
|
||||
/dist.zip
|
||||
/dist/
|
||||
/build/
|
||||
/dist-types/
|
||||
|
||||
Executable
+6
@@ -0,0 +1,6 @@
|
||||
# We ignore everying except for what is actually used by the pebble tool.
|
||||
/**/*
|
||||
!/dist.zip
|
||||
!/src/**/*
|
||||
!/index.js
|
||||
!/index.d.ts
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
language: node_js
|
||||
sudo: false
|
||||
node_js:
|
||||
- '4'
|
||||
|
||||
before_script:
|
||||
# Needed to include Chrome in Travis for karma tests
|
||||
- export CHROME_BIN=chromium-browser
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
|
||||
script: 'npm run test-travis'
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for checking out Clay. We accept pull requests from anybody, however we ask
|
||||
that you follow some conventions when contributing to Clay.
|
||||
|
||||
We have set up a
|
||||
[piece-of-cake](https://github.com/pebble-dev/clay/issues?q=is%3Aopen+is%3Aissue+label%3Apiece-of-cake)
|
||||
label in GitHub issues for bugs/features that are easy to implement. These issues are
|
||||
a great place to start for people that are new to the codebase.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Node](https://nodejs.org) (4.0 and later)
|
||||
- [Google Chrome](www.google.com/chrome) (To run tests)
|
||||
- [Pebble SDK](https://developer.rebble.io/sdk/) (optional)
|
||||
|
||||
|
||||
## Getting Started
|
||||
|
||||
### 1. Fork, then clone the repo:
|
||||
|
||||
`$ git clone git@github.com:your-username/clay.git`
|
||||
|
||||
### 2. Create a new branch with the following naming convention.
|
||||
|
||||
`#<issue-id>/description-of-change`
|
||||
|
||||
example:
|
||||
|
||||
`#90/support-message-keys`
|
||||
|
||||
If there is currently no GitHub issue open for your proposed contribution,
|
||||
then please make one. This allows us to assign milestones and triage priority.
|
||||
|
||||
### 3. Install the project dependencies.
|
||||
|
||||
`$ npm install`
|
||||
|
||||
### 4. Develop your feature
|
||||
|
||||
Commit as often as you like. Your branch will be squashed when the pull request is merged.
|
||||
|
||||
### 5. Run the tests
|
||||
|
||||
`$ npm run test`
|
||||
|
||||
### 6. Run linting
|
||||
|
||||
`$ npm run lint`
|
||||
|
||||
### 7. Submit your pull request
|
||||
|
||||
In the main comment of the pull request, you must state the GitHub issue that your PR
|
||||
resolves. You do this by using the syntax `Resolves #<issue-id>`. An example of this
|
||||
can be found [here](https://github.com/pebble/clay/pull/81)
|
||||
|
||||
|
||||
|
||||
# Project Structure and Development
|
||||
|
||||
|
||||
## Important Files
|
||||
|
||||
There are two main entry points for Clay - `index.js` and `src/scripts/config-page.js`.
|
||||
|
||||
### index.js
|
||||
|
||||
This is the main entry point for the code that will run in the Pebble app's `src/js/app.js`.
|
||||
It is responsible for serializing the provided config into a data URI that will be opened
|
||||
using `Pebble.openURL()`. It also persists data to `localStorage`.
|
||||
|
||||
### src/scripts/config-page.js
|
||||
|
||||
This is the main entry point for the code that runs on the generated config page,
|
||||
and is responsible for passing the injected config and other components to the
|
||||
`ClayConfig` class.
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
Use the following command to build Clay during development.
|
||||
|
||||
`$ npm run dev`
|
||||
|
||||
This command packages up `src/scripts/config-page.js` and `dev/dev.js` into the `tmp/` directory
|
||||
so `dev/dev.html` can include them as script tags. This will also watch for changes
|
||||
in the project.
|
||||
|
||||
While developing components and other functionality for Clay, it is much easier to
|
||||
work with the files in the `dev/` directory than on a phone or emulator. Below is
|
||||
an explanation of the files and their purpose.
|
||||
|
||||
| File | Purpose |
|
||||
|----------|-----|
|
||||
| `dev.html` | Open this page in a browser after running `$ npm run dev`. |
|
||||
| `dev.js` | Injects the components and dependencies into the window the same way `index.js` would. |
|
||||
| `config.js` | Clay config to use as a sandbox for testing components. |
|
||||
| `custom-fn.js` | Clay custom function to be injected by `dev.js`. |
|
||||
| `emulator.html` | Copy of the HTML page that is used to make the Clay compatible with the Pebble SDK emulator. |
|
||||
| `uri-test.html` | Used to stress test URI creation for older browser versions. |
|
||||
|
||||
|
||||
## Testing your change inside of a test project.
|
||||
|
||||
Once your change is ready to be tested in the emulator or on the watch, follow these steps:
|
||||
|
||||
1. From your Clay fork run `$ npm run pebble-build`. This builds Clay to work with the Pebble SDK
|
||||
2. From your test Pebble project, run `$ pebble package install /path/to/your/clay/fork`.
|
||||
This will modify your `package.json` to point directly to your local copy of Clay.
|
||||
3. Run the usual`$ pebble build && pebble install --emulator basalt && pebble emu-app-config` to test your modifications
|
||||
4. If you make modifications to your fork of Clay, you must repeat the above steps.
|
||||
|
||||
|
||||
## Functionality
|
||||
|
||||
Most of the magic happens in the `src/scripts/lib` directory. `config-page.js`
|
||||
initializes a new instance of `ClayConfig` and calls the injected custom function
|
||||
(`window.customFn`) with the `ClayConfig` as its context. This allows developers to
|
||||
add extra functionality to the config page, such as setting values of items dynamically
|
||||
or registering small custom components.
|
||||
|
||||
Once the `ClayConfig` is initialized, we run the `.build()` method. This iterates over
|
||||
the config and injects each item into the page. Each item is an instance of `ClayItem`.
|
||||
It also indexes the items to later be retrieved with `.getAllItems()`,
|
||||
`.getItemByMessageKey()`, `.getItemById()`, `.getItemsByType()`.
|
||||
|
||||
|
||||
## Testing
|
||||
|
||||
Clay enforces 100% test coverage. Chances are, that if you are submitting a pull request,
|
||||
there should be tests for the proposed code changes. Have a look at the other tests in
|
||||
the repository to see some examples of how you should be writing your tests.
|
||||
|
||||
There are two commands to use while writing your tests.
|
||||
|
||||
- `$ npm run test` - Runs all tests once.
|
||||
- `$ npm run test-debug` - Watches for file changes and re-runs all tests when it
|
||||
detects a change. This also leaves an in instance of Google Chrome running.
|
||||
If you click the "Debug" button in the top right of the page, a new tab will open.
|
||||
You can then use the Google Chrome developer tools to debug your tests as they run.
|
||||
Refreshing the page will re-run the tests.
|
||||
|
||||
## Style Guide
|
||||
|
||||
Our style guide is very similar to the
|
||||
[Airbnb style guide.](https://github.com/airbnb/javascript/tree/master/es5) with the
|
||||
following exceptions:
|
||||
|
||||
- When saving a reference to `this` use `self`.
|
||||
```javascript
|
||||
// bad
|
||||
function() {
|
||||
var _this = this;
|
||||
return function() {
|
||||
console.log(_this);
|
||||
};
|
||||
}
|
||||
|
||||
// bad
|
||||
function() {
|
||||
var that = this;
|
||||
return function() {
|
||||
console.log(that);
|
||||
};
|
||||
}
|
||||
|
||||
// good
|
||||
function() {
|
||||
var self = this;
|
||||
return function() {
|
||||
console.log(self);
|
||||
};
|
||||
}
|
||||
```
|
||||
- Don't double name your functions. It makes refactoring more difficult.
|
||||
|
||||
```javascript
|
||||
// bad
|
||||
var log = function log(msg) {
|
||||
console.log(msg);
|
||||
};
|
||||
|
||||
// good
|
||||
var log = function(msg) {
|
||||
console.log(msg);
|
||||
};
|
||||
```
|
||||
|
||||
The style guide is enforced using ESLint. Run `$ npm run lint` to ensure you are
|
||||
adhering to the style guide.
|
||||
|
||||
## Getting Help
|
||||
|
||||
- Chat to us in the `#app-dev` channel on Discord. https://discordapp.com/invite/aRUAYFN
|
||||
- Visit the [Rebble Forums](https://forum.rebble.io/)
|
||||
- Skeet at [@rebble.io](https://bsky.app/profile/rebble.io)
|
||||
- More options at https://developer.rebble.io/community/online/
|
||||
@@ -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"
|
||||
}
|
||||
];
|
||||
+69
-14
@@ -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,9 +31,14 @@ 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": "Input Field",
|
||||
"description": "This is a description for the input component. " +
|
||||
@@ -31,22 +47,58 @@ module.exports = [
|
||||
"placeholder": "Placeholder set with attributes"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"label": "Time Field",
|
||||
"attributes": {
|
||||
"type": "time"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "input",
|
||||
"label": "Date Field",
|
||||
"attributes": {
|
||||
"type": "date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "slider",
|
||||
"messageKey": "slider",
|
||||
"defaultValue": 0,
|
||||
"label": "Slider",
|
||||
"description": "This is the description for the slider",
|
||||
"min": 0,
|
||||
"max": 30,
|
||||
"step": 0.25
|
||||
},
|
||||
{
|
||||
"type": "toggle",
|
||||
"appKey": "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": "Standard Color Picker",
|
||||
"sunlight": false
|
||||
},
|
||||
{
|
||||
"type": "color",
|
||||
"appKey": "sunnyColorTest",
|
||||
"messageKey": "sunnyColorTest",
|
||||
"defaultValue": "00FF00",
|
||||
"label": "Sunny Color Picker",
|
||||
"sunlight": true
|
||||
@@ -70,7 +122,7 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
"type": "radiogroup",
|
||||
"appKey": "radiogroup-test",
|
||||
"messageKey": "radiogroup-test",
|
||||
"label": "Radio Group",
|
||||
"options": [
|
||||
{ "label": "Test thing", "value": "one" },
|
||||
@@ -80,25 +132,28 @@ module.exports = [
|
||||
},
|
||||
{
|
||||
"type": "checkboxgroup",
|
||||
"appKey": "checkboxgroup-test",
|
||||
"defaultValue": ["quote' \"test", "two"],
|
||||
"messageKey": "checkboxgroup-test",
|
||||
"defaultValue": [0, 1, 0],
|
||||
"label": "Checkbox Group",
|
||||
"options": [
|
||||
{ "label": "First thing", "value": "three" },
|
||||
{ "label": "Another thing", "value": "two" },
|
||||
{ "label": "Final thing", "value": "three" }
|
||||
]
|
||||
"options": [ "First thing", "Another thing", "Final thing" ]
|
||||
},
|
||||
{
|
||||
"type": "select",
|
||||
"appKey": "flavor",
|
||||
"messageKey": "flavor",
|
||||
"defaultValue": "grape",
|
||||
"label": "Select Menu",
|
||||
"options": [
|
||||
{ "label": "", "value": "" },
|
||||
{ "label": "Berry", "value": "berry" },
|
||||
{ "label": "This Option is Selected", "value": "grape" },
|
||||
{ "label": "Banana", "value": "banana" }
|
||||
{ "label": "Banana", "value": "banana" },
|
||||
{
|
||||
"label": "This is an optgroup",
|
||||
"value": [
|
||||
{ "label": "Peach", "value": "peach" },
|
||||
{ "label": "Mango", "value": "mango" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"attributes": {
|
||||
"required": "required"
|
||||
|
||||
+15
-3
@@ -10,15 +10,27 @@ module.exports = function() {
|
||||
*/
|
||||
function toggleBackground() {
|
||||
if (this.get()) {
|
||||
Clay.getItemByAppKey('colorTest').enable();
|
||||
Clay.getItemByMessageKey('colorTest').enable();
|
||||
} else {
|
||||
Clay.getItemByAppKey('colorTest').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
@@ -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
@@ -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>
|
||||
|
||||
+72
-36
@@ -8,65 +8,71 @@ var stringify = require('stringify');
|
||||
var del = require('del');
|
||||
var inline = require('gulp-inline');
|
||||
var htmlmin = require('gulp-htmlmin');
|
||||
var sass = require('gulp-sass');
|
||||
var sass = require('gulp-sass')(require('sass'));
|
||||
var sourceMaps = require('gulp-sourcemaps');
|
||||
var autoprefixer = require('gulp-autoprefixer');
|
||||
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,
|
||||
'src/styles'
|
||||
);
|
||||
|
||||
var sassifyOptions = {
|
||||
base64Encode: false,
|
||||
sourceMap: false,
|
||||
sourceMapEmbed: false,
|
||||
sourceMapContents: false,
|
||||
outputStyle: 'compact',
|
||||
includePaths: sassIncludePaths
|
||||
};
|
||||
|
||||
var autoprefixerOptions = {
|
||||
browsers: ['Android 4', 'iOS 8'],
|
||||
cascade: false
|
||||
};
|
||||
|
||||
var stringifyOptions = ['.html', '.tpl'];
|
||||
var versionMessage = '/* Clay - https://github.com/pebble/clay - Version: ' +
|
||||
require('./package.json').version +
|
||||
var stringifyOptions = ['.html', '.tpl', '.css'];
|
||||
var versionMessage = '/* Clay - https://github.com/pebble-dev/clay - Version: ' +
|
||||
clayPackage.version +
|
||||
' - Build Date: ' + new Date().toISOString() + ' */\n';
|
||||
|
||||
gulp.task('clean-js', function() {
|
||||
return del(['tmp/config-page.js']);
|
||||
});
|
||||
|
||||
gulp.task('js', ['clean-js'], function() {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
function taskJs() {
|
||||
return browserify('src/scripts/config-page.js', { debug: true })
|
||||
.transform('deamdify')
|
||||
.bundle()
|
||||
.pipe(source('config-page.js'))
|
||||
.pipe(gulp.dest('./tmp/'));
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('js', gulp.series('clean-js', taskJs));
|
||||
|
||||
gulp.task('clean-sass', function() {
|
||||
return del(['tmp/config-page.scss']);
|
||||
});
|
||||
gulp.task('sass', ['clean-sass'], function() {
|
||||
gulp.src('./src/styles/config-page.scss')
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
function taskSass() {
|
||||
return gulp.src(['./src/styles/config-page.scss',
|
||||
'./src/styles/clay/components/*.scss'])
|
||||
.pipe(sourceMaps.init())
|
||||
.pipe(sass({
|
||||
includePaths: sassIncludePaths
|
||||
loadPaths: sassIncludePaths
|
||||
}).on('error', sass.logError))
|
||||
.pipe(autoprefixer(autoprefixerOptions))
|
||||
.pipe(sourceMaps.write('./'))
|
||||
.pipe(gulp.dest('tmp'));
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('inlineHtml', ['js', 'sass'], function() {
|
||||
gulp.task('sass', gulp.series('clean-sass', taskSass));
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
function taskInlineHtml() {
|
||||
return gulp.src('src/config-page.html')
|
||||
.pipe(inline())
|
||||
.pipe(htmlmin({
|
||||
@@ -79,41 +85,71 @@ gulp.task('inlineHtml', ['js', 'sass'], function() {
|
||||
minifyCSS: true
|
||||
}))
|
||||
.pipe(gulp.dest('tmp/'));
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('clay', ['inlineHtml'], function() {
|
||||
gulp.task('inlineHtml', gulp.series('js', 'sass', taskInlineHtml));
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
function taskClay() {
|
||||
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'})
|
||||
// .transform(autoprefixify, autoprefixerOptions)
|
||||
.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('clay', gulp.series('inlineHtml', taskClay));
|
||||
|
||||
// Validates the curated index.d.ts against test/type-checks.ts.
|
||||
// For per-file reference declarations in dist-types/, run: npm run build:types
|
||||
gulp.task('types', function(done) {
|
||||
var exec = require('child_process').exec;
|
||||
exec('npx tsc -p tsconfig.typecheck.json', function(err, stdout, stderr) {
|
||||
if (stdout) { console.log(stdout); }
|
||||
if (stderr) { console.error(stderr); }
|
||||
done(err);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('dev-js', ['js', 'sass'], function() {
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
function taskDevJs() {
|
||||
return browserify('dev/dev.js', { debug: true })
|
||||
.transform(stringify(stringifyOptions))
|
||||
.transform('deamdify')
|
||||
.transform(sassify, sassifyOptions)
|
||||
.transform(autoprefixify, autoprefixerOptions)
|
||||
.ignore('message_keys')
|
||||
.bundle()
|
||||
.pipe(source('dev.js'))
|
||||
.pipe(gulp.dest('./tmp/'));
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('default', ['clay']);
|
||||
gulp.task('dev-js', gulp.series('js', 'sass', taskDevJs));
|
||||
|
||||
gulp.task('dev', ['dev-js'], function() {
|
||||
gulp.task('default', gulp.series('clay', 'types'));
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
function taskDev() {
|
||||
gulp.watch('src/styles/**/*.scss', ['sass']);
|
||||
gulp.watch(['src/scripts/**/*.js', 'src/templates/**/*.tpl'], ['js']);
|
||||
gulp.watch(['src/**', 'dev/**/*.js'], ['dev-js']);
|
||||
});
|
||||
}
|
||||
|
||||
gulp.task('dev', gulp.series('dev-js', taskDev));
|
||||
|
||||
Vendored
+312
@@ -0,0 +1,312 @@
|
||||
/**
|
||||
* TypeScript declarations for @rebble/clay
|
||||
*
|
||||
* A Pebble configuration framework that provides a simple way to generate a
|
||||
* configuration page for Pebble watchapps and watchfaces.
|
||||
*/
|
||||
|
||||
/** Minimal DOM wrapper type from minified.js */
|
||||
interface M {
|
||||
[index: number]: HTMLElement;
|
||||
length: number;
|
||||
add(child: M | string): M;
|
||||
set(property: string, value?: unknown): M;
|
||||
get(property: string): unknown;
|
||||
select(selector: string): M;
|
||||
on(events: string, handler: (...args: unknown[]) => void): M;
|
||||
each(callback: (element: HTMLElement, index: number) => void): M;
|
||||
}
|
||||
|
||||
/** A Clay config item as provided in the config array */
|
||||
interface ClayConfigItem {
|
||||
type: string;
|
||||
defaultValue?: string | boolean | number;
|
||||
messageKey?: string;
|
||||
id?: string;
|
||||
label?: string;
|
||||
attributes?: Record<string, unknown>;
|
||||
options?: unknown[];
|
||||
items?: ClayConfigItem[];
|
||||
capabilities?: string[];
|
||||
group?: string;
|
||||
}
|
||||
|
||||
/** A Clay component definition */
|
||||
interface ClayComponent {
|
||||
name: string;
|
||||
template: string;
|
||||
manipulator: string | ClayManipulator;
|
||||
defaults?: Record<string, unknown>;
|
||||
style?: string;
|
||||
initialize?: (this: ClayItem, minified: unknown, clay: ClayConfig) => void;
|
||||
}
|
||||
|
||||
/** A manipulator with get and set methods */
|
||||
interface ClayManipulator {
|
||||
get: (this: ClayItem) => unknown;
|
||||
set: (this: ClayItem, value: unknown) => ClayItem;
|
||||
disable?: (this: ClayItem) => ClayItem;
|
||||
enable?: (this: ClayItem) => ClayItem;
|
||||
hide?: (this: ClayItem) => ClayItem;
|
||||
show?: (this: ClayItem) => ClayItem;
|
||||
}
|
||||
|
||||
/** Clay meta information populated from the Pebble object */
|
||||
interface ClayMeta {
|
||||
activeWatchInfo: PebbleActiveWatchInfo | null;
|
||||
accountToken: string;
|
||||
watchToken: string;
|
||||
userData: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Watch info from the Pebble API */
|
||||
interface PebbleActiveWatchInfo {
|
||||
platform: string;
|
||||
model: string;
|
||||
language: string;
|
||||
firmware: {
|
||||
major: number;
|
||||
minor: number;
|
||||
patch: number;
|
||||
suffix: string;
|
||||
};
|
||||
}
|
||||
|
||||
/** Options for the Clay constructor */
|
||||
interface ClayOptions {
|
||||
autoHandleEvents?: boolean;
|
||||
userData?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/** Event methods mixed in by ClayEvents */
|
||||
interface ClayEvents {
|
||||
/**
|
||||
* Attach an event listener.
|
||||
* @param events - a space-separated list of events
|
||||
* @param handler - the event handler
|
||||
*/
|
||||
on(events: string, handler: (...args: unknown[]) => void): this;
|
||||
|
||||
/**
|
||||
* Remove the given event handler from all registered events.
|
||||
* @param handler - the handler to remove
|
||||
*/
|
||||
off(handler: (...args: unknown[]) => void): this;
|
||||
|
||||
/**
|
||||
* Trigger an event.
|
||||
* @param name - a single event name to trigger
|
||||
* @param eventObj - an object to pass to the event handler
|
||||
*/
|
||||
trigger(name: string, eventObj?: unknown): this;
|
||||
}
|
||||
|
||||
/** A ClayItem represents a single config component in the config page */
|
||||
interface ClayItem extends ClayEvents {
|
||||
/** The item's id from the config, or null */
|
||||
id: string | null;
|
||||
|
||||
/** The item's messageKey from the config, or null */
|
||||
messageKey: string | null;
|
||||
|
||||
/** The raw config for this item */
|
||||
config: ClayConfigItem;
|
||||
|
||||
/** The root DOM element wrapper */
|
||||
$element: M;
|
||||
|
||||
/** The manipulator target DOM element wrapper */
|
||||
$manipulatorTarget: M;
|
||||
|
||||
/** The precision for numeric values */
|
||||
precision?: number;
|
||||
|
||||
/**
|
||||
* Run the component initialiser.
|
||||
* @param clay - the ClayConfig instance
|
||||
*/
|
||||
initialize(clay: ClayConfig): ClayItem;
|
||||
|
||||
/** Get the current value of the item via its manipulator */
|
||||
get(): unknown;
|
||||
|
||||
/** Set the value of the item via its manipulator */
|
||||
set(value: unknown): ClayItem;
|
||||
|
||||
/** Disable the item */
|
||||
disable(): ClayItem;
|
||||
|
||||
/** Enable the item */
|
||||
enable(): ClayItem;
|
||||
|
||||
/** Hide the item */
|
||||
hide(): ClayItem;
|
||||
|
||||
/** Show the item */
|
||||
show(): ClayItem;
|
||||
}
|
||||
|
||||
/** ClayConfig lifecycle event names */
|
||||
interface ClayConfigEvents {
|
||||
readonly BEFORE_BUILD: 'BEFORE_BUILD';
|
||||
readonly AFTER_BUILD: 'AFTER_BUILD';
|
||||
readonly BEFORE_DESTROY: 'BEFORE_DESTROY';
|
||||
readonly AFTER_DESTROY: 'AFTER_DESTROY';
|
||||
}
|
||||
|
||||
/** ClayConfig manages the config page and its items */
|
||||
interface ClayConfig extends ClayEvents {
|
||||
/** Meta information from the Pebble object */
|
||||
meta: ClayMeta;
|
||||
|
||||
/** The root container element */
|
||||
$rootContainer: M;
|
||||
|
||||
/** Lifecycle event name constants */
|
||||
EVENTS: ClayConfigEvents;
|
||||
|
||||
/** The config array (may be modified before build) */
|
||||
config: ClayConfigItem | ClayConfigItem[];
|
||||
|
||||
/** Get all items. Must call build() first. */
|
||||
getAllItems(): ClayItem[];
|
||||
|
||||
/** Get an item by its messageKey. Must call build() first. */
|
||||
getItemByMessageKey(messageKey: string): ClayItem;
|
||||
|
||||
/** Get an item by its id. Must call build() first. */
|
||||
getItemById(id: string): ClayItem;
|
||||
|
||||
/** Get all items of a given type. Must call build() first. */
|
||||
getItemsByType(type: string): ClayItem[];
|
||||
|
||||
/** Get all items belonging to a given group. Must call build() first. */
|
||||
getItemsByGroup(group: string): ClayItem[];
|
||||
|
||||
/** Serialise the current settings. Must call build() first. */
|
||||
serialize(): Record<string, { value: unknown; precision?: number }>;
|
||||
|
||||
/** Register a component. Alias for ClayConfig.registerComponent. */
|
||||
registerComponent(component: ClayComponent): boolean;
|
||||
|
||||
/** Destroy the config page and reset items. */
|
||||
destroy(): ClayConfig;
|
||||
|
||||
/** Build the config page. Must be called before get methods. */
|
||||
build(): ClayConfig;
|
||||
}
|
||||
|
||||
interface ClayConfigConstructor {
|
||||
new (
|
||||
settings: Record<string, unknown>,
|
||||
config: ClayConfigItem | ClayConfigItem[],
|
||||
$rootContainer: M,
|
||||
meta: ClayMeta
|
||||
): ClayConfig;
|
||||
|
||||
/**
|
||||
* Register a component to Clay. Must be called before build().
|
||||
*/
|
||||
registerComponent(component: ClayComponent): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main Clay constructor.
|
||||
*
|
||||
* @param config - the Clay config array
|
||||
* @param customFn - custom code to run from the config page
|
||||
* @param options - additional options
|
||||
*/
|
||||
declare class Clay {
|
||||
constructor(
|
||||
config: ClayConfigItem[],
|
||||
customFn?: ((this: ClayConfig) => void) | null,
|
||||
options?: ClayOptions
|
||||
);
|
||||
|
||||
/** The Clay config array */
|
||||
config: ClayConfigItem[];
|
||||
|
||||
/** The custom function */
|
||||
customFn: (this: ClayConfig) => void;
|
||||
|
||||
/** Registered components */
|
||||
components: Record<string, ClayComponent>;
|
||||
|
||||
/** Meta information populated from the Pebble object */
|
||||
meta: ClayMeta;
|
||||
|
||||
/** The Clay version string */
|
||||
version: string;
|
||||
|
||||
/**
|
||||
* Register a component to Clay.
|
||||
* @param component - the clay component to register
|
||||
*/
|
||||
registerComponent(component: ClayComponent): void;
|
||||
|
||||
/**
|
||||
* Generate the Data URI used by the config page with settings injected.
|
||||
*/
|
||||
generateUrl(): string;
|
||||
|
||||
/**
|
||||
* Parse the response from the webviewclosed event data.
|
||||
* @param response - the response string
|
||||
* @param convert - if false, return raw settings without conversion
|
||||
*/
|
||||
getSettings(response: string, convert?: boolean): Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Update settings with the given key/value pair.
|
||||
* @param key - the setting key
|
||||
* @param value - the setting value
|
||||
*/
|
||||
setSettings(key: string, value: unknown): void;
|
||||
|
||||
/**
|
||||
* Update settings with the given object.
|
||||
* @param settings - an object of key/value pairs to set
|
||||
*/
|
||||
setSettings(settings: Record<string, unknown>): void;
|
||||
|
||||
/**
|
||||
* Encode content as a data URI.
|
||||
* @param input - the content to encode
|
||||
* @param prefix - the URI prefix
|
||||
*/
|
||||
static encodeDataUri(input: string, prefix?: string): string;
|
||||
|
||||
/**
|
||||
* Convert a value to a type compatible with Pebble.sendAppMessage().
|
||||
*/
|
||||
static prepareForAppMessage(
|
||||
val: unknown
|
||||
): number | string | (number | string)[];
|
||||
|
||||
/**
|
||||
* Convert Clay settings to a format compatible with Pebble.sendAppMessage().
|
||||
*/
|
||||
static prepareSettingsForAppMessage(
|
||||
settings: Record<string, unknown>
|
||||
): Record<number, number | string>;
|
||||
}
|
||||
|
||||
declare namespace Clay {
|
||||
export {
|
||||
ClayConfigItem,
|
||||
ClayComponent,
|
||||
ClayManipulator,
|
||||
ClayMeta,
|
||||
PebbleActiveWatchInfo,
|
||||
ClayOptions,
|
||||
ClayEvents,
|
||||
ClayItem,
|
||||
ClayConfig,
|
||||
ClayConfigEvents,
|
||||
ClayConfigConstructor,
|
||||
M,
|
||||
};
|
||||
}
|
||||
|
||||
export = Clay;
|
||||
@@ -3,15 +3,19 @@
|
||||
var configPageHtml = require('./tmp/config-page.html');
|
||||
var toSource = require('tosource');
|
||||
var standardComponents = require('./src/scripts/components');
|
||||
var utils = require('./src/scripts/lib/utils');
|
||||
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) {
|
||||
@@ -27,14 +31,30 @@ 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: ''
|
||||
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') {
|
||||
@@ -63,36 +83,50 @@ function Clay(config, customFn, options) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Populate the meta with data from the Pebble object. Make sure to run this inside
|
||||
* either the "showConfiguration" or "ready" event handler
|
||||
* @return {void}
|
||||
* If this function returns true then the callback will be executed
|
||||
* @callback _scanConfig_testFn
|
||||
* @param {Clay~ConfigItem} item
|
||||
*/
|
||||
function _populateMeta() {
|
||||
self.meta = {
|
||||
activeWatchInfo: Pebble.getActiveWatchInfo && Pebble.getActiveWatchInfo(),
|
||||
accountToken: Pebble.getAccountToken(),
|
||||
watchToken: Pebble.getWatchToken()
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @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');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,72 +190,190 @@ Clay.prototype.generateUrl = function() {
|
||||
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));
|
||||
// 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];
|
||||
}
|
||||
});
|
||||
|
||||
return convert === false ? settings : utils.prepareSettingsForAppMessage(settings);
|
||||
localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
|
||||
|
||||
return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the settings with the given value(s).
|
||||
*
|
||||
* @signature `clay.setSettings(key, value)`
|
||||
* @param {String} key - The property to set.
|
||||
* @param {*} value - the value assigned to _key_.
|
||||
* @return {undefined}
|
||||
*
|
||||
* @signature `clay.setSettings(settings)`
|
||||
* @param {Object} settings - an object containing the key/value pairs to be set.
|
||||
* @return {undefined}
|
||||
*/
|
||||
Clay.prototype.setSettings = function(key, value) {
|
||||
var settingsStorage = {};
|
||||
|
||||
try {
|
||||
settingsStorage = JSON.parse(localStorage.getItem('clay-settings')) || {};
|
||||
} catch (e) {
|
||||
console.error(e.toString());
|
||||
}
|
||||
|
||||
if (typeof key === 'object') {
|
||||
var settings = key;
|
||||
Object.keys(settings).forEach(function(key) {
|
||||
settingsStorage[key] = settings[key];
|
||||
});
|
||||
} else {
|
||||
settingsStorage[key] = value;
|
||||
}
|
||||
|
||||
localStorage.setItem('clay-settings', JSON.stringify(settingsStorage));
|
||||
};
|
||||
|
||||
/**
|
||||
* @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 returned without modification
|
||||
* eg: ['one', 'two'] becomes ['one', 'two']
|
||||
* - 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 multiplied 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;
|
||||
|
||||
Generated
+13777
File diff suppressed because it is too large
Load Diff
+57
-33
@@ -1,72 +1,96 @@
|
||||
{
|
||||
"name": "pebble-clay",
|
||||
"version": "0.1.3",
|
||||
"name": "@rebble/clay",
|
||||
"version": "1.0.9",
|
||||
"description": "Pebble Config Framework",
|
||||
"main": "dist/clay.js",
|
||||
"directories": {
|
||||
"example": "example"
|
||||
},
|
||||
"types": "index.d.ts",
|
||||
"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-travis": "./node_modules/.bin/gulp && ./node_modules/.bin/karma start ./test/karma.conf.js --single-run --browsers chromeTravisCI",
|
||||
"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"
|
||||
"build:types": "tsc -p tsconfig.declarations.json",
|
||||
"check:types": "tsc -p tsconfig.typecheck.json",
|
||||
"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"
|
||||
"url": "git+https://github.com/pebble-dev/clay.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Pebble",
|
||||
"Config"
|
||||
"pebble",
|
||||
"config",
|
||||
"configuration",
|
||||
"pebble-package"
|
||||
],
|
||||
"author": "Pebble Technology",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/pebble/clay/issues"
|
||||
"url": "https://github.com/pebble-dev/clay/issues"
|
||||
},
|
||||
"homepage": "https://github.com/pebble/clay#readme",
|
||||
"pebble": {
|
||||
"projectType": "package",
|
||||
"sdkVersion": "3",
|
||||
"targetPlatforms": [
|
||||
"aplite",
|
||||
"basalt",
|
||||
"chalk",
|
||||
"diorite",
|
||||
"emery",
|
||||
"flint",
|
||||
"gabbro"
|
||||
],
|
||||
"resources": {
|
||||
"media": []
|
||||
},
|
||||
"capabilities": [
|
||||
"configurable"
|
||||
]
|
||||
},
|
||||
"homepage": "https://github.com/pebble-dev/clay#readme",
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^6.3.1",
|
||||
"autoprefixer": "^10.4.23",
|
||||
"bourbon": "^4.2.6",
|
||||
"browserify": "^13.0.0",
|
||||
"browserify-istanbul": "^0.2.1",
|
||||
"browserify-istanbul": "^3.0.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",
|
||||
"eslint-plugin-standard": "^1.3.1",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp": "^5.0.1",
|
||||
"gulp-autoprefixer": "^3.1.0",
|
||||
"gulp-htmlmin": "^1.3.0",
|
||||
"gulp-inline": "0.0.15",
|
||||
"gulp-inline": "^0.1.3",
|
||||
"gulp-insert": "^0.5.0",
|
||||
"gulp-sass": "^2.1.1",
|
||||
"gulp-sass": "^6.0.1",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-uglify": "^1.5.2",
|
||||
"joi": "^6.10.1",
|
||||
"karma": "^0.13.19",
|
||||
"karma-browserify": "^5.0.1",
|
||||
"karma-chrome-launcher": "^0.2.2",
|
||||
"karma-coverage": "^0.5.3",
|
||||
"karma-mocha": "^0.2.1",
|
||||
"karma-mocha-reporter": "^1.1.5",
|
||||
"karma-source-map-support": "^1.1.0",
|
||||
"karma": "^6.4.0",
|
||||
"karma-browserify": "^8.1.0",
|
||||
"karma-chrome-launcher": "^3.2.0",
|
||||
"karma-coverage": "^2.2.1",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"karma-source-map-support": "^1.4.0",
|
||||
"karma-threshold-reporter": "^0.1.15",
|
||||
"mocha": "^2.3.4",
|
||||
"postcss": "^5.0.14",
|
||||
"require-from-string": "^1.1.0",
|
||||
"sassify": "^0.9.1",
|
||||
"mocha": "^11.7.5",
|
||||
"postcss": "8.5.6",
|
||||
"require-from-string": "^2.0.2",
|
||||
"sass": "^1.94.2",
|
||||
"sinon": "^1.17.3",
|
||||
"stringify": "^3.2.0",
|
||||
"through": "^2.3.8",
|
||||
"tosource": "^1.0.0",
|
||||
"typescript": "6.0.2",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0",
|
||||
"watchify": "^3.7.0"
|
||||
},
|
||||
"dependencies": {}
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"watchify": "^3.11.1"
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 135 KiB |
@@ -3,7 +3,7 @@
|
||||
module.exports = {
|
||||
name: 'button',
|
||||
template: require('../../templates/components/button.tpl'),
|
||||
style: require('../../styles/clay/components/button.scss'),
|
||||
style: require('../../../tmp/button.css'),
|
||||
manipulator: 'button',
|
||||
defaults: {
|
||||
primary: false,
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
module.exports = {
|
||||
name: 'checkboxgroup',
|
||||
template: require('../../templates/components/checkboxgroup.tpl'),
|
||||
style: require('../../styles/clay/components/checkboxgroup.scss'),
|
||||
style: require('../../../tmp/checkboxgroup.css'),
|
||||
manipulator: 'checkboxgroup',
|
||||
defaults: {
|
||||
label: '',
|
||||
options: [],
|
||||
description: '',
|
||||
attributes: {}
|
||||
description: ''
|
||||
}
|
||||
};
|
||||
|
||||
+226
-93
@@ -3,123 +3,233 @@
|
||||
module.exports = {
|
||||
name: 'color',
|
||||
template: require('../../templates/components/color.tpl'),
|
||||
style: require('../../styles/clay/components/color.scss'),
|
||||
style: require('../../../tmp/color.css'),
|
||||
manipulator: 'color',
|
||||
defaults: {
|
||||
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() {
|
||||
var bwWatches = ['aplite', 'diorite', 'flint'];
|
||||
if (!clay.meta.activeWatchInfo ||
|
||||
clay.meta.activeWatchInfo.firmware.major === 2 ||
|
||||
bwWatches.indexOf(clay.meta.activeWatchInfo.platform) > -1 &&
|
||||
!self.config.allowGray) {
|
||||
return standardLayouts.BLACK_WHITE;
|
||||
}
|
||||
|
||||
if (bwWatches.indexOf(clay.meta.activeWatchInfo.platform) > -1 &&
|
||||
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] ||
|
||||
@@ -146,7 +256,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 + '%; ' +
|
||||
@@ -155,13 +265,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
|
||||
}
|
||||
@@ -191,5 +322,7 @@ module.exports = {
|
||||
disabled = false;
|
||||
});
|
||||
|
||||
// expose layout for testing
|
||||
self._layout = layout;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11,5 +11,6 @@ module.exports = {
|
||||
toggle: require('./toggle'),
|
||||
radiogroup: require('./radiogroup'),
|
||||
checkboxgroup: require('./checkboxgroup'),
|
||||
button: require('./button')
|
||||
button: require('./button'),
|
||||
slider: require('./slider')
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
module.exports = {
|
||||
name: 'input',
|
||||
template: require('../../templates/components/input.tpl'),
|
||||
style: require('../../styles/clay/components/input.scss'),
|
||||
style: require('../../../tmp/input.css'),
|
||||
manipulator: 'val',
|
||||
defaults: {
|
||||
label: '',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
module.exports = {
|
||||
name: 'radiogroup',
|
||||
template: require('../../templates/components/radiogroup.tpl'),
|
||||
style: require('../../styles/clay/components/radiogroup.scss'),
|
||||
style: require('../../../tmp/radiogroup.css'),
|
||||
manipulator: 'radiogroup',
|
||||
defaults: {
|
||||
label: '',
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
module.exports = {
|
||||
name: 'select',
|
||||
template: require('../../templates/components/select.tpl'),
|
||||
style: require('../../styles/clay/components/select.scss'),
|
||||
style: require('../../../tmp/select.css'),
|
||||
manipulator: 'val',
|
||||
defaults: {
|
||||
label: '',
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
name: 'slider',
|
||||
template: require('../../templates/components/slider.tpl'),
|
||||
style: require('../../../tmp/slider.css'),
|
||||
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();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,7 @@
|
||||
module.exports = {
|
||||
name: 'submit',
|
||||
template: require('../../templates/components/submit.tpl'),
|
||||
style: require('../../styles/clay/components/submit.scss'),
|
||||
style: require('../../../tmp/submit.css'),
|
||||
manipulator: 'button',
|
||||
defaults: {
|
||||
attributes: {}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
module.exports = {
|
||||
name: 'toggle',
|
||||
template: require('../../templates/components/toggle.tpl'),
|
||||
style: require('../../styles/clay/components/toggle.scss'),
|
||||
style: require('../../../tmp/toggle.css'),
|
||||
manipulator: 'checked',
|
||||
defaults: {
|
||||
label: '',
|
||||
|
||||
@@ -28,7 +28,7 @@ var clayConfig = new ClayConfig(settings, config, $mainForm, clayMeta);
|
||||
$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
|
||||
|
||||
+112
-40
@@ -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;
|
||||
@@ -33,10 +34,22 @@ function ClayConfig(settings, config, $rootContainer, meta) {
|
||||
var self = this;
|
||||
|
||||
var _settings = _.copyObj(settings);
|
||||
var _items = [];
|
||||
var _itemsById = {};
|
||||
var _itemsByAppKey = {};
|
||||
var _isBuilt = false;
|
||||
var _items;
|
||||
var _itemsById;
|
||||
var _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
|
||||
@@ -49,34 +62,36 @@ function ClayConfig(settings, config, $rootContainer, meta) {
|
||||
item.forEach(function(item) {
|
||||
_addItems(item, $container);
|
||||
});
|
||||
} else if (item.type === 'section') {
|
||||
var $wrapper = HTML('<div class="section">');
|
||||
$container.add($wrapper);
|
||||
_addItems(item.items, $wrapper);
|
||||
} else {
|
||||
var _item = _.copyObj(item);
|
||||
_item.clayId = _items.length;
|
||||
} else if (utils.includesCapability(meta.activeWatchInfo, item.capabilities)) {
|
||||
if (item.type === 'section') {
|
||||
var $wrapper = HTML('<div class="section">');
|
||||
$container.add($wrapper);
|
||||
_addItems(item.items, $wrapper);
|
||||
} else {
|
||||
var _item = _.copyObj(item);
|
||||
_item.clayId = _items.length;
|
||||
|
||||
var clayItem = new ClayItem(_item).initialize(self);
|
||||
var clayItem = new ClayItem(_item).initialize(self);
|
||||
|
||||
if (_item.id) {
|
||||
_itemsById[_item.id] = clayItem;
|
||||
if (_item.id) {
|
||||
_itemsById[_item.id] = clayItem;
|
||||
}
|
||||
|
||||
if (_item.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(typeof value !== 'undefined' ? value : '');
|
||||
|
||||
$container.add(clayItem.$element);
|
||||
}
|
||||
|
||||
if (_item.appKey) {
|
||||
_itemsByAppKey[_item.appKey] = clayItem;
|
||||
}
|
||||
|
||||
_items.push(clayItem);
|
||||
|
||||
// set the value of the item via the manipulator to ensure consistency
|
||||
var value = typeof _settings[_item.appKey] !== 'undefined' ?
|
||||
_settings[_item.appKey] :
|
||||
(_item.defaultValue || '');
|
||||
|
||||
clayItem.set(value);
|
||||
|
||||
$container.add(clayItem.$element);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +112,7 @@ function ClayConfig(settings, config, $rootContainer, meta) {
|
||||
}
|
||||
|
||||
self.meta = meta;
|
||||
self.$rootContainer = $rootContainer;
|
||||
|
||||
self.EVENTS = {
|
||||
/**
|
||||
@@ -111,7 +127,21 @@ function ClayConfig(settings, config, $rootContainer, meta) {
|
||||
* value set
|
||||
* @const
|
||||
*/
|
||||
AFTER_BUILD: 'AFTER_BUILD'
|
||||
AFTER_BUILD: 'AFTER_BUILD',
|
||||
|
||||
/**
|
||||
* Called if .build() is executed after the page has already been built and
|
||||
* before the existing content is destroyed
|
||||
* @const
|
||||
*/
|
||||
BEFORE_DESTROY: 'BEFORE_DESTROY',
|
||||
|
||||
/**
|
||||
* Called if .build() is executed after the page has already been built and after
|
||||
* the existing content is destroyed
|
||||
* @const
|
||||
*/
|
||||
AFTER_DESTROY: 'AFTER_DESTROY'
|
||||
};
|
||||
utils.updateProperties(self.EVENTS, {writable: false});
|
||||
|
||||
@@ -124,12 +154,12 @@ function ClayConfig(settings, config, $rootContainer, meta) {
|
||||
};
|
||||
|
||||
/**
|
||||
* @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,13 +182,33 @@ function ClayConfig(settings, config, $rootContainer, meta) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} group
|
||||
* @returns {Array.<ClayItem>}
|
||||
*/
|
||||
self.getItemsByGroup = function(group) {
|
||||
_checkBuilt('getItemsByGroup');
|
||||
return _items.filter(function(item) {
|
||||
return item.config.group === group;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @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;
|
||||
};
|
||||
@@ -166,18 +216,40 @@ function ClayConfig(settings, config, $rootContainer, meta) {
|
||||
// @todo maybe don't do this and force the static method
|
||||
self.registerComponent = ClayConfig.registerComponent;
|
||||
|
||||
/**
|
||||
* Empties the root container
|
||||
* @returns {ClayConfig}
|
||||
*/
|
||||
self.destroy = function() {
|
||||
var el = $rootContainer[0];
|
||||
self.trigger(self.EVENTS.BEFORE_DESTROY);
|
||||
while (el.firstChild) {
|
||||
el.removeChild(el.firstChild);
|
||||
}
|
||||
_initializeItems();
|
||||
self.trigger(self.EVENTS.AFTER_DESTROY);
|
||||
return self;
|
||||
};
|
||||
|
||||
/**
|
||||
* Build the config page. This must be run before any of the get methods can be run
|
||||
* If you call this method after the page has already been built, teh page will be
|
||||
* destroyed and built again.
|
||||
* @returns {ClayConfig}
|
||||
*/
|
||||
self.build = function() {
|
||||
if (_isBuilt) {
|
||||
self.destroy();
|
||||
}
|
||||
self.trigger(self.EVENTS.BEFORE_BUILD);
|
||||
_addItems(config, $rootContainer);
|
||||
_addItems(self.config, $rootContainer);
|
||||
_isBuilt = true;
|
||||
self.trigger(self.EVENTS.AFTER_BUILD);
|
||||
return self;
|
||||
};
|
||||
|
||||
_initializeItems();
|
||||
|
||||
// attach event methods
|
||||
ClayEvents.call(self, $rootContainer);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -85,6 +85,21 @@ module.exports = {
|
||||
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');
|
||||
@@ -118,24 +133,27 @@ module.exports = {
|
||||
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;
|
||||
values = values || [];
|
||||
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);
|
||||
self.$element.select('input')
|
||||
.set('checked', false)
|
||||
.each(function(item, index) {
|
||||
item.checked = !!values[index];
|
||||
});
|
||||
|
||||
values.map(function(value) {
|
||||
self.$element
|
||||
.select('input[value="' + value.toString(10).replace('"', '\\"') + '"]')
|
||||
.set('checked', true);
|
||||
});
|
||||
return self.trigger('change');
|
||||
},
|
||||
disable: disable,
|
||||
@@ -145,16 +163,12 @@ module.exports = {
|
||||
},
|
||||
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 = value || '000000';
|
||||
value = this.roundColorToLayout(value || 0);
|
||||
|
||||
if (this.get() === parseInt(value, 16)) { return this; }
|
||||
if (this.get() === value) { return this; }
|
||||
this.$manipulatorTarget.set('value', value);
|
||||
return this.trigger('change');
|
||||
},
|
||||
|
||||
+130
-45
@@ -18,53 +18,138 @@ module.exports.updateProperties = function(obj, descriptor) {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param {number|string|boolean|Array} val
|
||||
* @returns {number|string|Array}
|
||||
*/
|
||||
module.exports.prepareForAppMessage = function(val) {
|
||||
var result;
|
||||
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
|
||||
},
|
||||
PLATFORM_FLINT: {
|
||||
platforms: ['flint'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
PLATFORM_GABBRO: {
|
||||
platforms: ['gabbro'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
BW: {
|
||||
platforms: ['aplite', 'diorite', 'flint'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
COLOR: {
|
||||
platforms: ['basalt', 'chalk', 'emery', 'gabbro'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
COMPASS: {
|
||||
platforms: ['aplite', 'basalt', 'chalk', 'emery', 'flint', 'gabbro'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
MICROPHONE: {
|
||||
platforms: ['basalt', 'chalk', 'diorite', 'emery', 'flint', 'gabbro'],
|
||||
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', 'flint', 'gabbro'],
|
||||
minFwMajor: 3,
|
||||
minFwMinor: 10
|
||||
},
|
||||
RECT: {
|
||||
platforms: ['aplite', 'basalt', 'diorite', 'emery', 'flint'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
ROUND: {
|
||||
platforms: ['chalk', 'gabbro'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
DISPLAY_144x168: {
|
||||
platforms: ['aplite', 'basalt', 'diorite', 'flint'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
DISPLAY_180x180_ROUND: {
|
||||
platforms: ['chalk'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
DISPLAY_200x228: {
|
||||
platforms: ['emery'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
},
|
||||
DISPLAY_260x260_ROUND: {
|
||||
platforms: ['gabbro'],
|
||||
minFwMajor: 0,
|
||||
minFwMinor: 0
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof val === 'boolean') {
|
||||
result = val ? 1 : 0;
|
||||
} else if (Array.isArray(val)) {
|
||||
result = [];
|
||||
val.forEach(function(item) {
|
||||
var itemConverted = module.exports.prepareForAppMessage(item);
|
||||
result.push(itemConverted);
|
||||
if (typeof itemConverted === 'string') {
|
||||
result.push(0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
result = val;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
for (var i = capabilities.length - 1; i >= 0; i--) {
|
||||
var capability = capabilities[i];
|
||||
var mapping = module.exports.capabilityMap[capability.replace(notRegex, '')];
|
||||
|
||||
/**
|
||||
* Converts a Clay settings dict into one that is compatible with
|
||||
* Pebble.sendAppMessage();
|
||||
* @see {prepareForAppMessage}
|
||||
* @param {Object} settings
|
||||
* @returns {{}}
|
||||
*/
|
||||
module.exports.prepareSettingsForAppMessage = function(settings) {
|
||||
var result = {};
|
||||
Object.keys(settings).forEach(function(key) {
|
||||
result[key] = module.exports.prepareForAppMessage(settings[key]);
|
||||
});
|
||||
return result;
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -67,6 +67,12 @@ h6 {
|
||||
@include font-size(0.8);
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -107,6 +113,7 @@ label {
|
||||
|
||||
.tap-highlight {
|
||||
@include tap-highlight();
|
||||
border-radius: $border-radius;
|
||||
}
|
||||
|
||||
.component {
|
||||
|
||||
@@ -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;
|
||||
@@ -37,4 +35,5 @@ $color-gray-11: #f2f2f2;
|
||||
$button-padding: 0.6rem;
|
||||
$button-padding-ios: 0.5rem;
|
||||
|
||||
$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,6 +12,7 @@
|
||||
border-radius: $base-height / 2;
|
||||
box-shadow: $box-shadow-small-components;
|
||||
display: block;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.picker-wrap {
|
||||
@@ -20,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 {
|
||||
@@ -46,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;
|
||||
@@ -77,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;
|
||||
|
||||
@@ -31,6 +31,8 @@
|
||||
vertical-align: baseline;
|
||||
color: $color-white;
|
||||
font-size: inherit;
|
||||
appearance: none;
|
||||
min-height: $item-spacing-v + ($base-height + 0rem) ;
|
||||
|
||||
@include placeholder {
|
||||
color: $color-gray-8;
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
background: #000;
|
||||
width: 100%;
|
||||
border: none;
|
||||
margin:0;
|
||||
|
||||
@@ -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,14 +3,8 @@
|
||||
<div class="checkbox-group">
|
||||
{{each options}}
|
||||
<label class="tap-highlight">
|
||||
<span class="label">{{{this.label}}}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
value="{{this.value}}"
|
||||
name="clay-{{clayId}}"
|
||||
data-manipulator-target
|
||||
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
|
||||
/>
|
||||
<span class="label">{{{this}}}</span>
|
||||
<input type="checkbox" value="1" name="clay-{{clayId}}" />
|
||||
<i></i>
|
||||
</label>
|
||||
{{/each}}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
type="radio"
|
||||
value="{{this.value}}"
|
||||
name="clay-{{clayId}}"
|
||||
data-manipulator-target
|
||||
{{each key: attributes}}{{key}}="{{this}}"{{/each}}
|
||||
/>
|
||||
<i></i>
|
||||
|
||||
@@ -4,7 +4,15 @@
|
||||
<span class="value"></span>
|
||||
<select data-manipulator-target {{each key: attributes}}{{key}}="{{this}}"{{/each}}>
|
||||
{{each options}}
|
||||
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
|
||||
{{if Array.isArray(this.value)}}
|
||||
<optgroup label="{{this.label}}">
|
||||
{{each this.value}}
|
||||
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
|
||||
{{/each}}
|
||||
</optgroup>
|
||||
{{else}}
|
||||
<option value="{{this.value}}" class="item-select-option">{{this.label}}</option>
|
||||
{{/if}}
|
||||
{{/each}}
|
||||
</select>
|
||||
</label>
|
||||
|
||||
@@ -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>
|
||||
+72
-7
@@ -11,12 +11,13 @@ 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}}}}
|
||||
* number, patch: number, suffix: string}}, userData: {}}}
|
||||
*/
|
||||
module.exports.meta = function() {
|
||||
return {
|
||||
module.exports.meta = function(extra) {
|
||||
var result = {
|
||||
accountToken: '0123456789abcdef0123456789abcdef',
|
||||
watchToken: '0123456789abcdef0123456789abcdef',
|
||||
activeWatchInfo: {
|
||||
@@ -29,8 +30,15 @@ module.exports.meta = function() {
|
||||
patch: 2,
|
||||
suffix: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
userData: {}
|
||||
};
|
||||
|
||||
_.eachObj(extra || {}, function(key, val) {
|
||||
result[key] = val;
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -45,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);
|
||||
|
||||
@@ -87,14 +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>')),
|
||||
module.exports.meta()
|
||||
module.exports.meta(meta)
|
||||
);
|
||||
return build === false ? clayConfig : clayConfig.build();
|
||||
};
|
||||
@@ -106,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}
|
||||
*/
|
||||
@@ -115,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;
|
||||
};
|
||||
|
||||
+13
-16
@@ -12,30 +12,22 @@ 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: [
|
||||
[
|
||||
'stringify',
|
||||
{
|
||||
extensions: ['.html', '.tpl']
|
||||
extensions: ['.html', '.tpl', '.css']
|
||||
}
|
||||
],
|
||||
'deamdify',
|
||||
[
|
||||
'sassify',
|
||||
{
|
||||
base64Encode: false,
|
||||
sourceMap: false,
|
||||
sourceMapEmbed: false,
|
||||
sourceMapContents: false,
|
||||
outputStyle: 'compact',
|
||||
includePaths: [].concat(
|
||||
require('bourbon').includePaths,
|
||||
'src/styles'
|
||||
)
|
||||
}
|
||||
],
|
||||
[
|
||||
'browserify-istanbul',
|
||||
{
|
||||
@@ -47,7 +39,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
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"rules": {
|
||||
"max-params": [0]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,224 @@ 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: ''
|
||||
}
|
||||
});
|
||||
testAutoLayout('diorite', standardLayouts.BLACK_WHITE, false, '', {
|
||||
platform: 'diorite',
|
||||
model: 'qemu_platform_diorite',
|
||||
language: 'en_US',
|
||||
firmware: {
|
||||
major: 4,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
suffix: ''
|
||||
}
|
||||
});
|
||||
testAutoLayout('diorite', standardLayouts.GRAY, true, '', {
|
||||
platform: 'diorite',
|
||||
model: 'qemu_platform_diorite',
|
||||
language: 'en_US',
|
||||
firmware: {
|
||||
major: 4,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
suffix: ''
|
||||
}
|
||||
});
|
||||
testAutoLayout('emery', standardLayouts.COLOR, true, '', {
|
||||
platform: 'emery',
|
||||
model: 'qemu_platform_emery',
|
||||
language: 'en_US',
|
||||
firmware: {
|
||||
major: 4,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
suffix: ''
|
||||
}
|
||||
});
|
||||
testAutoLayout('flint', standardLayouts.BLACK_WHITE, false, '', {
|
||||
platform: 'flint',
|
||||
model: 'qemu_platform_flint',
|
||||
language: 'en_US',
|
||||
firmware: {
|
||||
major: 4,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
suffix: ''
|
||||
}
|
||||
});
|
||||
testAutoLayout('flint', standardLayouts.GRAY, true, '', {
|
||||
platform: 'flint',
|
||||
model: 'qemu_platform_flint',
|
||||
language: 'en_US',
|
||||
firmware: {
|
||||
major: 4,
|
||||
minor: 0,
|
||||
patch: 0,
|
||||
suffix: ''
|
||||
}
|
||||
});
|
||||
testAutoLayout('gabbro', standardLayouts.COLOR, true, '', {
|
||||
platform: 'gabbro',
|
||||
model: 'qemu_platform_gabbro',
|
||||
language: 'en_US',
|
||||
firmware: {
|
||||
major: 4,
|
||||
minor: 0,
|
||||
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']
|
||||
);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,4 +21,26 @@ describe('component - select', function() {
|
||||
selectItem.set('value-2');
|
||||
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 2');
|
||||
});
|
||||
|
||||
it('sets the value display to the correct value on change when using optgroups',
|
||||
function() {
|
||||
var clayConfig = fixture.clayConfig([
|
||||
{
|
||||
type: 'select',
|
||||
defaultValue: 'value-1',
|
||||
options: [
|
||||
{ label: 'label 1', value: 'value-1' },
|
||||
{ label: 'group', value: [
|
||||
{ label: 'label 2', value: 'value-2' },
|
||||
{ label: 'label 3', value: 'value-3' }
|
||||
]}
|
||||
]
|
||||
}
|
||||
]);
|
||||
var selectItem = clayConfig.getItemsByType('select')[0];
|
||||
var $valueDisplay = selectItem.$element.select('.value');
|
||||
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 1');
|
||||
selectItem.set('value-2');
|
||||
assert.strictEqual($valueDisplay.get('innerHTML'), 'label 2');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
});
|
||||
});
|
||||
+329
-89
@@ -7,34 +7,40 @@ var standardComponents = require('../../src/scripts/components');
|
||||
var sinon = require('sinon');
|
||||
var toSource = require('tosource');
|
||||
|
||||
var accountToken = '0123456789abcdef0123456789abcdef';
|
||||
var watchToken = '0123456789abcdef0123456789abcdef';
|
||||
var activeWatchInfo = {
|
||||
platform: 'chalk',
|
||||
model: 'qemu_platform_chalk',
|
||||
language: 'en_US',
|
||||
firmware: {
|
||||
major: 3,
|
||||
minor: 3,
|
||||
patch: 2,
|
||||
suffix: ''
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {void}
|
||||
*/
|
||||
function stubPebble() {
|
||||
var meta = fixture.meta();
|
||||
|
||||
global.Pebble = {
|
||||
addEventListener: sinon.stub(),
|
||||
openURL: sinon.stub(),
|
||||
sendAppMessage: sinon.stub(),
|
||||
getActiveWatchInfo: sinon.stub().returns(activeWatchInfo),
|
||||
getAccountToken: sinon.stub().returns(accountToken),
|
||||
getWatchToken: sinon.stub().returns(watchToken)
|
||||
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() {
|
||||
@@ -43,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([], {});
|
||||
@@ -74,8 +89,6 @@ describe('Clay', function() {
|
||||
|
||||
it('handles the "showConfiguration" event if autoHandleEvents is not false',
|
||||
function() {
|
||||
this.timeout(10000); // 10 seconds
|
||||
|
||||
stubPebble();
|
||||
var clay = fixture.clay([]);
|
||||
|
||||
@@ -94,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'));
|
||||
@@ -135,6 +152,23 @@ describe('Clay', function() {
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.registerComponent()', function() {
|
||||
it('adds the component to the this.components', function() {
|
||||
var clay = fixture.clay([]);
|
||||
@@ -156,13 +190,13 @@ describe('Clay', function() {
|
||||
* @returns {string}
|
||||
*/
|
||||
function decodeUrl(url) {
|
||||
return atob(decodeURIComponent(url.replace(/^.*?[#,]/, '')));
|
||||
return decodeURIComponent(url.replace(/^.*?[#,]/, ''));
|
||||
}
|
||||
|
||||
describe('string substitutions', function() {
|
||||
var customFn = function() { this.getAllItems(); };
|
||||
var config = fixture.config(['input', 'color']);
|
||||
var settings = { appKey: 'value' };
|
||||
var settings = { messageKey: 'value' };
|
||||
var clay;
|
||||
var html;
|
||||
|
||||
@@ -235,24 +269,60 @@ 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() {
|
||||
@@ -264,19 +334,34 @@ describe('Clay', function() {
|
||||
test4: ['cb-1', 'cb-3'],
|
||||
test5: 12345,
|
||||
test6: [1, 2, 3, 4],
|
||||
test7: [true, false, true]
|
||||
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', 0, 'cb-3', 0],
|
||||
test4: ['cb-1', 'cb-3'],
|
||||
test5: 12345,
|
||||
test6: [1, 2, 3, 4],
|
||||
test7: [1, 0, 1]
|
||||
test7: [1, 0, 1],
|
||||
test8: 1234,
|
||||
test9: [10, 20, 30, 40]
|
||||
};
|
||||
|
||||
assert.deepEqual(clay.getSettings(response), expected);
|
||||
stubMessageKeys(fixture.messageKeysObjToArray(expected));
|
||||
|
||||
assert.deepEqual(
|
||||
clay.getSettings(response),
|
||||
fixture.messageKeysExpected(expected)
|
||||
);
|
||||
});
|
||||
|
||||
it('does not prepare the settings for sendAppMessage if convert is false',
|
||||
@@ -289,7 +374,11 @@ describe('Clay', function() {
|
||||
test4: ['cb-1', 'cb-3'],
|
||||
test5: 12345,
|
||||
test6: [1, 2, 3, 4],
|
||||
test7: [true, false, true]
|
||||
test7: [true, false, true],
|
||||
test8: {
|
||||
precision: 2,
|
||||
value: 12.34
|
||||
}
|
||||
};
|
||||
var response = encodeURIComponent(JSON.stringify(settings));
|
||||
|
||||
@@ -297,41 +386,86 @@ describe('Clay', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.setSettings', function() {
|
||||
it('it writes to localStorage when passing an object',
|
||||
function() {
|
||||
var clay = fixture.clay([]);
|
||||
var settings = {
|
||||
key1: 'value1',
|
||||
key2: 'value2'
|
||||
};
|
||||
var expected = {
|
||||
key1: 'value1',
|
||||
key2: 'value2'
|
||||
};
|
||||
|
||||
clay.setSettings(settings);
|
||||
assert.equal(
|
||||
localStorage.getItem('clay-settings'),
|
||||
JSON.stringify(expected)
|
||||
);
|
||||
});
|
||||
|
||||
it('it writes to localStorage when passing a key and a value',
|
||||
function() {
|
||||
var clay = fixture.clay([]);
|
||||
var expected = {
|
||||
key1: 'value1',
|
||||
key2: 'value2%7Dbreaks'
|
||||
};
|
||||
|
||||
clay.setSettings('key1', 'value1');
|
||||
clay.setSettings('key2', 'value2%7Dbreaks');
|
||||
assert.equal(localStorage.getItem('clay-settings'),
|
||||
JSON.stringify(expected)
|
||||
);
|
||||
});
|
||||
|
||||
it('doesn\'t throw and logs an error if settings in localStorage are broken',
|
||||
function() {
|
||||
var clay = fixture.clay([]);
|
||||
var errorStub = sinon.stub(console, 'error');
|
||||
localStorage.setItem('clay-settings', 'not valid JSON');
|
||||
assert.doesNotThrow(function() {
|
||||
clay.setSettings('key', 'value');
|
||||
});
|
||||
assert(errorStub.calledWithMatch(/SyntaxError/i));
|
||||
errorStub.restore();
|
||||
});
|
||||
});
|
||||
|
||||
describe('.meta', function() {
|
||||
var emptyMeta = {
|
||||
activeWatchInfo: null,
|
||||
accountToken: '',
|
||||
watchToken: ''
|
||||
watchToken: '',
|
||||
userData: {}
|
||||
};
|
||||
|
||||
it('populates the meta in the showConfiguration handler', function() {
|
||||
stubPebble();
|
||||
var clay = fixture.clay([]);
|
||||
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, {
|
||||
activeWatchInfo: activeWatchInfo,
|
||||
accountToken: accountToken,
|
||||
watchToken: watchToken
|
||||
});
|
||||
assert.deepEqual(clay.meta, fixture.meta({userData: userData}));
|
||||
});
|
||||
|
||||
it('populates the meta in the ready handler', function() {
|
||||
stubPebble();
|
||||
var clay = fixture.clay([], null, {autoHandleEvents: false});
|
||||
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, {
|
||||
activeWatchInfo: activeWatchInfo,
|
||||
accountToken: accountToken,
|
||||
watchToken: watchToken
|
||||
});
|
||||
assert.deepEqual(clay.meta, fixture.meta({userData: userData}));
|
||||
});
|
||||
|
||||
it('populates the meta with with empty values when there is no Pebble global',
|
||||
@@ -344,49 +478,155 @@ describe('Clay', function() {
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+408
-32
@@ -12,10 +12,11 @@ describe('ClayConfig', function() {
|
||||
it('defines read-only properties', function() {
|
||||
var properties = [
|
||||
'EVENTS',
|
||||
'getItemByAppKey',
|
||||
'getItemByMessageKey',
|
||||
'getItemById',
|
||||
'getItemsByType',
|
||||
'getSettings',
|
||||
'getItemsByGroup',
|
||||
'serialize',
|
||||
'registerComponent',
|
||||
'build',
|
||||
'on',
|
||||
@@ -27,6 +28,262 @@ describe('ClayConfig', function() {
|
||||
checkReadOnly(clayConfig, properties);
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests if the provided capabilities of the platform match
|
||||
* @param {string} platform
|
||||
* @param {number} fwMajor
|
||||
* @param {number} fwMinor
|
||||
* @param {Array} capabilities
|
||||
* @param {number} expected
|
||||
* @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, ['COMPASS'], 1);
|
||||
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, ['DISPLAY_260x260_ROUND'], 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', 2, 9, ['PLATFORM_FLINT'], 0);
|
||||
testCapabilities('aplite', 2, 9, ['PLATFORM_GABBRO'], 0);
|
||||
|
||||
testCapabilities('aplite', 3, 10, ['BW'], 1);
|
||||
testCapabilities('aplite', 3, 10, ['COLOR'], 0);
|
||||
testCapabilities('aplite', 3, 10, ['COMPASS'], 1);
|
||||
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, ['DISPLAY_260x260_ROUND'], 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('aplite', 3, 10, ['PLATFORM_FLINT'], 0);
|
||||
testCapabilities('aplite', 3, 10, ['PLATFORM_GABBRO'], 0);
|
||||
|
||||
testCapabilities('basalt', 3, 10, ['BW'], 0);
|
||||
testCapabilities('basalt', 3, 10, ['COLOR'], 1);
|
||||
testCapabilities('basalt', 3, 10, ['COMPASS'], 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, ['DISPLAY_260x260_ROUND'], 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('basalt', 3, 10, ['PLATFORM_FLINT'], 0);
|
||||
testCapabilities('basalt', 3, 10, ['PLATFORM_GABBRO'], 0);
|
||||
|
||||
testCapabilities('chalk', 3, 10, ['BW'], 0);
|
||||
testCapabilities('chalk', 3, 10, ['COLOR'], 1);
|
||||
testCapabilities('chalk', 3, 10, ['COMPASS'], 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, ['DISPLAY_260x260_ROUND'], 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('chalk', 3, 10, ['PLATFORM_FLINT'], 0);
|
||||
testCapabilities('chalk', 3, 10, ['PLATFORM_GABBRO'], 0);
|
||||
|
||||
testCapabilities('diorite', 3, 10, ['BW'], 1);
|
||||
testCapabilities('diorite', 3, 10, ['COLOR'], 0);
|
||||
testCapabilities('diorite', 3, 10, ['COMPASS'], 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, ['DISPLAY_260x260_ROUND'], 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('diorite', 3, 10, ['PLATFORM_FLINT'], 0);
|
||||
testCapabilities('diorite', 3, 10, ['PLATFORM_GABBRO'], 0);
|
||||
|
||||
testCapabilities('emery', 3, 10, ['BW'], 0);
|
||||
testCapabilities('emery', 3, 10, ['COLOR'], 1);
|
||||
testCapabilities('emery', 3, 10, ['COMPASS'], 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, ['DISPLAY_260x260_ROUND'], 0);
|
||||
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('emery', 3, 10, ['PLATFORM_FLINT'], 0);
|
||||
testCapabilities('emery', 3, 10, ['PLATFORM_GABBRO'], 0);
|
||||
|
||||
testCapabilities('flint', 3, 10, ['BW'], 1);
|
||||
testCapabilities('flint', 3, 10, ['COLOR'], 0);
|
||||
testCapabilities('flint', 3, 10, ['COMPASS'], 1);
|
||||
testCapabilities('flint', 3, 10, ['MICROPHONE'], 1);
|
||||
testCapabilities('flint', 3, 10, ['SMARTSTRAP'], 0);
|
||||
testCapabilities('flint', 3, 4, ['SMARTSTRAP'], 0);
|
||||
testCapabilities('flint', 3, 3, ['SMARTSTRAP'], 0);
|
||||
testCapabilities('flint', 3, 10, ['SMARTSTRAP_POWER'], 0);
|
||||
testCapabilities('flint', 3, 4, ['SMARTSTRAP_POWER'], 0);
|
||||
testCapabilities('flint', 3, 3, ['SMARTSTRAP_POWER'], 0);
|
||||
testCapabilities('flint', 3, 10, ['HEALTH'], 1);
|
||||
testCapabilities('flint', 3, 9, ['HEALTH'], 0);
|
||||
testCapabilities('flint', 3, 10, ['RECT'], 1);
|
||||
testCapabilities('flint', 3, 10, ['ROUND'], 0);
|
||||
testCapabilities('flint', 3, 10, ['DISPLAY_144x168'], 1);
|
||||
testCapabilities('flint', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
|
||||
testCapabilities('flint', 3, 10, ['DISPLAY_200x228'], 0);
|
||||
testCapabilities('flint', 3, 10, ['DISPLAY_260x260_ROUND'], 0);
|
||||
testCapabilities('flint', 3, 10, ['PLATFORM_APLITE'], 0);
|
||||
testCapabilities('flint', 3, 10, ['PLATFORM_BASALT'], 0);
|
||||
testCapabilities('flint', 3, 10, ['PLATFORM_CHALK'], 0);
|
||||
testCapabilities('flint', 3, 10, ['PLATFORM_DIORITE'], 0);
|
||||
testCapabilities('flint', 3, 10, ['PLATFORM_EMERY'], 0);
|
||||
testCapabilities('flint', 3, 10, ['PLATFORM_FLINT'], 1);
|
||||
testCapabilities('flint', 3, 10, ['PLATFORM_GABBRO'], 0);
|
||||
|
||||
testCapabilities('gabbro', 3, 10, ['BW'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['COLOR'], 1);
|
||||
testCapabilities('gabbro', 3, 10, ['COMPASS'], 1);
|
||||
testCapabilities('gabbro', 3, 10, ['MICROPHONE'], 1);
|
||||
testCapabilities('gabbro', 3, 10, ['SMARTSTRAP'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['SMARTSTRAP_POWER'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['HEALTH'], 1);
|
||||
testCapabilities('gabbro', 3, 10, ['RECT'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['ROUND'], 1);
|
||||
testCapabilities('gabbro', 3, 10, ['DISPLAY_144x168'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['DISPLAY_180x180_ROUND'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['DISPLAY_200x228'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['DISPLAY_260x260_ROUND'], 1);
|
||||
testCapabilities('gabbro', 3, 10, ['PLATFORM_APLITE'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['PLATFORM_BASALT'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['PLATFORM_CHALK'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['PLATFORM_DIORITE'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['PLATFORM_EMERY'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['PLATFORM_FLINT'], 0);
|
||||
testCapabilities('gabbro', 3, 10, ['PLATFORM_GABBRO'], 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']);
|
||||
@@ -36,10 +293,11 @@ describe('ClayConfig', function() {
|
||||
|
||||
describe('throws when trying to run methods before being built', function() {
|
||||
[
|
||||
'getItemByAppKey',
|
||||
'getItemByMessageKey',
|
||||
'getItemById',
|
||||
'getItemsByType',
|
||||
'getSettings'
|
||||
'getItemsByGroup',
|
||||
'serialize'
|
||||
].forEach(function(method) {
|
||||
it('.' + method + '()', function() {
|
||||
var clayConfig = fixtures.clayConfig(['input', 'text'], false);
|
||||
@@ -64,14 +322,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]
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -99,20 +360,40 @@ describe('ClayConfig', function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe('.getSettings()', function() {
|
||||
describe('.getItemsByGroup()', function() {
|
||||
it('it returns the correct items', function() {
|
||||
var config = fixtures.config([
|
||||
{type: 'input', id: 'g1-0', group: 'group1'},
|
||||
{type: 'text', id: 'g2-0', group: 'group2'},
|
||||
{type: 'input', id: 'g1-1', group: 'group1'}
|
||||
]);
|
||||
|
||||
var clayConfig = fixtures.clayConfig(config);
|
||||
assert.deepEqual(clayConfig.getItemsByGroup('group1'), [
|
||||
clayConfig.getItemById('g1-0'),
|
||||
clayConfig.getItemById('g1-1')
|
||||
]);
|
||||
assert.deepEqual(clayConfig.getItemsByGroup('group2'), [
|
||||
clayConfig.getItemById('g2-0')
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.serialize()', function() {
|
||||
it('returns the correct settings', function() {
|
||||
var config = [
|
||||
{type: 'input', appKey: 'test1', defaultValue: 'default val'},
|
||||
{type: 'select', appKey: 'test2', options: [
|
||||
{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', appKey: 'test3'},
|
||||
{type: 'checkboxgroup', appKey: 'test4', options: [
|
||||
{label: 'label-1', value: 'cb-1'},
|
||||
{label: 'label-2', value: 'cb-2'},
|
||||
{label: 'label-2', value: 'cb-3'}
|
||||
]}
|
||||
{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'
|
||||
@@ -120,29 +401,62 @@ describe('ClayConfig', function() {
|
||||
|
||||
var clayConfig = fixtures.clayConfig(config, true, true, settings);
|
||||
|
||||
assert.deepEqual(clayConfig.getSettings(), {
|
||||
test1: 'default val',
|
||||
test2: 'val-2',
|
||||
test3: false,
|
||||
test4: []
|
||||
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.getItemByAppKey('test4').set(['cb-1', 'cb-3']);
|
||||
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: true,
|
||||
test4: ['cb-1', 'cb-3']
|
||||
test4: [true, false, true],
|
||||
test5: 12.5
|
||||
});
|
||||
|
||||
// make sure the result of getSettings() can actually be fed back in to
|
||||
// a new instance of ClayConfig
|
||||
assert.doesNotThrow(function() {
|
||||
settings = clayConfig.getSettings();
|
||||
fixtures.clayConfig(config, true, true, settings);
|
||||
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'}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -240,7 +554,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() {
|
||||
|
||||
+12
-10
@@ -12,7 +12,7 @@ describe('ClayItem', function() {
|
||||
it('defines read-only properties', function() {
|
||||
var properties = [
|
||||
'id',
|
||||
'appKey',
|
||||
'messageKey',
|
||||
'config',
|
||||
'$element',
|
||||
'$manipulatorTarget',
|
||||
@@ -53,24 +53,25 @@ 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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -143,43 +143,67 @@ describe('manipulators', function() {
|
||||
}
|
||||
|
||||
describe('html', function() {
|
||||
testSetGet('text', 'test123');
|
||||
testSetGet('button', '<span>some HTML</span>');
|
||||
testShow('text');
|
||||
testHide('text');
|
||||
var type = 'text';
|
||||
testSetGet(type, 'test123');
|
||||
testSetGet(type, '<span>some HTML</span>');
|
||||
testShow(type);
|
||||
testHide(type);
|
||||
});
|
||||
|
||||
describe('button', function() {
|
||||
testSetGet('button', 'test123');
|
||||
testSetGet('button', '<span>some HTML</span>');
|
||||
testDisable('button');
|
||||
testEnable('button');
|
||||
testShow('button');
|
||||
testHide('button');
|
||||
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');
|
||||
testSetGet('input', 1234, '1234');
|
||||
testDisable('input');
|
||||
testEnable('input');
|
||||
testShow('text');
|
||||
testHide('text');
|
||||
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({type: 'toggle', defaultValue: false}, 1, true);
|
||||
testSetGet({type: 'toggle', defaultValue: false}, true);
|
||||
testSetGet({type: 'toggle', defaultValue: true}, 0, false);
|
||||
testSetGet({type: 'toggle', defaultValue: true}, false);
|
||||
testDisable('toggle');
|
||||
testEnable('toggle');
|
||||
testShow('toggle');
|
||||
testHide('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: [
|
||||
@@ -188,48 +212,52 @@ describe('manipulators', function() {
|
||||
{ label: '3', value: 'three "quote' }
|
||||
]
|
||||
};
|
||||
testSetGet(item, 'one');
|
||||
testSetGet(item, 'two');
|
||||
testSetGet(item, 'three "quote');
|
||||
testDisable(item);
|
||||
testEnable(item);
|
||||
testShow(item);
|
||||
testHide(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,
|
||||
defaultValue: ['two'],
|
||||
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);
|
||||
testShow(item);
|
||||
testHide(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({type: 'color', defaultValue: 0x00ff00}, '', 0x000000);
|
||||
testSetGet({type: 'color', defaultValue: 0x00ff00}, false, 0x000000);
|
||||
testSetGet({type: 'color', defaultValue: 0x00ff00}, undefined, 0x000000);
|
||||
testDisable('color');
|
||||
testEnable('color');
|
||||
testShow('color');
|
||||
testHide('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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -26,60 +26,5 @@ describe('Utils', function() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.prepareForAppMessage', function() {
|
||||
it('converts an array correctly when array contains strings', function() {
|
||||
assert.deepEqual(
|
||||
utils.prepareForAppMessage(['one', 'two']),
|
||||
['one', 0, 'two', 0]
|
||||
);
|
||||
});
|
||||
|
||||
it('converts an array correctly when array contains numbers', function() {
|
||||
assert.deepEqual(utils.prepareForAppMessage([1, 2, 3]), [1, 2, 3]);
|
||||
});
|
||||
|
||||
it('converts an array correctly when array contains booleans', function() {
|
||||
assert.deepEqual(utils.prepareForAppMessage([true, false, true]), [1, 0, 1]);
|
||||
});
|
||||
|
||||
it('converts booleans to ints', function() {
|
||||
assert.strictEqual(utils.prepareForAppMessage(false), 0);
|
||||
assert.strictEqual(utils.prepareForAppMessage(true), 1);
|
||||
});
|
||||
|
||||
it('leaves strings alone', function() {
|
||||
assert.strictEqual(utils.prepareForAppMessage('test'), 'test');
|
||||
});
|
||||
|
||||
it('leaves numbers alone', function() {
|
||||
assert.strictEqual(utils.prepareForAppMessage(123), 123);
|
||||
});
|
||||
});
|
||||
|
||||
describe('.prepareSettingsForAppMessage', function() {
|
||||
it('converts the settings correctly', function() {
|
||||
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]
|
||||
};
|
||||
var expected = {
|
||||
test1: 0,
|
||||
test2: 'val-2',
|
||||
test3: 1,
|
||||
test4: ['cb-1', 0, 'cb-3', 0],
|
||||
test5: 12345,
|
||||
test6: [1, 2, 3, 4],
|
||||
test7: [1, 0, 1]
|
||||
};
|
||||
|
||||
assert.deepEqual(utils.prepareSettingsForAppMessage(settings), expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
import Clay = require('../index');
|
||||
|
||||
// --- Exported types are accessible via the Clay namespace ---
|
||||
|
||||
const configItem: Clay.ClayConfigItem = {
|
||||
type: 'input',
|
||||
messageKey: 'name',
|
||||
defaultValue: '',
|
||||
label: 'Name',
|
||||
attributes: { placeholder: 'Enter name' },
|
||||
capabilities: ['COLOR'],
|
||||
group: 'appearance',
|
||||
};
|
||||
|
||||
const meta: Clay.ClayMeta = {
|
||||
activeWatchInfo: {
|
||||
platform: 'basalt',
|
||||
model: 'qemu_platform_basalt',
|
||||
language: 'en_US',
|
||||
firmware: { major: 4, minor: 3, patch: 0, suffix: '' },
|
||||
},
|
||||
accountToken: 'abc',
|
||||
watchToken: 'def',
|
||||
userData: {},
|
||||
};
|
||||
|
||||
const options: Clay.ClayOptions = {
|
||||
autoHandleEvents: true,
|
||||
userData: { foo: 'bar' },
|
||||
};
|
||||
|
||||
const manipulator: Clay.ClayManipulator = {
|
||||
get() { return this.$manipulatorTarget.get('value'); },
|
||||
set(value) {
|
||||
this.$manipulatorTarget.set('value', value);
|
||||
return this;
|
||||
},
|
||||
disable() { return this; },
|
||||
enable() { return this; },
|
||||
hide() { return this; },
|
||||
show() { return this; },
|
||||
};
|
||||
|
||||
const component: Clay.ClayComponent = {
|
||||
name: 'my-component',
|
||||
template: '<div></div>',
|
||||
manipulator,
|
||||
defaults: { label: 'Default' },
|
||||
};
|
||||
|
||||
// --- Constructor ---
|
||||
|
||||
const clay = new Clay(
|
||||
[configItem, { type: 'heading', defaultValue: 'My App' }],
|
||||
function() {
|
||||
// `this` is ClayConfig — verify event methods
|
||||
const self: Clay.ClayConfig = this;
|
||||
|
||||
const handler = (..._args: unknown[]) => {};
|
||||
this.on('AFTER_BUILD', handler);
|
||||
this.off(handler);
|
||||
this.trigger('AFTER_BUILD');
|
||||
|
||||
// Verify chaining returns the same type
|
||||
this.on('BEFORE_BUILD', handler).off(handler);
|
||||
|
||||
// Verify EVENTS constants
|
||||
const events: Clay.ClayConfigEvents = this.EVENTS;
|
||||
const beforeBuild: 'BEFORE_BUILD' = events.BEFORE_BUILD;
|
||||
const afterBuild: 'AFTER_BUILD' = events.AFTER_BUILD;
|
||||
const beforeDestroy: 'BEFORE_DESTROY' = events.BEFORE_DESTROY;
|
||||
const afterDestroy: 'AFTER_DESTROY' = events.AFTER_DESTROY;
|
||||
|
||||
// Verify build/destroy return ClayConfig for chaining
|
||||
this.build().destroy().build();
|
||||
|
||||
// Verify item getters
|
||||
const allItems: Clay.ClayItem[] = this.getAllItems();
|
||||
const byKey: Clay.ClayItem = this.getItemByMessageKey('bg_color');
|
||||
const byId: Clay.ClayItem = this.getItemById('my-id');
|
||||
const byType: Clay.ClayItem[] = this.getItemsByType('color');
|
||||
const byGroup: Clay.ClayItem[] = this.getItemsByGroup('appearance');
|
||||
|
||||
// Verify ClayItem properties
|
||||
const item = byKey;
|
||||
const itemId: string | null = item.id;
|
||||
const itemMsgKey: string | null = item.messageKey;
|
||||
const itemConfig: Clay.ClayConfigItem = item.config;
|
||||
const $el: Clay.M = item.$element;
|
||||
const $target: Clay.M = item.$manipulatorTarget;
|
||||
|
||||
// Verify ClayItem manipulator methods
|
||||
const value: unknown = item.get();
|
||||
const afterSet: Clay.ClayItem = item.set('new value');
|
||||
const afterDisable: Clay.ClayItem = item.disable();
|
||||
const afterEnable: Clay.ClayItem = item.enable();
|
||||
const afterHide: Clay.ClayItem = item.hide();
|
||||
const afterShow: Clay.ClayItem = item.show();
|
||||
|
||||
// Verify ClayItem event methods
|
||||
item.on('change', handler).off(handler);
|
||||
item.trigger('change');
|
||||
|
||||
// Verify ClayItem initialize
|
||||
const afterInit: Clay.ClayItem = item.initialize(this);
|
||||
|
||||
// Verify serialize
|
||||
const serialised: Record<string, { value: unknown; precision?: number }> =
|
||||
this.serialize();
|
||||
|
||||
// Verify meta
|
||||
const configMeta: Clay.ClayMeta = this.meta;
|
||||
|
||||
// Verify registerComponent on instance
|
||||
const registered: boolean = this.registerComponent(component);
|
||||
},
|
||||
options
|
||||
);
|
||||
|
||||
// Constructor with null customFn
|
||||
const clay2 = new Clay([{ type: 'heading' }], null);
|
||||
|
||||
// Constructor with no options
|
||||
const clay3 = new Clay([{ type: 'heading' }]);
|
||||
|
||||
// --- Instance properties ---
|
||||
|
||||
const config: Clay.ClayConfigItem[] = clay.config;
|
||||
const version: string = clay.version;
|
||||
const components: Record<string, Clay.ClayComponent> = clay.components;
|
||||
const clayMeta: Clay.ClayMeta = clay.meta;
|
||||
const customFn: (this: Clay.ClayConfig) => void = clay.customFn;
|
||||
|
||||
// --- Instance methods ---
|
||||
|
||||
clay.registerComponent(component);
|
||||
|
||||
// String manipulator name
|
||||
clay.registerComponent({
|
||||
name: 'another',
|
||||
template: '<input>',
|
||||
manipulator: 'val',
|
||||
});
|
||||
|
||||
const url: string = clay.generateUrl();
|
||||
const settings: Record<string, unknown> = clay.getSettings('{}');
|
||||
const rawSettings: Record<string, unknown> = clay.getSettings('{}', false);
|
||||
clay.setSettings('key', 'value');
|
||||
clay.setSettings({ key: 'value' });
|
||||
|
||||
// --- Static methods ---
|
||||
|
||||
const uri: string = Clay.encodeDataUri('<html></html>');
|
||||
const uriWithPrefix: string = Clay.encodeDataUri('<html></html>', 'http://example.com/#');
|
||||
const prepared: number | string | (number | string)[] = Clay.prepareForAppMessage(42);
|
||||
const appMsg: Record<number, number | string> =
|
||||
Clay.prepareSettingsForAppMessage({ bg_color: { value: 255 } });
|
||||
|
||||
// --- M interface ---
|
||||
|
||||
function testM(el: Clay.M) {
|
||||
const len: number = el.length;
|
||||
const htmlEl: HTMLElement = el[0];
|
||||
const added: Clay.M = el.add('<span>');
|
||||
const afterSet: Clay.M = el.set('className', 'active');
|
||||
const val: unknown = el.get('value');
|
||||
const selected: Clay.M = el.select('.child');
|
||||
el.on('click', () => {});
|
||||
el.each((element: HTMLElement, index: number) => {});
|
||||
}
|
||||
|
||||
// --- PebbleActiveWatchInfo ---
|
||||
|
||||
const watchInfo: Clay.PebbleActiveWatchInfo = {
|
||||
platform: 'chalk',
|
||||
model: 'qemu_platform_chalk',
|
||||
language: 'en_US',
|
||||
firmware: { major: 4, minor: 3, patch: 0, suffix: '' },
|
||||
};
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"module": "node16",
|
||||
"allowJs": true,
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"rootDir": ".",
|
||||
"outDir": "./dist-types",
|
||||
"strict": false,
|
||||
"skipLibCheck": true,
|
||||
"moduleResolution": "node16",
|
||||
"typeRoots": ["./types", "./node_modules/@types"]
|
||||
},
|
||||
"include": [
|
||||
"src/scripts/lib/clay-config.js",
|
||||
"src/scripts/lib/clay-events.js",
|
||||
"src/scripts/lib/clay-item.js",
|
||||
"src/scripts/lib/component-registry.js",
|
||||
"src/scripts/lib/manipulators.js",
|
||||
"src/scripts/lib/utils.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"test",
|
||||
"dev",
|
||||
"tmp",
|
||||
"src/scripts/config-page.js",
|
||||
"src/scripts/components",
|
||||
"src/scripts/vendor",
|
||||
"src/js"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2015",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node10",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"ignoreDeprecations": "6.0"
|
||||
},
|
||||
"include": [
|
||||
"test/type-checks.ts"
|
||||
]
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module './tmp/config-page.html' {
|
||||
const html: string;
|
||||
export = html;
|
||||
}
|
||||
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
declare module 'message_keys' {
|
||||
const messageKeys: { [key: string]: number };
|
||||
export = messageKeys;
|
||||
}
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user