From e12bf19e35867f3ea69f45054decb024a75fc2b4 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 10 Dec 2014 00:47:05 +0100 Subject: web: add event store, fix all those bugs --- web/gulpfile.js | 7 +- web/src/js/actions.js | 16 +++- web/src/js/components/eventlog.jsx.js | 35 +++---- web/src/js/components/flowtable.jsx.js | 4 +- web/src/js/components/mainview.jsx.js | 2 +- web/src/js/components/proxyapp.jsx.js | 2 +- web/src/js/components/virtualscroll.jsx.js | 14 +-- web/src/js/dispatcher.js | 4 +- web/src/js/store/settingstore.js | 28 ++++++ web/src/js/store/store.js | 123 +++++++++++++++++++++++++ web/src/js/store/view.js | 87 ++++++++++++++++++ web/src/js/stores/base.js | 141 ----------------------------- web/src/js/stores/eventlogstore.js | 99 -------------------- web/src/js/stores/flowstore.js | 105 --------------------- web/src/js/stores/settingstore.js | 28 ------ web/src/js/utils.js | 36 ++++++++ 16 files changed, 320 insertions(+), 411 deletions(-) create mode 100644 web/src/js/store/settingstore.js create mode 100644 web/src/js/store/store.js create mode 100644 web/src/js/store/view.js delete mode 100644 web/src/js/stores/base.js delete mode 100644 web/src/js/stores/eventlogstore.js delete mode 100644 web/src/js/stores/flowstore.js delete mode 100644 web/src/js/stores/settingstore.js (limited to 'web') diff --git a/web/gulpfile.js b/web/gulpfile.js index 6e338924..f7df820d 100644 --- a/web/gulpfile.js +++ b/web/gulpfile.js @@ -36,10 +36,9 @@ var path = { 'js/dispatcher.js', 'js/actions.js', 'js/flow/utils.js', - 'js/stores/base.js', - 'js/stores/settingstore.js', - 'js/stores/eventlogstore.js', - 'js/stores/flowstore.js', + 'js/store/store.js', + 'js/store/view.js', + 'js/store/settingstore.js', 'js/connection.js', 'js/components/utils.jsx.js', 'js/components/virtualscroll.jsx.js', diff --git a/web/src/js/actions.js b/web/src/js/actions.js index 28bb58b8..82fbcdca 100644 --- a/web/src/js/actions.js +++ b/web/src/js/actions.js @@ -8,13 +8,18 @@ var ActionTypes = { UPDATE_SETTINGS: "update_settings", // EventLog + EVENT_STORE: "events", ADD_EVENT: "add_event", // Flow - ADD_FLOW: "add_flow", - UPDATE_FLOW: "update_flow", - REMOVE_FLOW: "remove_flow", - RESET_FLOWS: "reset_flows", + FLOW_STORE: "flows", +}; + +var StoreCmds = { + ADD: "add", + UPDATE: "update", + REMOVE: "remove", + RESET: "reset" }; var ConnectionActions = { @@ -52,7 +57,8 @@ var EventLogActions_event_id = 0; var EventLogActions = { add_event: function (message) { AppDispatcher.dispatchViewAction({ - type: ActionTypes.ADD_EVENT, + type: ActionTypes.EVENT_STORE, + cmd: StoreCmds.ADD, data: { message: message, level: "web", diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js index 708432d0..3bd188ea 100644 --- a/web/src/js/components/eventlog.jsx.js +++ b/web/src/js/components/eventlog.jsx.js @@ -25,32 +25,33 @@ var LogMessage = React.createClass({ var EventLogContents = React.createClass({ mixins: [AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { + getInitialState: function(){ + var store = new EventLogStore(); + var view = new StoreView(store, function(entry){ + return this.props.filter[entry.level]; + }.bind(this)); + view.addListener("add recalculate", this.onEventLogChange); return { + store: store, + view: view, log: [] }; }, - componentDidMount: function () { - this.log = EventLogStore.getView(); - this.log.addListener("change", this.onEventLogChange); - }, componentWillUnmount: function () { - this.log.removeListener("change", this.onEventLogChange); - this.log.close(); + this.state.view.removeListener("add recalculate", this.onEventLogChange); + this.state.view.close(); + this.state.store.close(); }, onEventLogChange: function () { - var log = this.log.getAll().filter(function (entry) { - return this.props.filter[entry.level]; - }.bind(this)); this.setState({ - log: log + log: this.state.view.list }); }, - componentWillReceiveProps: function () { - if (this.log) { - this.onEventLogChange(); + componentWillReceiveProps: function (nextProps) { + if(nextProps.filter !== this.props.filter){ + this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update. + this.state.view.recalculate(this.state.store._list); } - }, getDefaultProps: function () { return { @@ -66,7 +67,7 @@ var EventLogContents = React.createClass({ var rows = this.renderRows(this.state.log); return
-            { this.getPlaceholderTop() }
+            { this.getPlaceholderTop(this.state.log.length) }
             {rows}
             { this.getPlaceholderBottom(this.state.log.length) }
         
; @@ -112,7 +113,7 @@ var EventLog = React.createClass({ }); }, toggleLevel: function (level) { - var filter = this.state.filter; + var filter = _.extend({}, this.state.filter); filter[level] = !filter[level]; this.setState({filter: filter}); }, diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 1a4efe89..4b72dd29 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -88,7 +88,7 @@ var FlowTable = React.createClass({ }, render: function () { //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); - var flows = this.props.view ? this.props.view.flows : []; + var flows = this.props.view ? this.props.view.list : []; var rows = this.renderRows(flows); @@ -98,7 +98,7 @@ var FlowTable = React.createClass({ - { this.getPlaceholderTop() } + { this.getPlaceholderTop(flows.length) } {rows} { this.getPlaceholderBottom(flows.length) } diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js index c7c9ee9b..570962e0 100644 --- a/web/src/js/components/mainview.jsx.js +++ b/web/src/js/components/mainview.jsx.js @@ -12,7 +12,7 @@ var MainView = React.createClass({ } }, openView: function (store) { - var view = new FlowView(store); + var view = new StoreView(store); this.setState({ view: view }); diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js index 3545cfe0..e03b1a57 100644 --- a/web/src/js/components/proxyapp.jsx.js +++ b/web/src/js/components/proxyapp.jsx.js @@ -10,7 +10,7 @@ var ProxyAppMain = React.createClass({ getInitialState: function () { return { settings: SettingsStore.getAll(), - flowStore: new LiveFlowStore() + flowStore: new FlowStore() }; }, componentDidMount: function () { diff --git a/web/src/js/components/virtualscroll.jsx.js b/web/src/js/components/virtualscroll.jsx.js index 5a67bbf5..b1924949 100644 --- a/web/src/js/components/virtualscroll.jsx.js +++ b/web/src/js/components/virtualscroll.jsx.js @@ -5,15 +5,17 @@ var VirtualScrollMixin = { stop: 0 }; }, - componentWillMount: function(){ - if(!this.props.rowHeight){ + componentWillMount: function () { + if (!this.props.rowHeight) { console.warn("VirtualScrollMixin: No rowHeight specified", this); } }, - getPlaceholderTop: function () { + getPlaceholderTop: function (total) { var Tag = this.props.placeholderTagName || "tr"; + // When a large trunk of elements is removed from the button, start may be far off the viewport. + // To make this issue less severe, limit the top placeholder to the total number of rows. var style = { - height: this.state.start * this.props.rowHeight + height: Math.min(this.state.start, total) * this.props.rowHeight }; var spacer = ; @@ -46,7 +48,7 @@ var VirtualScrollMixin = { stop: stop }); }, - renderRows: function(elems){ + renderRows: function (elems) { var rows = []; var max = Math.min(elems.length, this.state.stop); @@ -56,7 +58,7 @@ var VirtualScrollMixin = { } return rows; }, - scrollRowIntoView: function(index, head_height){ + scrollRowIntoView: function (index, head_height) { var row_top = (index * this.props.rowHeight) + head_height; var row_bottom = row_top + this.props.rowHeight; diff --git a/web/src/js/dispatcher.js b/web/src/js/dispatcher.js index 7fa97481..860ade9f 100644 --- a/web/src/js/dispatcher.js +++ b/web/src/js/dispatcher.js @@ -11,9 +11,9 @@ Dispatcher.prototype.register = function (callback) { this.callbacks.push(callback); }; Dispatcher.prototype.unregister = function (callback) { - var index = this.callbacks.indexOf(f); + var index = this.callbacks.indexOf(callback); if (index >= 0) { - this.callbacks.splice(this.callbacks.indexOf(f), 1); + this.callbacks.splice(index, 1); } }; Dispatcher.prototype.dispatch = function (payload) { diff --git a/web/src/js/store/settingstore.js b/web/src/js/store/settingstore.js new file mode 100644 index 00000000..7eef9b8f --- /dev/null +++ b/web/src/js/store/settingstore.js @@ -0,0 +1,28 @@ +function _SettingsStore() { + EventEmitter.call(this); + + //FIXME: What do we do if we haven't requested anything from the server yet? + this.settings = { + version: "0.12", + showEventLog: true, + mode: "transparent", + }; +} +_.extend(_SettingsStore.prototype, EventEmitter.prototype, { + getAll: function () { + return this.settings; + }, + handle: function (action) { + switch (action.type) { + case ActionTypes.UPDATE_SETTINGS: + this.settings = action.settings; + this.emit("change"); + break; + default: + return; + } + } +}); + +var SettingsStore = new _SettingsStore(); +AppDispatcher.register(SettingsStore.handle.bind(SettingsStore)); diff --git a/web/src/js/store/store.js b/web/src/js/store/store.js new file mode 100644 index 00000000..da288a5d --- /dev/null +++ b/web/src/js/store/store.js @@ -0,0 +1,123 @@ +function Store() { + this._views = []; + this.reset(); +} +_.extend(Store.prototype, { + add: function (elem) { + if (elem.id in this._pos_map) { + return; + } + + this._pos_map[elem.id] = this._list.length; + this._list.push(elem); + for (var i = 0; i < this._views.length; i++) { + this._views[i].add(elem); + } + }, + update: function (elem) { + if (!(elem.id in this._pos_map)) { + return; + } + + this._list[this._pos_map[elem.id]] = elem; + for (var i = 0; i < this._views.length; i++) { + this._views[i].update(elem); + } + }, + remove: function (elem_id) { + if (!(elem.id in this._pos_map)) { + return; + } + + this._list.splice(this._pos_map[elem_id], 1); + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].remove(elem_id); + } + }, + reset: function (elems) { + this._list = elems || []; + this._build_map(); + for (var i = 0; i < this._views.length; i++) { + this._views[i].recalculate(this._list); + } + }, + _build_map: function () { + this._pos_map = {}; + for (var i = 0; i < this._list.length; i++) { + var elem = this._list[i]; + this._pos_map[elem.id] = i; + } + }, + get: function (elem_id) { + return this._list[this._pos_map[elem_id]]; + }, + index: function(elem_id) { + return this._pos_map[elem_id]; + } +}); + + +function LiveStore(type) { + Store.call(this); + this.type = type; + + this._updates_before_fetch = undefined; + this._fetchxhr = false; + + this.handle = this.handle.bind(this); + AppDispatcher.register(this.handle); + + // Avoid double-fetch on startup. + if (!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { + this.fetch(); + } +} +_.extend(LiveStore.prototype, Store.prototype, { + handle: function (event) { + if (event.type === ActionTypes.CONNECTION_OPEN) { + return this.fetch(); + } + if (event.type === this.type) { + if (event.cmd === StoreCmds.RESET) { + this.fetch(); + } else if (this._updates_before_fetch) { + console.log("defer update", event); + this._updates_before_fetch.push(event); + } else { + this[event.cmd](event.data); + } + } + }, + close: function () { + AppDispatcher.unregister(this.handle); + }, + fetch: function () { + console.log("fetch " + this.type); + if (this._fetchxhr) { + this._fetchxhr.abort(); + } + this._fetchxhr = $.getJSON("/" + this.type, this.handle_fetch.bind(this)); + this._updates_before_fetch = []; // (JS: empty array is true) + }, + handle_fetch: function (data) { + this._fetchxhr = false; + console.log(this.type + " fetched.", this._updates_before_fetch); + this.reset(data.list); + var updates = this._updates_before_fetch; + this._updates_before_fetch = false; + for (var i = 0; i < updates.length; i++) { + this.handle(updates[i]); + } + }, +}); + + +function FlowStore() { + return new LiveStore(ActionTypes.FLOW_STORE); +} + + +function EventLogStore() { + return new LiveStore(ActionTypes.EVENT_STORE); +} \ No newline at end of file diff --git a/web/src/js/store/view.js b/web/src/js/store/view.js new file mode 100644 index 00000000..261429b2 --- /dev/null +++ b/web/src/js/store/view.js @@ -0,0 +1,87 @@ +function SortByStoreOrder(elem) { + return this.store.index(elem.id); +} + +var default_sort = SortByStoreOrder; +var default_filt = function(elem){ + return true; +}; + +function StoreView(store, filt, sortfun) { + EventEmitter.call(this); + filt = filt || default_filt; + sortfun = sortfun || default_sort; + + this.store = store; + this.store._views.push(this); + this.recalculate(this.store._list, filt, sortfun); +} + +_.extend(StoreView.prototype, EventEmitter.prototype, { + close: function () { + this.store._views = _.without(this.store._views, this); + }, + recalculate: function (elems, filt, sortfun) { + if (filt) { + this.filt = filt; + } + if (sortfun) { + this.sortfun = sortfun.bind(this); + } + + this.list = elems.filter(this.filt); + this.list.sort(function (a, b) { + return this.sortfun(a) - this.sortfun(b); + }.bind(this)); + this.emit("recalculate"); + }, + index: function (elem) { + return _.sortedIndex(this.list, elem, this.sortfun); + }, + add: function (elem) { + if (this.filt(elem)) { + var idx = this.index(elem); + if (idx === this.list.length) { //happens often, .push is way faster. + this.list.push(elem); + } else { + this.list.splice(idx, 0, elem); + } + this.emit("add", elem, idx); + } + }, + update: function (elem) { + var idx; + var i = this.list.length; + // Search from the back, we usually update the latest entries. + while (i--) { + if (this.list[i].id === elem.id) { + idx = i; + break; + } + } + + if (idx === -1) { //not contained in list + this.add(elem); + } else if (!this.filt(elem)) { + this.remove(elem.id); + } else { + if (this.sortfun(this.list[idx]) !== this.sortfun(elem)) { //sortpos has changed + this.remove(this.list[idx]); + this.add(elem); + } else { + this.list[idx] = elem; + this.emit("update", elem, idx); + } + } + }, + remove: function (elem_id) { + var idx = this.list.length; + while (idx--) { + if (this.list[idx].id === elem_id) { + this.list.splice(idx, 1); + this.emit("remove", elem_id, idx); + break; + } + } + } +}); \ No newline at end of file diff --git a/web/src/js/stores/base.js b/web/src/js/stores/base.js deleted file mode 100644 index f9534bd2..00000000 --- a/web/src/js/stores/base.js +++ /dev/null @@ -1,141 +0,0 @@ -function EventEmitter() { - this.listeners = {}; -} -EventEmitter.prototype.emit = function (event) { - if (!(event in this.listeners)) { - return; - } - var args = Array.prototype.slice.call(arguments, 1); - this.listeners[event].forEach(function (listener) { - listener.apply(this, args); - }.bind(this)); -}; -EventEmitter.prototype.addListener = function (events, f) { - events.split(" ").forEach(function (event) { - this.listeners[event] = this.listeners[event] || []; - this.listeners[event].push(f); - }.bind(this)); -}; -EventEmitter.prototype.removeListener = function (events, f) { - if (!(events in this.listeners)) { - return false; - } - events.split(" ").forEach(function (event) { - var index = this.listeners[event].indexOf(f); - if (index >= 0) { - this.listeners[event].splice(index, 1); - } - }.bind(this)); -}; - - -function Store() { - this._views = []; - this.reset(); -} -_.extend(Store.prototype, { - add: function (elem) { - if (elem.id in this._pos_map) { - return; - } - - this._pos_map[elem.id] = this._list.length; - this._list.push(elem); - for (var i = 0; i < this._views.length; i++) { - this._views[i].add(elem); - } - }, - update: function (elem) { - if (!(elem.id in this._pos_map)) { - return; - } - - this._list[this._pos_map[elem.id]] = elem; - for (var i = 0; i < this._views.length; i++) { - this._views[i].update(elem); - } - }, - remove: function (elem_id) { - if (!(elem.id in this._pos_map)) { - return; - } - - this._list.splice(this._pos_map[elem_id], 1); - this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].remove(elem_id); - } - }, - reset: function (elems) { - this._list = elems || []; - this._build_map(); - for (var i = 0; i < this._views.length; i++) { - this._views[i].recalculate(this._list); - } - }, - _build_map: function () { - this._pos_map = {}; - for (var i = 0; i < this._list.length; i++) { - var elem = this._list[i]; - this._pos_map[elem.id] = i; - } - }, - get: function (elem_id) { - return this._list[this._pos_map[elem_id]]; - } -}); - - -function LiveStore(type) { - Store.call(this); - this.type = type; - - this._updates_before_fetch = undefined; - this._fetchxhr = false; - - this.handle = this.handle.bind(this); - AppDispatcher.register(this.handle); - - // Avoid double-fetch on startup. - if (!(window.ws && window.ws.readyState === WebSocket.CONNECTING)) { - this.fetch(); - } -} -_.extend(LiveStore.prototype, Store.prototype, { - handle: function (event) { - if (event.type === ActionTypes.CONNECTION_OPEN) { - return this.fetch(); - } - if (event.type === this.type) { - if (event.cmd === "reset") { - this.fetch(); - } else if (this._updates_before_fetch) { - console.log("defer update", event); - this._updates_before_fetch.push(event); - } else { - this[event.cmd](event.data); - } - } - }, - close: function () { - AppDispatcher.unregister(this.handle); - }, - fetch: function () { - console.log("fetch " + this.type); - if (this._fetchxhr) { - this._fetchxhr.abort(); - } - this._fetchxhr = $.getJSON("/" + this.type, this.handle_fetch.bind(this)); - this._updates_before_fetch = []; // (JS: empty array is true) - }, - handle_fetch: function (data) { - this._fetchxhr = false; - console.log(this.type + " fetched.", this._updates_before_fetch); - this.reset(data.flows); - var updates = this._updates_before_fetch; - this._updates_before_fetch = false; - for (var i = 0; i < updates.length; i++) { - this.handle(updates[i]); - } - }, -}); \ No newline at end of file diff --git a/web/src/js/stores/eventlogstore.js b/web/src/js/stores/eventlogstore.js deleted file mode 100644 index 439c2360..00000000 --- a/web/src/js/stores/eventlogstore.js +++ /dev/null @@ -1,99 +0,0 @@ -// -// We have an EventLogView and an EventLogStore: -// The basic architecture is that one can request views on the event log -// from the store, which returns a view object and then deals with getting the data required for the view. -// The view object is accessed by React components and distributes updates etc. -// -// See also: components/EventLog.react.js -function EventLogView(store, live) { - EventEmitter.call(this); - this._store = store; - this.live = live; - this.log = []; - - this.add = this.add.bind(this); - - if (live) { - this._store.addListener(ActionTypes.ADD_EVENT, this.add); - } -} -_.extend(EventLogView.prototype, EventEmitter.prototype, { - close: function () { - this._store.removeListener(ActionTypes.ADD_EVENT, this.add); - }, - getAll: function () { - return this.log; - }, - add: function (entry) { - this.log.push(entry); - if (this.log.length > 200) { - this.log.shift(); - } - this.emit("change"); - }, - add_bulk: function (messages) { - var log = messages; - var last_id = log[log.length - 1].id; - var to_add = _.filter(this.log, function (entry) { - return entry.id > last_id; - }); - this.log = log.concat(to_add); - this.emit("change"); - } -}); - - -function _EventLogStore() { - EventEmitter.call(this); -} -_.extend(_EventLogStore.prototype, EventEmitter.prototype, { - getView: function (since) { - var view = new EventLogView(this, !since); - return view; - /* - //TODO: Really do bulk retrieval of last messages. - window.setTimeout(function () { - view.add_bulk([ - { - id: 1, - message: "Hello World" - }, - { - id: 2, - message: "I was already transmitted as an event." - } - ]); - }, 100); - - var id = 2; - view.add({ - id: id++, - message: "I was already transmitted as an event." - }); - view.add({ - id: id++, - message: "I was only transmitted as an event before the bulk was added.." - }); - window.setInterval(function () { - view.add({ - id: id++, - message: "." - }); - }, 1000); - return view; - */ - }, - handle: function (action) { - switch (action.type) { - case ActionTypes.ADD_EVENT: - this.emit(ActionTypes.ADD_EVENT, action.data); - break; - default: - return; - } - } -}); - - -var EventLogStore = new _EventLogStore(); -AppDispatcher.register(EventLogStore.handle.bind(EventLogStore)); \ No newline at end of file diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js deleted file mode 100644 index 8bffe5b2..00000000 --- a/web/src/js/stores/flowstore.js +++ /dev/null @@ -1,105 +0,0 @@ -function LiveFlowStore() { - return new LiveStore("flows"); -} - -function SortByInsertionOrder() { - this.i = 0; - this.map = {}; - this.key = this.key.bind(this); -} -SortByInsertionOrder.prototype.key = function (flow) { - if (!(flow.id in this.map)) { - this.i++; - this.map[flow.id] = this.i; - } - return this.map[flow.id]; -}; - -var default_sort = (new SortByInsertionOrder()).key; - -function FlowView(store, filt, sortfun) { - EventEmitter.call(this); - filt = filt || function (flow) { - return true; - }; - sortfun = sortfun || default_sort; - - this.store = store; - this.store._views.push(this); - this.recalculate(this.store._list, filt, sortfun); -} - -_.extend(FlowView.prototype, EventEmitter.prototype, { - close: function () { - this.store._views = _.without(this.store._views, this); - }, - recalculate: function (flows, filt, sortfun) { - if (filt) { - this.filt = filt; - } - if (sortfun) { - this.sortfun = sortfun; - } - - //Ugly workaround: Call .sortfun() for each flow once in order, - //so that SortByInsertionOrder make sense. - for (var i = 0; i < flows.length; i++) { - this.sortfun(flows[i]); - } - - this.flows = flows.filter(this.filt); - this.flows.sort(function (a, b) { - return this.sortfun(a) - this.sortfun(b); - }.bind(this)); - this.emit("recalculate"); - }, - index: function (flow) { - return _.sortedIndex(this.flows, flow, this.sortfun); - }, - add: function (flow) { - if (this.filt(flow)) { - var idx = this.index(flow); - if (idx === this.flows.length) { //happens often, .push is way faster. - this.flows.push(flow); - } else { - this.flows.splice(idx, 0, flow); - } - this.emit("add", flow, idx); - } - }, - update: function (flow) { - var idx; - var i = this.flows.length; - // Search from the back, we usually update the latest flows. - while (i--) { - if (this.flows[i].id === flow.id) { - idx = i; - break; - } - } - - if (idx === -1) { //not contained in list - this.add(flow); - } else if (!this.filt(flow)) { - this.remove(flow.id); - } else { - if (this.sortfun(this.flows[idx]) !== this.sortfun(flow)) { //sortpos has changed - this.remove(this.flows[idx]); - this.add(flow); - } else { - this.flows[idx] = flow; - this.emit("update", flow, idx); - } - } - }, - remove: function (flow_id) { - var i = this.flows.length; - while (i--) { - if (this.flows[i].id === flow_id) { - this.flows.splice(i, 1); - this.emit("remove", flow_id, i); - break; - } - } - } -}); \ No newline at end of file diff --git a/web/src/js/stores/settingstore.js b/web/src/js/stores/settingstore.js deleted file mode 100644 index 7eef9b8f..00000000 --- a/web/src/js/stores/settingstore.js +++ /dev/null @@ -1,28 +0,0 @@ -function _SettingsStore() { - EventEmitter.call(this); - - //FIXME: What do we do if we haven't requested anything from the server yet? - this.settings = { - version: "0.12", - showEventLog: true, - mode: "transparent", - }; -} -_.extend(_SettingsStore.prototype, EventEmitter.prototype, { - getAll: function () { - return this.settings; - }, - handle: function (action) { - switch (action.type) { - case ActionTypes.UPDATE_SETTINGS: - this.settings = action.settings; - this.emit("change"); - break; - default: - return; - } - } -}); - -var SettingsStore = new _SettingsStore(); -AppDispatcher.register(SettingsStore.handle.bind(SettingsStore)); diff --git a/web/src/js/utils.js b/web/src/js/utils.js index 329de956..8ae7aa54 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -12,6 +12,7 @@ var AutoScrollMixin = { }, }; + var StickyHeadMixin = { adjustHead: function () { // Abusing CSS transforms to set the element @@ -21,6 +22,7 @@ var StickyHeadMixin = { } }; + var Key = { UP: 38, DOWN: 40, @@ -38,6 +40,7 @@ var Key = { L: 76 }; + var formatSize = function (bytes) { var size = bytes; var prefix = ["B", "KB", "MB", "GB", "TB"]; @@ -49,6 +52,7 @@ var formatSize = function (bytes) { return (Math.floor(size * 100) / 100.0).toFixed(2) + prefix[i]; }; + var formatTimeDelta = function (milliseconds) { var time = milliseconds; var prefix = ["ms", "s", "min", "h"]; @@ -61,7 +65,39 @@ var formatTimeDelta = function (milliseconds) { return Math.round(time) + prefix[i]; }; + var formatTimeStamp = function (seconds) { var ts = (new Date(seconds * 1000)).toISOString(); return ts.replace("T", " ").replace("Z", ""); +}; + + +function EventEmitter() { + this.listeners = {}; +} +EventEmitter.prototype.emit = function (event) { + if (!(event in this.listeners)) { + return; + } + var args = Array.prototype.slice.call(arguments, 1); + this.listeners[event].forEach(function (listener) { + listener.apply(this, args); + }.bind(this)); +}; +EventEmitter.prototype.addListener = function (events, f) { + events.split(" ").forEach(function (event) { + this.listeners[event] = this.listeners[event] || []; + this.listeners[event].push(f); + }.bind(this)); +}; +EventEmitter.prototype.removeListener = function (events, f) { + if (!(events in this.listeners)) { + return false; + } + events.split(" ").forEach(function (event) { + var index = this.listeners[event].indexOf(f); + if (index >= 0) { + this.listeners[event].splice(index, 1); + } + }.bind(this)); }; \ No newline at end of file -- cgit v1.2.3