From e13fc6701f9178f95f1b37a57b8a28887ed97cb7 Mon Sep 17 00:00:00 2001 From: Simo Kinnunen Date: Fri, 4 Jul 2014 22:59:29 +0900 Subject: [PATCH] Save user settings to the database and embed them to the template for fast access. --- bower.json | 1 - lib/db/api.js | 13 ++ lib/roles/websocket.js | 7 + res/app/components/stf/settings/index.js | 17 +-- .../stf/settings/settings-service.js | 124 ++++++++++++------ res/app/layout/layout-controller.js | 2 +- res/app/menu/menu-controller.js | 13 +- .../settings/language/language-controller.js | 20 +-- res/app/settings/language/language-service.js | 88 +++++-------- .../local/local-settings-controller.js | 5 +- res/app/settings/local/local-settings.jade | 3 - 11 files changed, 152 insertions(+), 141 deletions(-) diff --git a/bower.json b/bower.json index 5b04f623..a42f2be9 100644 --- a/bower.json +++ b/bower.json @@ -13,7 +13,6 @@ "oboe": "~1.15.1", "fa-borderlayout": "~0.3.1-beta2", "Snap.svg": "~0.3.0", - "angular-localForage": "~0.2.6", "ng-table": "git://github.com/esvit/ng-table.git#bd9ec42172389257fcd312330013302b2696ac2a", "jquery.terminal": "~0.8.7", "stf-site": "~0.4.1", diff --git a/lib/db/api.js b/lib/db/api.js index 7b1d0a4d..a09f62d6 100644 --- a/lib/db/api.js +++ b/lib/db/api.js @@ -25,6 +25,7 @@ dbapi.saveUserAfterLogin = function(user) { , lastLoggedInAt: r.now() , createdAt: r.now() , forwards: [] + , settings: {} })) } return stats @@ -35,6 +36,18 @@ dbapi.loadUser = function(email) { return db.run(r.table('users').get(email)) } +dbapi.updateUserSettings = function(email, changes) { + return db.run(r.table('users').get(email).update({ + settings: changes + })) +} + +dbapi.resetUserSettings = function(email) { + return db.run(r.table('users').get(email).update({ + settings: r.literal({}) + })) +} + dbapi.addUserForward = function(email, forward) { var devicePort = forward.devicePort return db.run(r.table('users').get(email).update({ diff --git a/lib/roles/websocket.js b/lib/roles/websocket.js index b5c98239..716250e9 100644 --- a/lib/roles/websocket.js +++ b/lib/roles/websocket.js @@ -256,6 +256,13 @@ module.exports = function(options) { new Promise(function(resolve) { socket.on('disconnect', resolve) + // Settings + .on('user.settings.update', function(data) { + dbapi.updateUserSettings(user.email, data) + }) + .on('user.settings.reset', function() { + dbapi.resetUserSettings(user.email) + }) // Touch events .on('input.touchDown', createTouchHandler(wire.TouchDownMessage)) .on('input.touchMove', createTouchHandler(wire.TouchMoveMessage)) diff --git a/res/app/components/stf/settings/index.js b/res/app/components/stf/settings/index.js index a068450c..e7c0c38c 100644 --- a/res/app/components/stf/settings/index.js +++ b/res/app/components/stf/settings/index.js @@ -1,17 +1,2 @@ -require('localforage') -require('angular-localForage') - -module.exports = angular.module('stf/settings', [ - 'LocalForageModule' -]) - .config(['$localForageProvider', function ($localForageProvider) { - $localForageProvider.config({ - driver: 'localStorageWrapper', - name: 'stf-v0', - version: 1.0, - storeName: 'settings', - description: 'STF Local Settings' - }) - - }]) +module.exports = angular.module('stf/settings', []) .factory('SettingsService', require('./settings-service')) diff --git a/res/app/components/stf/settings/settings-service.js b/res/app/components/stf/settings/settings-service.js index 1af128da..ea6657ec 100644 --- a/res/app/components/stf/settings/settings-service.js +++ b/res/app/components/stf/settings/settings-service.js @@ -1,61 +1,103 @@ -var Promise = require('bluebird') +var _ = require('lodash') -module.exports = function SettingsServiceFactory($localForage) { +module.exports = function SettingsServiceFactory( + $rootScope +, UserService +, socket +) { var SettingsService = {} - var loadedInMemory = false + var settings = UserService.currentUser.settings || {} + , syncListeners = [] - var memoryData = Object.create(null) + function createListener(object, options, monitor) { + var source = options.source || options.target + return function() { + var value = object[options.target] = (source in settings) + ? settings[source] + : options.defaultValue - function setItemMemory(key, value) { - memoryData[key] = value - } - - function getItemMemory(key) { - return memoryData[key] - } - - SettingsService.setItem = function (key, value) { - setItemMemory(key, value) - return $localForage.setItem(key, value) - } - - function loadAllItems() { - if (loadedInMemory) { - return Promise.resolve() + if (monitor) { + monitor(value) + } } + } - return $localForage.getKeys().then(function (keys) { - return Promise.all(keys.map(function (key) { - return $localForage.getItem(key).then(setItemMemory.bind(null, key)) - })) - }).then(function () { - loadedInMemory = true + function applyDelta(delta) { + $rootScope.safeApply(function() { + _.merge(settings, delta, function(a, b) { + // New Arrays overwrite old Arrays + return _.isArray(b) ? b : undefined + }) + + for (var i = 0, l = syncListeners.length; i < l; ++i) { + syncListeners[i]() + } }) } - SettingsService.getItem = function (key) { - return loadAllItems().then(function () { - return getItemMemory(key) - }) + SettingsService.update = function(delta) { + socket.emit('user.settings.update', delta) + applyDelta(delta) } - SettingsService.bind = function () { - - - - return $localForage.bind.apply($localForage, arguments) + SettingsService.set = function(key, value) { + var delta = Object.create(null) + delta[key] = value + SettingsService.update(delta) } - SettingsService.driver = function () { - return $localForage.driver.apply($localForage, arguments) - + ' with memory cache' + SettingsService.reset = function() { + socket.emit('user.settings.reset') + settings = {} + applyDelta(null) } - SettingsService.clear = function () { - memoryData = Object.create(null) - return $localForage.clear.apply($localForage, arguments) + SettingsService.bind = function(scope, options) { + var source = options.source || options.target + + scope.$watch( + options.target + , function(newValue, oldValue) { + // Skip initial value. + if (newValue !== oldValue) { + var delta = Object.create(null) + delta[source] = newValue + SettingsService.update(delta) + } + } + , true + ) + + scope.$watch( + function() { + return settings[source] + } + , function(newValue, oldValue) { + // Skip initial value. The new value might not be different if + // settings were reset, for example. In that case we call back + // to the default value. + if (newValue !== oldValue) { + scope[options.target] = newValue || options.defaultValue + } + } + , true + ) + + scope[options.target] = settings[source] || options.defaultValue } + SettingsService.sync = function(object, options, monitor) { + var listener = createListener(object, options, monitor) + listener() // Initialize + return syncListeners.push(listener) - 1 + } + + SettingsService.unsync = function(id) { + syncListeners.splice(id, 1) + } + + socket.on('user.settings.update', applyDelta) + return SettingsService } diff --git a/res/app/layout/layout-controller.js b/res/app/layout/layout-controller.js index 8bc5e1f2..57e23981 100644 --- a/res/app/layout/layout-controller.js +++ b/res/app/layout/layout-controller.js @@ -1,3 +1,3 @@ module.exports = function LayoutCtrl(LanguageService) { - LanguageService.init() + LanguageService.updateLanguage() } diff --git a/res/app/menu/menu-controller.js b/res/app/menu/menu-controller.js index c880d486..d1e4b7d8 100644 --- a/res/app/menu/menu-controller.js +++ b/res/app/menu/menu-controller.js @@ -1,8 +1,13 @@ -module.exports = function MenuCtrl($scope, $rootScope, SettingsService, $location, ExternalUrlModalService) { - $rootScope.platform = 'native' +module.exports = function MenuCtrl( + $scope +, $rootScope +, SettingsService +, $location +, ExternalUrlModalService +) { SettingsService.bind($rootScope, { - key: 'platform', - storeName: 'Platform' + target: 'platform' + , defaultValue: 'native' }) $scope.$on('$routeChangeSuccess', function () { diff --git a/res/app/settings/language/language-controller.js b/res/app/settings/language/language-controller.js index 966f7012..df497b63 100644 --- a/res/app/settings/language/language-controller.js +++ b/res/app/settings/language/language-controller.js @@ -1,20 +1,8 @@ module.exports = function ($scope, LanguageService, SettingsService) { -// SettingsService.sync($scope, 'Language', { -// language: LanguageService.detectedLanguage -// }) -// SettingsService.bind($scope, { -// key: 'language', -// defaultValue: LanguageService.selectedLanguage -// }) - - LanguageService.getSelectedLanguage().then(function (data) { - $scope.language = data - }) - - $scope.$watch('language', function (newValue, oldValue) { - if (newValue !== oldValue) { - LanguageService.setSelectedLanguage(newValue) - } + SettingsService.bind($scope, { + target: 'language' + , source: LanguageService.settingKey + , defaultValue: LanguageService.detectedLanguage }) $scope.supportedLanguages = LanguageService.supportedLanguages diff --git a/res/app/settings/language/language-service.js b/res/app/settings/language/language-service.js index f6ae60f4..933969eb 100644 --- a/res/app/settings/language/language-service.js +++ b/res/app/settings/language/language-service.js @@ -1,67 +1,45 @@ -var _ = require('lodash') var supportedLanguages = require('./../../../common/lang/langs.json') -module.exports = function (SettingsService, $q, gettextCatalog) { +module.exports = function LanguageServiceFactory( + SettingsService +, gettextCatalog +) { var LanguageService = {} + function detectLanguage() { + return (navigator.language || navigator.userLanguage || 'en-US') + .substring(0, 2) + } + + function isSupported(lang) { + return !!supportedLanguages[lang] + } + + function onlySupported(lang, defaultValue) { + return isSupported(lang) ? lang : defaultValue + } + + LanguageService.settingKey = 'selectedLanguage' LanguageService.supportedLanguages = supportedLanguages + LanguageService.defaultLanguage = 'en' + LanguageService.detectedLanguage = + onlySupported(detectLanguage(), LanguageService.defaultLanguage) - var browserLocale = navigator.language || navigator.userLanguage || 'en-US' - var browserLanguage = browserLocale.substring(0, 2) - var isLanguageMatched = _.some(supportedLanguages, function (value, key) { - return key === browserLanguage - }) - var detectedLanguage = isLanguageMatched ? browserLanguage : 'en' - - var defaultLanguage = 'ja' - LanguageService.detectedLanguage = defaultLanguage - LanguageService.selectedLanguage = null - - // TODO: Can't this be refactored to something like this? - // SettingsService.sync(LanguageService.selectedLanguage, 'Language', { - // selected: LanguageService.detectedLanguage - // }) - - LanguageService.getSelectedLanguage = function () { - var deferred = $q.defer() - if (LanguageService.selectedLanguage) { - deferred.resolve(LanguageService.selectedLanguage) - } else { - SettingsService.getItem('Language.selected').then(function (data) { - if (data) { - deferred.resolve(data) - } else { - LanguageService.setSelectedLanguage(LanguageService.detectedLanguage) - .then(function () { - deferred.resolve(LanguageService.detectedLanguage) - }) - } - }) + SettingsService.sync( + LanguageService + , { + target: LanguageService.settingKey + , source: LanguageService.settingKey + , defaultValue: LanguageService.detectedLanguage } - return deferred.promise + , updateLanguage + ) + + function updateLanguage() { + gettextCatalog.currentLanguage = LanguageService.selectedLanguage } - // TODO: this is to prevent initial text flashing from ja - gettextCatalog.currentLanguage = LanguageService.detectedLanguage - - // Initialize gettextCatalog - - LanguageService.init = function () { - LanguageService.getSelectedLanguage().then(function (data) { - LanguageService.setSelectedLanguage(data) - }) - } - - - LanguageService.setSelectedLanguage = function (lang) { - var deferred = $q.defer() - LanguageService.selectedLanguage = lang - gettextCatalog.currentLanguage = lang - SettingsService.setItem('Language.selected', lang).then(function () { - deferred.resolve(lang) - }) - return deferred.promise - } + LanguageService.updateLanguage = updateLanguage return LanguageService } diff --git a/res/app/settings/local/local-settings-controller.js b/res/app/settings/local/local-settings-controller.js index 451295d6..84c08e1a 100644 --- a/res/app/settings/local/local-settings-controller.js +++ b/res/app/settings/local/local-settings-controller.js @@ -1,12 +1,9 @@ module.exports = function ($scope, SettingsService) { - $scope.resetSettings = function () { - SettingsService.clear() + SettingsService.reset() console.log('Settings cleared') } - $scope.savedTo = SettingsService.driver() - // $scope.resetSettings = function () { // var title = 'Reset Settings'; // var msg = 'Are you sure you want to revert all settings to ' + diff --git a/res/app/settings/local/local-settings.jade b/res/app/settings/local/local-settings.jade index 248c3719..408e66c3 100644 --- a/res/app/settings/local/local-settings.jade +++ b/res/app/settings/local/local-settings.jade @@ -6,6 +6,3 @@ button(ng-click='resetSettings()').btn.btn-danger i.fa.fa-trash-o span(translate) Reset Settings - - p - span.text-muted(translate) Saved to: {{savedTo}}