From 6fcdd4aa892532c7983412c710664adee361a598 Mon Sep 17 00:00:00 2001 From: k88hudson Date: Tue, 4 Apr 2017 12:57:53 -0400 Subject: [PATCH] Bug 1350409 - Add Store, Actions, and Reducers to Activity Stream system add-on r=ursula MozReview-Commit-ID: 5lCFGBCtH2e --HG-- extra : rebase_source : 01e7ae01e1dd03de9fbe84fa1fbc7797323ed475 --- .../activity-stream/common/Actions.jsm | 19 +++++ .../activity-stream/common/Reducers.jsm | 44 +++++++++++ browser/extensions/activity-stream/jar.mn | 2 + .../activity-stream/lib/ActivityStream.jsm | 10 ++- .../extensions/activity-stream/lib/Store.jsm | 78 +++++++++++++++++++ 5 files changed, 151 insertions(+), 2 deletions(-) create mode 100644 browser/extensions/activity-stream/common/Actions.jsm create mode 100644 browser/extensions/activity-stream/common/Reducers.jsm create mode 100644 browser/extensions/activity-stream/lib/Store.jsm diff --git a/browser/extensions/activity-stream/common/Actions.jsm b/browser/extensions/activity-stream/common/Actions.jsm new file mode 100644 index 000000000000..42e21a0d5594 --- /dev/null +++ b/browser/extensions/activity-stream/common/Actions.jsm @@ -0,0 +1,19 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +this.actionTypes = [ + "INIT", + "UNINIT", +// The line below creates an object like this: +// { +// INIT: "INIT", +// UNINIT: "UNINIT" +// } +// It prevents accidentally adding a different key/value name. +].reduce((obj, type) => { obj[type] = type; return obj; }, {}); + +this.EXPORTED_SYMBOLS = [ + "actionTypes" +]; diff --git a/browser/extensions/activity-stream/common/Reducers.jsm b/browser/extensions/activity-stream/common/Reducers.jsm new file mode 100644 index 000000000000..33b6db8a44d4 --- /dev/null +++ b/browser/extensions/activity-stream/common/Reducers.jsm @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +this.INITIAL_STATE = { + TopSites: { + rows: [ + { + "title": "Facebook", + "url": "https://www.facebook.com/" + }, + { + "title": "YouTube", + "url": "https://www.youtube.com/" + }, + { + "title": "Amazon", + "url": "http://www.amazon.com/" + }, + { + "title": "Yahoo", + "url": "https://www.yahoo.com/" + }, + { + "title": "eBay", + "url": "http://www.ebay.com" + }, + { + "title": "Twitter", + "url": "https://twitter.com/" + } + ] + } +}; + +// TODO: Handle some real actions here, once we have a TopSites feed working +function TopSites(prevState = INITIAL_STATE.TopSites, action) { + return prevState; +} + +this.reducers = {TopSites}; + +this.EXPORTED_SYMBOLS = ["reducers", "INITIAL_STATE"]; diff --git a/browser/extensions/activity-stream/jar.mn b/browser/extensions/activity-stream/jar.mn index f8d1cb9ae15f..186d65e4a4ed 100644 --- a/browser/extensions/activity-stream/jar.mn +++ b/browser/extensions/activity-stream/jar.mn @@ -5,4 +5,6 @@ [features/activity-stream@mozilla.org] chrome.jar: % resource activity-stream %content/ content/lib/ (./lib/*) + content/common/ (./common/*) + content/vendor/Redux.jsm (./vendor/Redux.jsm) content/data/ (./data/*) diff --git a/browser/extensions/activity-stream/lib/ActivityStream.jsm b/browser/extensions/activity-stream/lib/ActivityStream.jsm index 285c14158671..eba3b375c68b 100644 --- a/browser/extensions/activity-stream/lib/ActivityStream.jsm +++ b/browser/extensions/activity-stream/lib/ActivityStream.jsm @@ -3,7 +3,10 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; -class ActivityStream { +const {utils: Cu} = Components; +const {Store} = Cu.import("resource://activity-stream/lib/Store.jsm", {}); + +this.ActivityStream = class ActivityStream { /** * constructor - Initializes an instance of ActivityStream @@ -16,13 +19,16 @@ class ActivityStream { constructor(options) { this.initialized = false; this.options = options; + this.store = new Store(); } init() { this.initialized = true; + this.store.init(); } uninit() { + this.store.uninit(); this.initialized = false; } -} +}; this.EXPORTED_SYMBOLS = ["ActivityStream"]; diff --git a/browser/extensions/activity-stream/lib/Store.jsm b/browser/extensions/activity-stream/lib/Store.jsm new file mode 100644 index 000000000000..6c2b5db1691d --- /dev/null +++ b/browser/extensions/activity-stream/lib/Store.jsm @@ -0,0 +1,78 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +"use strict"; + +const {utils: Cu} = Components; + +const {redux} = Cu.import("resource://activity-stream/vendor/Redux.jsm", {}); +const {actionTypes: at} = Cu.import("resource://activity-stream/common/Actions.jsm", {}); +const {reducers} = Cu.import("resource://activity-stream/common/Reducers.jsm", {}); + +/** + * Store - This has a similar structure to a redux store, but includes some extra + * functionality. It accepts an array of "Feeds" on inititalization, which + * can listen for any action that is dispatched through the store. + */ +this.Store = class Store { + + /** + * constructor - The redux store is created here, + * but no listeners are added until "init" is called. + */ + constructor() { + this._middleware = this._middleware.bind(this); + // Bind each redux method so we can call it directly from the Store. E.g., + // store.dispatch() will call store._store.dispatch(); + ["dispatch", "getState", "subscribe"].forEach(method => { + this[method] = function(...args) { + return this._store[method](...args); + }.bind(this); + }); + this.feeds = new Set(); + this._store = redux.createStore( + redux.combineReducers(reducers), + redux.applyMiddleware(this._middleware) + ); + } + + /** + * _middleware - This is redux middleware consumed by redux.createStore. + * it calls each feed's .onAction method, if one + * is defined. + */ + _middleware(store) { + return next => action => { + next(action); + this.feeds.forEach(s => s.onAction && s.onAction(action)); + }; + } + + /** + * init - Initializes the MessageManager channel, and adds feeds. + * After initialization has finished, an INIT action is dispatched. + * + * @param {array} feeds An array of objects with an optional .onAction method + */ + init(feeds) { + if (feeds) { + feeds.forEach(subscriber => { + subscriber.store = this; + this.feeds.add(subscriber); + }); + } + this.dispatch({type: at.INIT}); + } + + /** + * uninit - Clears all feeds, dispatches an UNINIT action + * + * @return {type} description + */ + uninit() { + this.feeds.clear(); + this.dispatch({type: at.UNINIT}); + } +}; + +this.EXPORTED_SYMBOLS = ["Store"];