1
0
Эх сурвалжийг харах

refactor: operate options from one place

Gerald 9 жил өмнө
parent
commit
9af0f6eb9b

+ 37 - 27
src/background/app.js

@@ -1,3 +1,4 @@
+var _ = require('src/common');
 var VMDB = require('./db');
 var sync = require('./sync');
 var requests = require('./requests');
@@ -5,17 +6,24 @@ var cache = require('./utils/cache');
 var tabsUtils = require('./utils/tabs');
 var scriptUtils = require('./utils/script');
 var clipboard = require('./utils/clipboard');
-var _ = require('../common');
+var options = require('./options');
 
 var vmdb = exports.vmdb = new VMDB;
 var VM_VER = chrome.app.getDetails().version;
 
+options.hook(function (changes) {
+  _.messenger.post({
+    cmd: 'UpdateOptions',
+    data: changes,
+  });
+});
+
 var autoUpdate = function () {
   function check() {
     checking = true;
     return new Promise(function (resolve, reject) {
-      if (!_.options.get('autoUpdate')) return reject();
-      if (Date.now() - _.options.get('lastUpdate') >= 864e5)
+      if (!options.get('autoUpdate')) return reject();
+      if (Date.now() - options.get('lastUpdate') >= 864e5)
         resolve(commands.CheckUpdateAll());
     }).then(function () {
       setTimeout(check, 36e5);
@@ -36,10 +44,6 @@ var commands = {
     return vmdb.removeScript(id)
     .then(function () {
       sync.sync();
-      _.messenger.post({
-        cmd: 'del',
-        data: id,
-      });
     });
   },
   GetData: function (_data, _src) {
@@ -51,8 +55,8 @@ var commands = {
   },
   GetInjected: function (url, src) {
     var data = {
-      isApplied: _.options.get('isApplied'),
-      injectMode: _.options.get('injectMode'),
+      isApplied: options.get('isApplied'),
+      injectMode: options.get('injectMode'),
       version: VM_VER,
     };
     if (src.url == src.tab.url)
@@ -69,7 +73,7 @@ var commands = {
     .then(function (script) {
       sync.sync();
       _.messenger.post({
-        cmd: 'update',
+        cmd: 'UpdateScript',
         data: script,
       });
     });
@@ -104,7 +108,7 @@ var commands = {
   ParseScript: function (data, _src) {
     return vmdb.parseScript(data).then(function (res) {
       var meta = res.data.meta;
-      if (!meta.grant.length && !_.options.get('ignoreGrant'))
+      if (!meta.grant.length && !options.get('ignoreGrant'))
         notify({
           id: 'VM-NoGrantWarning',
           title: _.i18n('Warning'),
@@ -121,7 +125,7 @@ var commands = {
     return false;
   },
   CheckUpdateAll: function (_data, _src) {
-    _.options.set('lastUpdate', Date.now());
+    options.set('lastUpdate', Date.now());
     vmdb.getScriptsByIndex('update', 1).then(function (scripts) {
       return Promise.all(scripts.map(vmdb.checkUpdate));
     });
@@ -176,12 +180,30 @@ var commands = {
   },
   SetClipboard: function (data, _src) {
     clipboard.set(data);
+    return false;
   },
   OpenTab: function (data, _src) {
     chrome.tabs.create({
       url: data.url,
       active: data.active,
     });
+    return false;
+  },
+  GetAllOptions: function (_data, _src) {
+    return options.getAll();
+  },
+  GetOptions: function (data, _src) {
+    return data.reduce(function (res, key) {
+      res[key] = options.get(key);
+      return res;
+    }, {});
+  },
+  SetOptions: function (data, _src) {
+    if (!Array.isArray(data)) data = [data];
+    data.forEach(function (item) {
+      options.set(item.key, item.value);
+    });
+    return false;
   },
 };
 
@@ -237,7 +259,7 @@ var setBadge = function () {
       color: '#808',
       tabId: src.tab.id,
     });
-    var text = (_.options.get('showBadge') && o.num || '').toString();
+    var text = (options.get('showBadge') && o.num || '').toString();
     chrome.browserAction.setBadgeText({
       text: text,
       tabId: src.tab.id,
@@ -250,21 +272,9 @@ var setBadge = function () {
 }();
 
 _.messenger = function () {
-  var port;
-  chrome.runtime.onConnect.addListener(function (_port) {
-    port = _port;
-    _port.onDisconnect.addListener(function () {
-      if (port === _port) port = null;
-    });
-  });
-
   return {
     post: function (data) {
-      try {
-        port && port.postMessage(data);
-      } catch (e) {
-        port = null;
-      }
+      chrome.runtime.sendMessage(data);
     },
     send: function (tabId, data) {
       chrome.tabs.sendMessage(tabId, data);
@@ -279,7 +289,7 @@ _.messenger = function () {
       38: '/images/icon38' + (isApplied ? '' : 'w') + '.png'
     },
   });
-}(_.options.get('isApplied'));
+}(options.get('isApplied'));
 
 chrome.notifications.onClicked.addListener(function (id) {
   if (id == 'VM-NoGrantWarning') {

+ 9 - 3
src/background/db.js

@@ -216,6 +216,12 @@ VMDB.prototype.removeScript = function (id) {
     o.delete(id).onsuccess = function () {
       resolve();
     };
+  })
+  .then(function () {
+    _.messenger.post({
+      cmd: 'RemoveScript',
+      data: id,
+    });
   });
 };
 
@@ -518,7 +524,7 @@ VMDB.prototype.parseScript = function (data) {
   var meta = scriptUtils.parseMeta(data.code);
   if (!meta.name) return Promise.reject(_.i18n('msgInvalidScript'));
   var res = {
-    cmd: 'update',
+    cmd: 'UpdateScript',
     data: {
       message: data.message == null ? _.i18n('msgUpdated') : data.message || '',
     },
@@ -561,7 +567,7 @@ VMDB.prototype.parseScript = function (data) {
       if (data.isNew) throw _.i18n('msgNamespaceConflict');
     } else {
       script = scriptUtils.newScript();
-      res.cmd = 'add';
+      res.cmd = 'AddScript';
       res.data.message = _.i18n('msgInstalled');
     }
     data.more && Object.keys(data.more).forEach(function (key) {
@@ -586,7 +592,7 @@ VMDB.prototype.parseScript = function (data) {
 VMDB.prototype.checkUpdate = function () {
   function check(script) {
     var res = {
-      cmd: 'update',
+      cmd: 'UpdateScript',
       data: {
         id: script.id,
         checking: true,

+ 74 - 0
src/background/options.js

@@ -0,0 +1,74 @@
+var _ = require('src/common');
+
+var defaults = {
+  isApplied: true,
+  autoUpdate: true,
+  ignoreGrant: false,
+  lastUpdate: 0,
+  showBadge: true,
+  exportValues: true,
+  closeAfterInstall: false,
+  trackLocalFile: false,
+  autoReload: false,
+  dropbox: {},
+  onedrive: {},
+  features: null,
+  blacklist: null,
+};
+var changes = {};
+var hooks = _.initHooks();
+var callHooksLater = _.debounce(callHooks, 100);
+
+function fireChange(key, value) {
+  changes[key] = value;
+  callHooksLater();
+}
+
+function callHooks() {
+  hooks.fire(changes);
+  changes = {};
+}
+
+function getOption(key, def) {
+  var keys = _.normalizeKeys(key);
+  key = keys[0];
+  var value = localStorage.getItem(key), obj;
+  if (value) {
+    try {
+      obj = JSON.parse(value);
+    } catch (e) {
+      // ignore invalid JSON
+    }
+  }
+  if (obj == null) obj = defaults[key];
+  if (obj == null) obj = def;
+  return keys.length > 1 ? _.object.get(obj, keys.slice(1), def) : obj;
+}
+
+function setOption(key, value) {
+  var keys = _.normalizeKeys(key);
+  var optionKey = keys.join('.');
+  var optionValue = value;
+  key = keys[0];
+  if (key in defaults) {
+    if (keys.length > 1) {
+      optionValue = _.object.set(getOption(key), keys.slice(1), value);
+    }
+    localStorage.setItem(key, JSON.stringify(optionValue));
+    fireChange(optionKey, value);
+  }
+}
+
+function getAllOptions() {
+  return Object.keys(defaults).reduce(function (res, key) {
+    res[key] = getOption(key);
+    return res;
+  }, {});
+}
+
+module.exports = {
+  get: getOption,
+  set: setOption,
+  getAll: getAllOptions,
+  hook: hooks.hook,
+};

+ 7 - 12
src/background/sync/index.js

@@ -3,6 +3,7 @@ var events = require('../utils/events');
 var app = require('../app');
 var tabs = require('../utils/tabs');
 var _ = require('../../common');
+var options = require('../options');
 
 setTimeout(function () {
   // import sync modules
@@ -28,19 +29,19 @@ ServiceConfig.prototype.normalizeKeys = function (key) {
 };
 ServiceConfig.prototype.get = function (key, def) {
   var keys = this.normalizeKeys(key);
-  return _.options.get(keys, def);
+  return options.get(keys, def);
 };
 ServiceConfig.prototype.set = function (key, val) {
   var _this = this;
   if (arguments.length === 1) {
-    return _.options.set(_this.name, Object.assign(_.options.get(_this.name, {}), key));
+    return options.set(_this.name, Object.assign(options.get(_this.name, {}), key));
   } else {
     var keys = this.normalizeKeys(key);
-    return _.options.set(keys, val);
+    return options.set(keys, val);
   }
 };
 ServiceConfig.prototype.clear = function () {
-  _.options.set(this.name, {});
+  options.set(this.name, {});
 };
 
 function serviceState(validStates, initialState, onChange) {
@@ -192,7 +193,7 @@ var BaseService = serviceFactory({
   },
   onStateChange: function () {
     _.messenger.post({
-      cmd: 'sync',
+      cmd: 'UpdateSync',
       data: getStates(),
     });
   },
@@ -433,13 +434,7 @@ var BaseService = serviceFactory({
         }),
         delLocal.map(function (item) {
           console.log('Remove local script:', item.uri);
-          return app.vmdb.removeScript(item.id)
-          .then(function () {
-            _.messenger.post({
-              cmd: 'del',
-              data: item.id,
-            });
-          });
+          return app.vmdb.removeScript(item.id);
         })
       );
       promises.push(Promise.all(promises).then(function () {

+ 47 - 94
src/common.js

@@ -77,115 +77,68 @@ _.object = function () {
   };
 }();
 
-_.options = function () {
-  function getOption(key, def) {
-    var keys = normalizeKeys(key);
-    key = keys[0];
-    var value = localStorage.getItem(key), obj;
-    if (value) {
-      try {
-        obj = JSON.parse(value);
-      } catch (e) {
-        // ignore invalid JSON
-      }
-    }
-    if (obj == null) obj = defaults[key];
-    if (obj == null) obj = def;
-    return keys.length > 1 ? _.object.get(obj, keys.slice(1), def) : obj;
-  }
+_.initHooks = function () {
+  var hooks = [];
 
-  function fire(hkey, key, value) {
-    var group = hooks[hkey];
-    group && group.forEach(function (cb) {
-      cb(value, key);
+  function fire(data) {
+    hooks.slice().forEach(function (hook) {
+      hook(data);
     });
   }
 
-  function setOption(key, value) {
-    var keys = normalizeKeys(key);
-    var optionKey = keys.join('.');
-    var optionValue = value;
-    key = keys[0];
-    if (key in defaults) {
-      if (keys.length > 1) {
-        optionValue = _.object.set(getOption(key), keys.slice(1), value);
-      }
-      localStorage.setItem(key, JSON.stringify(optionValue));
-      [optionKey, key, ''].forEach(function (hkey) {
-        fire(hkey, optionKey, value);
-      });
-    }
+  function hook(callback) {
+    hooks.push(callback);
+    return function () {
+      var i = hooks.indexOf(callback);
+      ~i && hooks.splice(i, 1);
+    };
   }
 
-  function getAllOptions() {
-    return Object.keys(defaults).reduce(function (res, key) {
-      res[key] = getOption(key);
-      return res;
-    }, {});
-  }
+  return {
+    hook: hook,
+    fire: fire,
+  };
+};
 
-  function parseArgs(args) {
-    return args.length === 1 ? {
-      key: '',
-      cb: args[0],
-    } : {
-      key: args[0] || '',
-      cb: args[1],
-    };
-  }
+_.initOptions = function () {
+  var options = {};
+  var hooks = _.initHooks();
+  var ready = _.sendMessage({cmd: 'GetAllOptions'})
+  .then(function (data) {
+    options = data;
+    hooks.fire(data);
+  });
 
-  function hook() {
-    var arg = parseArgs(arguments);
-    var list = hooks[arg.key];
-    if (!list) list = hooks[arg.key] = [];
-    list.push(arg.cb);
-    return function () {
-      unhook(arg.key, arg.cb);
-    };
+  function getOption(key, def) {
+    var keys = normalizeKeys(key);
+    return _.object.get(options, keys, def);
   }
-  function unhook() {
-    var arg = parseArgs(arguments);
-    var list = hooks[arg.key];
-    if (list) {
-      var i = list.indexOf(arg.cb);
-      ~i && list.splice(i, 1);
-    }
+
+  function setOption(key, value) {
+    _.sendMessage({
+      cmd: 'SetOptions',
+      data: {
+        key: key,
+        value: value,
+      },
+    });
   }
 
-  var defaults = {
-    isApplied: true,
-    autoUpdate: true,
-    ignoreGrant: false,
-    lastUpdate: 0,
-    showBadge: true,
-    exportValues: true,
-    closeAfterInstall: false,
-    trackLocalFile: false,
-    autoReload: false,
-    dropbox: {},
-    onedrive: {},
-    features: null,
-  };
-  var hooks = {};
-
-  // XXX migrate sync status options
-  ['dropbox', 'onedrive'].forEach(function (name) {
-    var key = name + 'Enabled';
-    var val = getOption(key);
-    if (val != null) {
-      setOption([name, 'enabled'], val);
-      localStorage.removeItem(key);
-    }
-  });
+  function updateOptions(data) {
+    Object.keys(data).forEach(function (key) {
+      _.object.set(options, key, data[key]);
+    });
+    hooks.fire(data);
+  }
 
-  return {
+  _.options = {
     get: getOption,
     set: setOption,
-    getAll: getAllOptions,
-    hook: hook,
-    unhook: unhook,
+    update: updateOptions,
+    hook: hooks.hook,
+    ready: ready,
   };
-}();
+};
 
 _.sendMessage = function (data) {
   return new Promise(function (resolve, reject) {

+ 19 - 14
src/options/app.js

@@ -13,16 +13,15 @@ function initMain() {
     // utils.features.reset(data.version);
     utils.features.reset('sync');
   });
-  var port = chrome.runtime.connect({name: 'Options'});
   var handlers = {
-    sync: function (data) {
+    UpdateSync: function (data) {
       store.sync = data;
     },
-    add: function (data) {
+    AddScript: function (data) {
       data.message = '';
       store.scripts.push(data);
     },
-    update: function (data) {
+    UpdateScript: function (data) {
       if (!data) return;
       var script = store.scripts.find(function (script) {
         return script.id === data.id;
@@ -31,14 +30,17 @@ function initMain() {
         Vue.set(script, key, data[key]);
       });
     },
-    del: function (data) {
+    RemoveScript: function (data) {
       var i = store.scripts.findIndex(function (script) {
         return script.id === data;
       });
       ~i && store.scripts.splice(i, 1);
     },
+    UpdateOptions: function (data) {
+      _.options.update(data);
+    },
   };
-  port.onMessage.addListener(function (res) {
+  chrome.runtime.onMessage.addListener(function (res) {
     var handle = handlers[res.cmd];
     handle && handle(res.data);
   });
@@ -61,6 +63,7 @@ function loadHash() {
 }
 
 var _ = require('../common');
+_.initOptions();
 var utils = require('./utils');
 var Main = require('./views/main');
 var Confirm = require('./views/confirm');
@@ -93,12 +96,14 @@ loadHash();
 zip.workerScriptsPath = '/lib/zip.js/';
 document.title = _.i18n('extName');
 
-new Vue({
-  el: '#app',
-  template: '<component :is=type :params=params></component>',
-  components: {
-    Main: Main,
-    Confirm: Confirm,
-  },
-  data: hashData,
+_.options.ready.then(function () {
+  new Vue({
+    el: '#app',
+    template: '<component :is=type :params=params></component>',
+    components: {
+      Main: Main,
+      Confirm: Confirm,
+    },
+    data: hashData,
+  });
 });

+ 19 - 4
src/options/utils/features.js

@@ -1,6 +1,16 @@
-var _ = require('../../common');
+var _ = require('src/common');
 
 var key = 'features';
+var hooks = _.initHooks();
+var revoke = _.options.hook(function (data) {
+  if (data[key]) {
+    features = data[key];
+    revoke();
+    revoke = null;
+    hooks.fire();
+    hooks = null;
+  }
+});
 var features = _.options.get(key);
 if (!features || !features.data) features = {
   data: {},
@@ -23,9 +33,14 @@ Vue.directive('feature', {
       el.classList.remove('feature');
       el.removeEventListener('click', onFeatureClick, false);
     }
+    function reset() {
+      if (!features.version || features.data[value]) return;
+      el.classList.add('feature');
+      el.removeEventListener('click', onFeatureClick, false);
+      el.addEventListener('click', onFeatureClick, false);
+    }
     var value = binding.value;
-    if (features.data[value]) return;
-    el.classList.add('feature');
-    el.addEventListener('click', onFeatureClick, false);
+    reset();
+    hooks && hooks.hook(reset);
   },
 });

+ 6 - 4
src/options/utils/settings.js

@@ -1,10 +1,12 @@
 var _ = require('src/common');
 
 var hooks = {};
-_.options.hook(function (value, key) {
-  var list = hooks[key];
-  list && list.forEach(function (el) {
-    el.checked = value;
+_.options.hook(function (data) {
+  Object.keys(data).forEach(function (key) {
+    var list = hooks[key];
+    list && list.forEach(function (el) {
+      el.checked = data[key];
+    });
   });
 });
 

+ 5 - 1
src/popup/app.js

@@ -1,8 +1,9 @@
+var _ = require('src/common');
+_.initOptions();
 var Menu = require('./views/menu');
 var Commands = require('./views/command');
 var Domains = require('./views/domain');
 var utils = require('./utils');
-var _ = require('../common');
 
 var app = new Vue({
   el: '#app',
@@ -53,6 +54,9 @@ exports.navigate = app.navigate.bind(app);
         utils.store.scripts = scripts;
       });
     },
+    UpdateOptions: function (data) {
+      _.options.update(data);
+    },
   };
   chrome.runtime.onMessage.addListener(function (req, src, callback) {
     var func = commands[req.cmd];

+ 11 - 5
src/popup/views/menu.js

@@ -55,13 +55,15 @@ module.exports = {
         symbol: null,
         disabled: null,
         init: function (options) {
+          options.update(options);
+          _.options.hook(function (data) {
+            data.isApplied != null && options.update(options);
+          });
+        },
+        update: function (options) {
           options.disabled = !_.options.get('isApplied');
           options.name = options.disabled ? _.i18n('menuScriptDisabled') : _.i18n('menuScriptEnabled');
           options.symbol = options.disabled ? 'remove' : 'check';
-        },
-        onClick: function (options) {
-          _.options.set('isApplied', options.disabled);
-          options.init.call(this, options);
           chrome.browserAction.setIcon({
             path: {
               19: '/images/icon19' + (options.disabled ? 'w' : '') + '.png',
@@ -69,6 +71,9 @@ module.exports = {
             },
           });
         },
+        onClick: function (options) {
+          _.options.set('isApplied', options.disabled);
+        },
       }],
     };
   },
@@ -93,7 +98,8 @@ module.exports = {
                 id: script.id,
                 enabled: !script.enabled,
               },
-            }).then(function () {
+            })
+            .then(function () {
               script.enabled = !script.enabled;
               options.init.call(vm, options);
               _.options.get('autoReload') && chrome.tabs.reload(_this.store.currentTab.id);