From b1726354053ae9144d518a6182f56f830862b699 Mon Sep 17 00:00:00 2001 From: Arney Date: Fri, 2 Jan 2026 20:23:14 +0100 Subject: [PATCH 1/3] Add support for watch-specific settings in localStorage --- README.md | 8 ++++++ index.js | 72 +++++++++++++++++++++++++++++++++++----------- test/spec/index.js | 32 +++++++++++++++++++++ 3 files changed, 95 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 7fffada..2203eb1 100755 --- a/README.md +++ b/README.md @@ -173,6 +173,7 @@ Standard text input field. | attributes | object | An object containing HTML attributes to set on the input field. Set `type` to values such as "email", "time", "date" etc to adjust the behavior of the component. | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example @@ -211,6 +212,7 @@ Switch for a single item. | description | string | Optional sub-text to include below the component | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example @@ -245,6 +247,7 @@ A dropdown menu containing multiple options. | options | array of objects | The options you want to appear in the dropdown menu. Each option is an object with a `label` and `value` property. | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example @@ -354,6 +357,7 @@ The color picker will automatically show a different layout depending on the wat | allowGray | boolean | Optional. Set this to `true` to include gray (`#AAAAAA`) in the color picker for aplite running on firmware 3 and above. This is optional because only a subset of the drawing operations support gray on aplite. Defaults to `false` | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example @@ -419,6 +423,7 @@ A list of options allowing the user can only choose one option to submit. | options | array of objects | The options you want to appear in the radio group. Each option is an object with a `label` and `value` property. | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example @@ -465,6 +470,7 @@ A list of options where a user may choose more than one option to submit. | options | array of strings | The labels for each checkbox you want to appear in the checkbox group. | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example @@ -497,6 +503,7 @@ In the above example, Sushi and Burgers will be selected by default. | description | string | Optional sub-text to include below the component | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example @@ -536,6 +543,7 @@ you must just remember to divide the received value on the watch accordingly. | description | string | Optional sub-text to include below the component | | capabilities | array | Array of features that the connected watch must have for this item to be present | | group | string | Set this to allow this item, along with other items sharing the same group to be looked up using `Clay.getItemsByGroup()` in your [custom function](#custom-function) | +| persistPerWatch | boolean | Optional. If set to `true`, the selected value will be stored separately for each watch model. Defaults to `false`. | | ##### Example diff --git a/index.js b/index.js index 6b19cd0..8e96d34 100755 --- a/index.js +++ b/index.js @@ -41,6 +41,7 @@ function Clay(config, customFn, options) { userData: {} }; self.version = version; + self.watchSpecificKeys = []; /** * Populate the meta with data from the Pebble object. Make sure to run this inside @@ -120,7 +121,14 @@ function Clay(config, customFn, options) { self.registerComponent(standardComponents[item.type]); }); - // validate config against teh use of appKeys + // extract watch specific keys + _scanConfig(self.config, function(item) { + return item.persistPerWatch === true; + }, function(item) { + self.watchSpecificKeys.push(item.messageKey); + }); + + // validate config against the use of appKeys _scanConfig(self.config, function(item) { return item.appKey; }, function() { @@ -129,6 +137,48 @@ function Clay(config, customFn, options) { }); } +Clay.prototype._getStoredSettings = function() { + var self = this; + var settings = {}; + var watchSpecificSettings = {}; + + try { + settings = JSON.parse(localStorage.getItem('clay-settings')) || {}; + if (self.meta.watchToken) { + watchSpecificSettings = JSON.parse( + localStorage.getItem('clay-watch-' + self.meta.watchToken) + ) || {}; + } + } catch (e) { + console.error(e.toString()); + } + + Object.keys(watchSpecificSettings).forEach(function(key) { + if (self.watchSpecificKeys.includes(key)) { + settings[key] = watchSpecificSettings[key]; + } + }); + + return settings; +}; + +Clay.prototype._setStoredSettings = function(settings) { + var self = this; + var watchSpecificSettings = {}; + + if (self.meta.watchToken) { + Object.keys(settings).forEach(function(key) { + if (self.watchSpecificKeys.includes(key)) { + watchSpecificSettings[key] = settings[key]; + } + }); + + localStorage.setItem('clay-watch-' + this.meta.watchToken, + JSON.stringify(watchSpecificSettings)); + } + localStorage.setItem('clay-settings', JSON.stringify(settings)); +}; + /** * Register a component to Clay. * @param {Object} component - the clay component to register @@ -150,16 +200,10 @@ Clay.prototype.registerComponent = function(component) { * @return {string} */ Clay.prototype.generateUrl = function() { - var settings = {}; + var settings = this._getStoredSettings(); var emulator = !Pebble || Pebble.platform === 'pypkjs'; var returnTo = emulator ? '$$$RETURN_TO$$$' : 'pebblejs://close#'; - try { - settings = JSON.parse(localStorage.getItem('clay-settings')) || {}; - } catch (e) { - console.error(e.toString()); - } - var compiledHtml = configPageHtml .replace('$$RETURN_TO$$', returnTo) .replace('$$CUSTOM_FN$$', toSource(this.customFn)) @@ -208,7 +252,7 @@ Clay.prototype.getSettings = function(response, convert) { } }); - localStorage.setItem('clay-settings', JSON.stringify(settingsStorage)); + this._setStoredSettings(settingsStorage); return convert === false ? settings : Clay.prepareSettingsForAppMessage(settings); }; @@ -226,13 +270,7 @@ Clay.prototype.getSettings = function(response, convert) { * @return {undefined} */ Clay.prototype.setSettings = function(key, value) { - var settingsStorage = {}; - - try { - settingsStorage = JSON.parse(localStorage.getItem('clay-settings')) || {}; - } catch (e) { - console.error(e.toString()); - } + var settingsStorage = this._getStoredSettings(); if (typeof key === 'object') { var settings = key; @@ -243,7 +281,7 @@ Clay.prototype.setSettings = function(key, value) { settingsStorage[key] = value; } - localStorage.setItem('clay-settings', JSON.stringify(settingsStorage)); + this._setStoredSettings(settingsStorage); }; /** diff --git a/test/spec/index.js b/test/spec/index.js index 0088eb9..692ab0f 100644 --- a/test/spec/index.js +++ b/test/spec/index.js @@ -409,6 +409,8 @@ describe('Clay', function() { it('it writes to localStorage when passing a key and a value', function() { var clay = fixture.clay([]); + clay.meta.watchToken = '0123456789abcdef0123456789abcdef'; + var expected = { key1: 'value1', key2: 'value2%7Dbreaks' @@ -419,8 +421,38 @@ describe('Clay', function() { assert.equal(localStorage.getItem('clay-settings'), JSON.stringify(expected) ); + + assert.equal( + localStorage.getItem('clay-watch-0123456789abcdef0123456789abcdef'), + null); }); + it('it writes watch specific keys to a separate localStorage item as well', + function() { + + var clay = fixture.clay([{messageKey: 'key1', persistPerWatch: true}]); + clay.meta.watchToken = '0123456789abcdef0123456789abcdef'; + + var expectedAllSettings = { + key1: 'value1', + key2: 'value2%7Dbreaks' + }; + + var expectedWatchSettings = { + key1: 'value1' + }; + + clay.setSettings('key1', 'value1'); + clay.setSettings('key2', 'value2%7Dbreaks'); + assert.equal(localStorage.getItem('clay-settings'), + JSON.stringify(expectedAllSettings) + ); + + assert.equal( + localStorage.getItem('clay-watch-0123456789abcdef0123456789abcdef'), + JSON.stringify(expectedWatchSettings)); + }); + it('doesn\'t throw and logs an error if settings in localStorage are broken', function() { var clay = fixture.clay([]); From 6dfc46672551e268aadeb7d21ec334206835d74b Mon Sep 17 00:00:00 2001 From: Arney Date: Sat, 3 Jan 2026 10:21:32 +0100 Subject: [PATCH 2/3] Only store each setting once Store each setting either in a watch specific setting or in a global setting. When no setting is stored for a watch-specific key, clay will use the default value. --- index.js | 34 +++++++++++++++------------------- test/spec/index.js | 7 +++---- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/index.js b/index.js index 8e96d34..96acc4a 100755 --- a/index.js +++ b/index.js @@ -139,44 +139,40 @@ function Clay(config, customFn, options) { Clay.prototype._getStoredSettings = function() { var self = this; - var settings = {}; + var globalSettings = {}; var watchSpecificSettings = {}; + var watchStorageKey = 'clay-watch-' + (self.meta.watchToken || 'default'); try { - settings = JSON.parse(localStorage.getItem('clay-settings')) || {}; - if (self.meta.watchToken) { - watchSpecificSettings = JSON.parse( - localStorage.getItem('clay-watch-' + self.meta.watchToken) - ) || {}; - } + globalSettings = JSON.parse(localStorage.getItem('clay-settings')) || {}; + watchSpecificSettings = JSON.parse(localStorage.getItem(watchStorageKey)) || {}; } catch (e) { console.error(e.toString()); } - Object.keys(watchSpecificSettings).forEach(function(key) { - if (self.watchSpecificKeys.includes(key)) { - settings[key] = watchSpecificSettings[key]; - } - }); - - return settings; + return Object.assign({}, globalSettings, watchSpecificSettings); }; Clay.prototype._setStoredSettings = function(settings) { var self = this; + var globalSettings = {}; var watchSpecificSettings = {}; + var watchStorageKey = 'clay-watch-' + (self.meta.watchToken || 'default'); - if (self.meta.watchToken) { + if (self.watchSpecificKeys.length) { Object.keys(settings).forEach(function(key) { if (self.watchSpecificKeys.includes(key)) { watchSpecificSettings[key] = settings[key]; + } else { + globalSettings[key] = settings[key]; } }); - - localStorage.setItem('clay-watch-' + this.meta.watchToken, - JSON.stringify(watchSpecificSettings)); + } else { + globalSettings = settings; } - localStorage.setItem('clay-settings', JSON.stringify(settings)); + + localStorage.setItem(watchStorageKey, JSON.stringify(watchSpecificSettings)); + localStorage.setItem('clay-settings', JSON.stringify(globalSettings)); }; /** diff --git a/test/spec/index.js b/test/spec/index.js index 692ab0f..6f93d5d 100644 --- a/test/spec/index.js +++ b/test/spec/index.js @@ -424,7 +424,7 @@ describe('Clay', function() { assert.equal( localStorage.getItem('clay-watch-0123456789abcdef0123456789abcdef'), - null); + JSON.stringify({})); }); it('it writes watch specific keys to a separate localStorage item as well', @@ -433,8 +433,7 @@ describe('Clay', function() { var clay = fixture.clay([{messageKey: 'key1', persistPerWatch: true}]); clay.meta.watchToken = '0123456789abcdef0123456789abcdef'; - var expectedAllSettings = { - key1: 'value1', + var expectedGlobalSettings = { key2: 'value2%7Dbreaks' }; @@ -445,7 +444,7 @@ describe('Clay', function() { clay.setSettings('key1', 'value1'); clay.setSettings('key2', 'value2%7Dbreaks'); assert.equal(localStorage.getItem('clay-settings'), - JSON.stringify(expectedAllSettings) + JSON.stringify(expectedGlobalSettings) ); assert.equal( From 2bc9b22a507febd9a918845d6f22cdfb93a35125 Mon Sep 17 00:00:00 2001 From: Arney Date: Sat, 3 Jan 2026 15:51:56 +0100 Subject: [PATCH 3/3] Rename localStorage fallback watch key use 'fallback' instead of 'default' to better describe this (theoretical) situation --- index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.js b/index.js index 96acc4a..a915241 100755 --- a/index.js +++ b/index.js @@ -141,7 +141,7 @@ Clay.prototype._getStoredSettings = function() { var self = this; var globalSettings = {}; var watchSpecificSettings = {}; - var watchStorageKey = 'clay-watch-' + (self.meta.watchToken || 'default'); + var watchStorageKey = 'clay-watch-' + (self.meta.watchToken || 'fallback'); try { globalSettings = JSON.parse(localStorage.getItem('clay-settings')) || {}; @@ -157,7 +157,7 @@ Clay.prototype._setStoredSettings = function(settings) { var self = this; var globalSettings = {}; var watchSpecificSettings = {}; - var watchStorageKey = 'clay-watch-' + (self.meta.watchToken || 'default'); + var watchStorageKey = 'clay-watch-' + (self.meta.watchToken || 'fallback'); if (self.watchSpecificKeys.length) { Object.keys(settings).forEach(function(key) {