From: dsc Date: Wed, 4 Apr 2012 20:04:22 +0000 (-0700) Subject: First pass at data UI, mostly inactive due to time. Adds simple dropdown for datasets. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=40fbaf0e0aabcd92785d6bf3e8a12a058d7b94d1;p=limn.git First pass at data UI, mostly inactive due to time. Adds simple dropdown for datasets. --- diff --git a/lib/base.co b/lib/base.co index f1f28f7..2ac0400 100644 --- a/lib/base.co +++ b/lib/base.co @@ -1,5 +1,5 @@ -_ = require 'kraken/util/underscore' -op = require 'kraken/util/op' +{ _, op, +} = require 'kraken/util' Backbone = require 'backbone' @@ -19,9 +19,7 @@ BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{ constructor : function BaseModel @__class__ = @constructor - @__super__ = @constructor.__super__ - @__superclass__ = @__super__.constructor - # Backbone.NestedModel ... + @__superclass__ = @..__super__.constructor Backbone.Model ... @trigger 'create', this @@ -50,7 +48,7 @@ BaseModel = exports.BaseModel = Backbone.Model.extend do # {{{ # if _.str.contains key, '.' # _.setNested @attributes, key, value, opts # else - # @__super__.set.call this, key, value, opts + # Backbone.Model::set.call this, key, value, opts # # this # @@ -126,8 +124,14 @@ BaseList = exports.BaseList = Backbone.Collection.extend do # {{{ __bind__ : [] + + constructor : function BaseList + @__class__ = @constructor + @__superclass__ = @..__super__.constructor + Backbone.Collection ... + @trigger 'create', this + initialize : -> - @__super__ = @constructor.__super__ _.bindAll this, ...@__bind__ if @__bind__.length @@ -151,13 +155,27 @@ BaseList = exports.BaseList = Backbone.Collection.extend do # {{{ BaseView = exports.BaseView = Backbone.View.extend do # {{{ ctorName : 'BaseView' - # A list of method-names to bind on initialize; set this on a subclass to override. + /** + * A list of method-names to bind on initialize; set this on a subclass to override. + * @type Array + */ __bind__ : [] + /** + * @type Array + */ + subviews : [] + + constructor : function BaseView + @__class__ = @constructor + @__superclass__ = @..__super__.constructor + @subviews = [] + Backbone.View ... + @trigger 'create', this + initialize: -> - @__super__ = @constructor.__super__ _.bindAll this, ...@__bind__ if @__bind__.length @model.view = this diff --git a/lib/chart/chart-option-model.co b/lib/chart/chart-option-model.co index e5277f8..7cf2bc7 100644 --- a/lib/chart/chart-option-model.co +++ b/lib/chart/chart-option-model.co @@ -48,6 +48,9 @@ ChartOption = exports.ChartOption = Field.extend do # {{{ IGNORED_TAGS : <[ callback deprecated debugging ]> + constructor: function ChartOption + Field ... + initialize : -> # console.log "#this.initialize!" Field::initialize ... @@ -113,6 +116,10 @@ ChartOptionList = exports.ChartOptionList = FieldList.extend do # {{{ ctorName : 'ChartOptionList' model : ChartOption + + constructor: function ChartOptionList + FieldList ... + /** * Override to omit defaults from URL. */ diff --git a/lib/chart/chart-option-view.co b/lib/chart/chart-option-view.co index fa68de9..369c198 100644 --- a/lib/chart/chart-option-view.co +++ b/lib/chart/chart-option-view.co @@ -28,8 +28,11 @@ ChartOptionView = exports.ChartOptionView = FieldView.extend do # {{{ 'click .collapsed' : 'onClick' + constructor: function ChartOptionView + FieldView ... + render: -> - @__super__.render ... + FieldView::render ... @$el.addClass 'collapsed' if @isCollapsed this @@ -60,12 +63,14 @@ ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{ template : require 'kraken/template/chart-scaffold' collectionType : ChartOptionList subviewType : ChartOptionView - fields : '.fields' + fields : '.fields' # GraphView will set this ready : false + constructor: function ChartOptionScaffold + Scaffold ... initialize : -> @render = _.debounce @render.bind(this), DEBOUNCE_RENDER @@ -73,7 +78,7 @@ ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{ render: -> # console.log "#this.render() -> .isotope()" - # @__super__.render ... + # Scaffold::render ... return this unless @ready container = if @fields then @$el.find @fields else @$el container @@ -94,7 +99,7 @@ ChartOptionScaffold = exports.ChartOptionScaffold = Scaffold.extend do # {{{ * layout after collapse events. */ addOne: (field) -> - view = @__super__.addOne ... + view = Scaffold::addOne ... view.on 'change:collapse render', @render view diff --git a/lib/dataset/data-view.co b/lib/dataset/data-view.co new file mode 100644 index 0000000..75439f9 --- /dev/null +++ b/lib/dataset/data-view.co @@ -0,0 +1,63 @@ +Seq = require 'seq' +{ _, op, +} = require 'kraken/util' +{ BaseView, +} = require 'kraken/base' +{ DataSetView, +} = require 'kraken/dataset/dataset-view' +{ MetricEditView, +} = require 'kraken/dataset/metric-edit-view' + + +/** + * @class + */ +DataView = exports.DataView = BaseView.extend do # {{{ + ctorName : 'DataView' + tagName : 'section' + className : 'data-ui' + template : require 'kraken/template/data' + + data : {} + + + + constructor: function DataView + BaseView ... + + initialize: -> + @graph_id = @options.graph_id + BaseView::initialize ... + + @load() + + # @subviews.push @dataset_view = new DataSetView {@model, @graph_id} + # @$el.append @dataset_view.render().el + # @dataset_view.on 'edit-metric', @editMetric, this + # + # @subviews.push @metric_edit_view = new MetricEditView {dataset:@model, @graph_id} + # @$el.append @metric_edit_view.render().hide().el + + + toTemplateLocals: -> + attrs = _.clone @model.attributes + { $, _, op, @model, view:this, @data, @graph_id } import attrs + + + load: -> + $.getJSON '/datasources/all', (@data) ~> + @ready = true + @render() + @trigger 'ready', this + + # Don't rebuild HTML, simply notify subviews + render: -> + BaseView::render ... + + # _.invoke @subviews, 'render' + this + + editMetric: (metric) -> + @metric_edit_view.editMetric metric + +# }}} diff --git a/lib/dataset/dataset-model.co b/lib/dataset/dataset-model.co new file mode 100644 index 0000000..1b40c21 --- /dev/null +++ b/lib/dataset/dataset-model.co @@ -0,0 +1,61 @@ +Seq = require 'seq' +ColorBrewer = require 'colorbrewer' + +{ _, op, +} = require 'kraken/util' +{ BaseModel, BaseList, +} = require 'kraken/base' +{ Metric, MetricList, +} = require 'kraken/dataset/metric-model' +{ DataSource, DataSourceList, +} = require 'kraken/dataset/datasource-model' + + + +/** + * @class + */ +DataSet = exports.DataSet = BaseModel.extend do # {{{ + ctorName : 'DataSet' + urlRoot : '/datasets' + + /** + * @type DataSourceList + */ + sources : [] + + /** + * @type MetricList + */ + metrics : [] + + + constructor: function DataSet + BaseModel ... + + initialize : -> + BaseModel::initialize ... + @sources = new DataSourceList @attributes.sources + @metrics = new MetricList @attributes.metrics + + + defaults : -> + sources : [] # XXX: needed? metrics now implies this info + metrics : [] + # lines : [] + + + /** + * @returns {Array} The reified dataset, materialized to an array of data-series arrays. + */ + getData: -> + '/data/datasources/non_mobile_pageviews_by.timestamp.language.csv' + + newMetric: -> + index = @metrics.length + @metrics.add m = new Metric { index, color:ColorBrewer.Spectral[11][index] } + m + + +# }}} + diff --git a/lib/dataset/dataset-view.co b/lib/dataset/dataset-view.co new file mode 100644 index 0000000..eadaff0 --- /dev/null +++ b/lib/dataset/dataset-view.co @@ -0,0 +1,111 @@ +{ _, op, +} = require 'kraken/util' +{ BaseView, +} = require 'kraken/base' + + +/** + * @class + */ +DataSetView = exports.DataSetView = BaseView.extend do # {{{ + ctorName : 'DataSetView' + tagName : 'section' + className : 'dataset-ui dataset' + template : require 'kraken/template/dataset' + + events: + 'click .new-metric-button' : 'newMetric' + 'click .metrics .dataset-metric' : 'editMetric' + + views_by_cid : {} + active_view : null + + + constructor: function DataSetView + BaseView ... + + initialize: -> + @graph_id = @options.graph_id + BaseView::initialize ... + @views_by_cid = {} + @model.metrics.on 'add', @addMetric, this + + + newMetric: -> + # triggers 'add' on @model.metrics + @model.newMetric() + false + + addMetric: (metric) -> + console.log "#this.addMetric!", metric + if metric.view + _.remove @subviews, metric.view + delete @views_by_cid[metric.cid] + + @subviews.push view = new DataSetMetricView {model:metric, @graph_id} + @views_by_cid[metric.cid] = view + @$el.find '.metrics' .append view.render().el + + # @render() + view + + editMetric: (metric) -> + console.log "#this.editMetric!", metric + if metric instanceof [jQuery.Event, Event] + metric = $ metric.currentTarget .data 'model' + view = @views_by_cid[metric.cid] + console.log ' --> metric:', metric, 'view:', view + + @$el.find '.metrics' .removeClass 'metric-active' + view.$el.addClass 'metric-active' + view.$el.find '.activity-arrow' .css 'font-size', 2+view.$el.height() + @trigger 'edit-metric', metric + +# }}} + + + +/** + * @class + */ +DataSetMetricView = exports.DataSetMetricView = BaseView.extend do # {{{ + ctorName : 'DataSetMetricView' + tagName : 'tr' + className : 'dataset-metric metric' + template : require 'kraken/template/dataset-metric' + + + + constructor: function DataSetMetricView + BaseView ... + + initialize: -> + @graph_id = @options.graph_id + BaseView::initialize ... + + + toTemplateLocals: -> + m = @model.toJSON() + m import + graph_id : @graph_id + viewClasses : _.compact([ + if @model.isOk() then 'valid' else 'invalid', + if m.visible then 'visible' else 'hidden', + 'disabled' if m.disabled, + ]).map( -> "metric-#it" ).join ' ' + source : + if m.source_id and m.source_col_name + "#{m.source_id}.#{m.source_col_name}" + else + 'No source' + timespan : + if _.every ts = m.timespan, op.ok + "#{ts.start} to #{ts.end} by #{ts.step}" + else + '—' + + # XXX: Icons/classes for visible/disabled? + { $, _, op, @model, view:this } import m + +# }}} + diff --git a/lib/dataset/datasource-model.co b/lib/dataset/datasource-model.co new file mode 100644 index 0000000..ef23d98 --- /dev/null +++ b/lib/dataset/datasource-model.co @@ -0,0 +1,47 @@ +{ _, op, +} = require 'kraken/util' +{ BaseModel, BaseList, BaseView, +} = require 'kraken/base' + + +/** + * @class + */ +DataSource = exports.DataSource = BaseModel.extend do # {{{ + ctorName : 'DataSource' + urlRoot : '/datasources' + + + constructor: function DataSource + BaseModel ... + + initialize: -> + BaseModel::initialize ... + + + defaults: -> + {} + + url: -> + "/data/#{@id}.json" + +# }}} + + + +/** + * @class + */ +DataSourceList = exports.DataSourceList = BaseList.extend do # {{{ + ctorName : 'DataSourceList' + urlRoot : '/datasources' + model : DataSource + + constructor: function DataSourceList + BaseList ... + + initialize : -> + BaseList::initialize ... + + +# }}} diff --git a/lib/dataset/datasource-ui-view.co b/lib/dataset/datasource-ui-view.co new file mode 100644 index 0000000..25d52d1 --- /dev/null +++ b/lib/dataset/datasource-ui-view.co @@ -0,0 +1,32 @@ +{ _, op, +} = require 'kraken/util' +{ BaseModel, BaseList, BaseView, +} = require 'kraken/base' + + +/** + * @class + */ +DataSourceUIView = exports.DataSourceUIView = BaseView.extend do # {{{ + __bind__ : <[ ]> + ctorName : 'DataSourceUIView' + tagName : 'section' + className : 'datasource-ui' + template : require 'kraken/template/datasource-ui' + + + constructor: function DataSourceUIView + BaseView ... + + initialize: -> + @graph_id = @options.graph_id + BaseView::initialize ... + + toTemplateLocals: -> + locals = @model.toJSON() + locals import + graph_id : @graph_id + source_summary : 'Source Summary' + metric_summary : 'Metric Summary' + timespan_summary : 'Timespan Summary' +# }}} diff --git a/lib/dataset/datasource-view.co b/lib/dataset/datasource-view.co new file mode 100644 index 0000000..e7d6aa2 --- /dev/null +++ b/lib/dataset/datasource-view.co @@ -0,0 +1,25 @@ +{ _, op, +} = require 'kraken/util' +{ BaseModel, BaseList, BaseView, +} = require 'kraken/base' + + +/** + * @class + */ +DataSourceView = exports.DataSourceView = BaseView.extend do # {{{ + __bind__ : <[ ]> + ctorName : 'DataSourceView' + tagName : 'section' + className : 'datasource' + template : require 'kraken/template/datasource' + + + + constructor: function DataSourceView + BaseView ... + + initialize: -> + BaseView::initialize ... + +# }}} diff --git a/lib/dataset/index.co b/lib/dataset/index.co new file mode 100644 index 0000000..56d3e89 --- /dev/null +++ b/lib/dataset/index.co @@ -0,0 +1,14 @@ +metric_model = require 'kraken/dataset/metric-model' +metric_view = require 'kraken/dataset/metric-view' +metric_edit_view = require 'kraken/dataset/metric-edit-view' +datasource_model = require 'kraken/dataset/datasource-model' +datasource_view = require 'kraken/dataset/datasource-view' +datasource_ui_view = require 'kraken/dataset/datasource-ui-view' +dataset_model = require 'kraken/dataset/dataset-model' +dataset_view = require 'kraken/dataset/dataset-view' +data_view = require 'kraken/dataset/data-view' + +exports import metric_model import metric_view import metric_edit_view \ + import datasource_model import datasource_view import datasource_ui_view \ + import dataset_model import dataset_view \ + import data_view diff --git a/lib/dataset/metric-edit-view.co b/lib/dataset/metric-edit-view.co new file mode 100644 index 0000000..b397ff5 --- /dev/null +++ b/lib/dataset/metric-edit-view.co @@ -0,0 +1,55 @@ +{ _, op, +} = require 'kraken/util' +{ BaseView, +} = require 'kraken/base' +{ Metric, +} = require 'kraken/dataset/metric-model' +{ DataSourceUIView, +} = require 'kraken/dataset/datasource-ui-view' + + + +/** + * @class + */ +MetricEditView = exports.MetricEditView = BaseView.extend do # {{{ + ctorName : 'MetricEditView' + tagName : 'section' + className : 'metric-edit-ui' + template : require 'kraken/template/metric-edit' + + graph_id : null + dataset : null + datasource_ui_view : null + + + constructor: function MetricEditView + BaseView ... + + initialize: -> + this import @options.{graph_id, dataset} + @model or= new Metric + BaseView::initialize ... + @subviews.push @datasource_ui_view = new DataSourceUIView {@model, @graph_id} + @$el.find '.metric-datasource' .append @datasource_ui_view.render().el + + + toTemplateLocals: -> + locals = BaseView::toTemplateLocals ... + locals import {@graph_id} + + build: -> + BaseView::build ... + if @datasource_ui_view + @$el.find '.metric-datasource' .append @datasource_ui_view.render().el + this + + editMetric: (metric) -> + console.log "#this.editMetric!", metric + @model = metric + @render() + @show() + this + +# }}} + diff --git a/lib/dataset/metric-model.co b/lib/dataset/metric-model.co new file mode 100644 index 0000000..63a88a0 --- /dev/null +++ b/lib/dataset/metric-model.co @@ -0,0 +1,73 @@ +{ _, op, +} = require 'kraken/util' +{ BaseModel, BaseList, +} = require 'kraken/base' + + +/** + * @class + */ +Metric = exports.Metric = BaseModel.extend do # {{{ + ctorName : 'Metric' + urlRoot : '/metrics' + + /** + * Data source of the Metric. + * @type DataSource + */ + source : null + + + constructor: function Metric + BaseModel ... + + initialize : -> + BaseModel::initialize ... + + + defaults : -> + index : 0 + label : 'New Metric' + type : 'int' + timespan : { start:null, stop:null, step:null } + disabled : false + + # DataSource + source_id : null + source_col_idx : -1 + source_col_name : '' + + # Chart Options + color : null + visible : true + format_value : null + format_axis : null + + scale : 1.0 + transforms : [] + + + /** + * Check whether the metric has aiight-looking values so we don't + * attempt to graph unconfigured crap. + */ + isOk: -> + (label = @get('label')) and label is not 'New Metric' + and @get('source_id') and @get('source_col_idx') >= 0 + and _.every @get('timespan'), op.ok + + + +# }}} + + +/** + * @class + */ +MetricList = exports.MetricList = BaseList.extend do # {{{ + ctorName : 'MetricList' + urlRoot : '/metrics' + model : Metric + + constructor: function MetricList then BaseList ... +# }}} diff --git a/lib/dataset/metric-view.co b/lib/dataset/metric-view.co new file mode 100644 index 0000000..e69de29 diff --git a/lib/graph/graph-display-view.co b/lib/graph/graph-display-view.co index 8d24a78..cdbdfd3 100644 --- a/lib/graph/graph-display-view.co +++ b/lib/graph/graph-display-view.co @@ -21,6 +21,9 @@ GraphDisplayView = exports.GraphDisplayView = BaseView.extend do # {{{ # 'submit .value' : 'update' + constructor: function GraphDisplayView + BaseView ... + initialize: -> @model or= new Graph BaseView::initialize ... diff --git a/lib/graph/graph-edit-view.co b/lib/graph/graph-edit-view.co index 49dca93..c95a6e8 100644 --- a/lib/graph/graph-edit-view.co +++ b/lib/graph/graph-edit-view.co @@ -7,7 +7,7 @@ _ = require 'kraken/util/underscore' } = require 'kraken/chart' { Graph, } = require 'kraken/graph/graph-model' -{ DataSetView, +{ DataView, DataSetView, DataSet, } = require 'kraken/dataset' root = do -> this @@ -45,29 +45,33 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ 'keypress form.details input[type="text"]' : 'onKeypress' 'keypress form.options .value' : 'onKeypress' 'submit form.details' : 'onDetailsSubmit' + 'change select' : 'onDetailsSubmit' 'submit form.options' : 'onOptionsSubmit' 'change input[type="checkbox"]' : 'onOptionsSubmit' - subviews: [] - ready: false + data : {} + ready : false + constructor: function GraphEditView + BaseView ... initialize : (o={}) -> - @subviews = [] + @data = {} @model or= new Graph + @id = _.domize 'graph', (@model.id or @model.get('slug') or @model.cid) BaseView::initialize ... # console.log "#this.initialize!" for name of @__debounce__ @[name] = _.debounce @[name], DEBOUNCE_RENDER - @id = _.domize 'graph', (@model.get('slug') or @model.id or @model.cid) + @viewport = @$el.find '.viewport' ### Model Events @model - .on 'ready', @onReady - .on 'sync', @onSync + .on 'ready', @onReady, this + .on 'sync', @onSync, this .on 'destroy', @remove, this .on 'change', @render, this .on 'change:dataset', @onModelChange @@ -76,11 +80,6 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ console.error "#this.error!", arguments # TODO: UI alert - ### Graph Data UI - @subviews.push @dataset = new DataSetView { model:@model.get 'dataset' } - @$el.find '.graph-data-pane' .append @dataset.el - @dataset.on 'change', @onDataChange - ### Chart Options Tab, Scaffold @scaffold = new ChartOptionScaffold @$el.find '.graph-options-pane' .append @scaffold.el @@ -92,8 +91,13 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ # Rerender the options boxes once the tab is visible @$el.on 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab + ### Graph Data UI + # @subviews.push @data = new DataView { model:@model.get('dataset'), graph_id:@id } + @subviews.push @data = new DataView { model:new DataSet, graph_id:@id } + @$el.find '.graph-data-pane' .append @data.render().el + @data.on 'change', @onDataChange + ### Chart Viewport - @viewport = @$el.find '.viewport' @resizeViewport() # Resize chart on window resize @@ -145,7 +149,8 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ toTemplateLocals: -> attrs = _.clone @model.attributes delete attrs.options - delete attrs.dataset + # delete attrs.dataset + attrs.data = @data { $, _, op, @model, view:this } import attrs @@ -196,7 +201,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ # Redraw chart inside viewport. renderChart: -> - data = @model.get 'dataset' .getData() + data = @model.get 'dataset' #.getData() size = @resizeViewport() # XXX: use @model.changedAttributes() to calculate what to update @@ -302,9 +307,11 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ onReady: -> - console.log "(#this via GraphEditView).ready!" - @ready = @scaffold.ready = true - @onSync() + return if @ready + $.getJSON '/datasources/all', (@data) ~> + console.log "(#this via GraphEditView).ready!" + @ready = @scaffold.ready = true + @onSync() onSync: -> return unless @ready @@ -340,7 +347,7 @@ GraphEditView = exports.GraphEditView = BaseView.extend do # {{{ @model.setOption(key, value, {+silent}) onDataChange: -> - ... + console.log 'onDataChange!' onFirstClickRenderOptionsTab: -> @$el.off 'click', '.graph-options-tab', @onFirstClickRenderOptionsTab diff --git a/lib/graph/graph-model.co b/lib/graph/graph-model.co index 339b6e8..4b55d54 100644 --- a/lib/graph/graph-model.co +++ b/lib/graph/graph-model.co @@ -59,8 +59,8 @@ Graph = exports.Graph = BaseModel.extend do # {{{ slug : '' name : '' desc : '' - # dataset : '/data/datasources/non_mobile_pageviews_by.timestamp.language.csv' - dataset : null + dataset : '/data/datasources/non_mobile_pageviews_by.timestamp.language.csv' + # dataset : null width : 'auto' height : 320 chartType : 'dygraphs' @@ -73,31 +73,28 @@ Graph = exports.Graph = BaseModel.extend do # {{{ - - constructor : (attributes={}, opts) -> - @on 'ready', ~> console.log "(#this via Graph).ready!" + constructor: function Graph (attributes={}, opts) + # @on 'ready', ~> console.log "(#this via Graph).ready!" attributes.options or= {} @optionCascade = new Cascade attributes.options BaseModel.call this, attributes, opts - initialize : (attributes, opts) -> - @__super__.initialize ... - opts = {+autoLoad} import (opts or {}) + initialize: (attributes, opts) -> + BaseModel::initialize ... + opts = {+autoload} import (opts or {}) @constructor.register this @parents = new GraphList + # TODO: Load on-demand @chartType = ChartType.lookup @get('chartType') - # unless @id or @get('id') or @get('slug') - # @set 'slug', "unsaved_graph_#{@cid}" - # Insert submodels in place of JSON - @set 'dataset', new DataSet(@get('dataset')), {+silent} + # @set 'dataset', new DataSet(@get('dataset')), {+silent} @trigger 'init', this - @load() if opts.autoLoad + @load() if opts.autoload load: (opts={}) -> @@ -114,7 +111,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{ console.error "#{this}.fetch() --> error! #arguments" next.ok() success : (model, res) ~> - console.log "#{this}.fetch() --> success!", res + # console.log "#{this}.fetch() --> success!", res next.ok res .seq_ (next) ~> next.ok @get('parents') @@ -137,7 +134,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{ if _.startsWith key, 'options.' @getOption key.slice(8) else - (@__super__ or BaseModel::).get.call this, key + (@..__super__ or BaseModel::).get.call this, key set: (key, value, opts) -> @@ -148,7 +145,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{ values = { "#key": value } values = @parse values - setter = (@__super__ or BaseModel::).set + setter = (@..__super__ or BaseModel::).set # Merge options in, firing granulated change events if values.options @@ -182,7 +179,7 @@ Graph = exports.Graph = BaseModel.extend do # {{{ else values = { "#key": value } - _.dump values, "#this.setOption" + # _.dump values, "#this.setOption" options = @get('options') changed = false for key, value in values @@ -305,7 +302,7 @@ Graph import do CACHE : GRAPH_CACHE register: (model) -> - console.log "#{@CACHE}.register(#{model.id or model.get('id')})", model + # console.log "#{@CACHE}.register(#{model.id or model.get('id')})", model if @CACHE.contains model @CACHE.remove model, {+silent} @CACHE.add model diff --git a/lib/scaffold/scaffold-model.co b/lib/scaffold/scaffold-model.co index eda72ed..05e320d 100644 --- a/lib/scaffold/scaffold-model.co +++ b/lib/scaffold/scaffold-model.co @@ -13,6 +13,9 @@ Field = exports.Field = BaseModel.extend do # {{{ valueAttribute : 'value' + constructor: function Field + BaseModel ... + initialize: -> _.bindAll this, ...(_.functions this .filter -> _.startsWith(it, 'parse')) @set 'value', @get('default'), {+silent} if not @has 'value' @@ -107,6 +110,9 @@ FieldList = exports.FieldList = BaseList.extend do # {{{ ctorName : 'FieldList' model : Field + + constructor: function FieldList then BaseList ... + /** * Collects a map of fields to their values, excluding those set to `null` or their default. * @returns {Object} diff --git a/lib/scaffold/scaffold-view.co b/lib/scaffold/scaffold-view.co index 21aa194..be8e36a 100644 --- a/lib/scaffold/scaffold-view.co +++ b/lib/scaffold/scaffold-view.co @@ -17,6 +17,9 @@ FieldView = exports.FieldView = BaseView.extend do # {{{ 'submit .value' : 'update' + constructor: function FieldView + BaseView ... + initialize: -> # console.log "#this.initialize!" BaseView::initialize ... @@ -66,11 +69,12 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{ subviewType : FieldView + constructor: function Scaffold + BaseView ... + initialize: -> - @subviews = [] CollectionType = @collectionType @model = @collection or= new CollectionType - BaseView::initialize ... @collection.on 'add', @addOne @@ -80,7 +84,7 @@ Scaffold = exports.Scaffold = BaseView.extend do # {{{ addOne: (field) -> - # console.log "[S] #this.addOne!", @__super__ + # console.log "[S] #this.addOne!", @..__super__ _.remove @subviews, field.view if field.view # avoid duplicating event propagation diff --git a/lib/server/controllers/datasource.co b/lib/server/controllers/datasource.co index 90c68f3..b77d4e2 100644 --- a/lib/server/controllers/datasource.co +++ b/lib/server/controllers/datasource.co @@ -14,7 +14,7 @@ class DataSourceController extends Controller dataDir : 'data/graphs' mapping : - all : 'getAllData' + all : 'allData' -> super ... diff --git a/lib/server/server.co b/lib/server/server.co index fcf1090..afd3f66 100755 --- a/lib/server/server.co +++ b/lib/server/server.co @@ -139,7 +139,49 @@ app.configure -> Controller = require './controller' app.controller require './controllers/graph' -app.controller require './controllers/datasource' +ds = app.controller require './controllers/datasource' +# ds.map 'get', 'all', ds.allData.bind(ds) + +YAML_EXT_PAT = /\.ya?ml$/i +app.get '/datasources/all', (req, res, next) -> + data = {} + files = [] + Seq() + .seq fs.readdir, 'data/datasources', Seq + .flatten() + .filter -> /\.(json|ya?ml)$/.test it + .seq -> + files := @stack.slice() + # console.log 'files:', files + @ok files + .flatten() + .parMap (f) -> + # console.log "fs.readFile '#CWD/data/#f'" + fs.readFile "data/datasources/#f", 'utf8', this + .parMap (text, i) -> + f = files[i] + # console.log "parsing file[#i]: '#f' -> text[#{text.length}]..." + k = f.replace YAML_EXT_PAT, '.json' + v = data[k] = {} + try + if YAML_EXT_PAT.test f + v = data[k] = yaml.load text + else + v = data[k] = JSON.parse text + # console.log "#f ok!", data + @ok v + catch err + console.error "[/data/all] catch! #err" + console.error err + console.error that if err.stack + res.send { error:String(err), partial_data:data } + .seq -> res.send data + .catch (err) -> + console.error '[/data/all] catch!' + console.error err + console.error that if err.stack + res.send { error:String(err), partial_data:data } + app.get '/', (req, res) -> res.render 'dashboard' diff --git a/lib/template/data.jade b/lib/template/data.jade new file mode 100644 index 0000000..21c9a26 --- /dev/null +++ b/lib/template/data.jade @@ -0,0 +1,13 @@ +section.data-ui + .row-fluid + .dataset-controls.dropdown + select(name="dataset") + for datum, k in data + option(value=datum.url, name=datum.id) #{datum.name} + + //- + a.span3.dataset.dropdown-toggle(data-toggle="dropdown", data-target="#{graph_id} .dataset-dropdown", href="#dataset-dropdown") Select... + ul.dropdown-menu + for datum, k in data + li: a(href="#", data-dataset="#{datum.id}") #{datum.name} + diff --git a/lib/template/dataset-metric.jade b/lib/template/dataset-metric.jade new file mode 100644 index 0000000..e15dac5 --- /dev/null +++ b/lib/template/dataset-metric.jade @@ -0,0 +1,8 @@ +tr.dataset-metric(class=viewClasses) + td.col-label(style="color: #{color};") #{label} + td.col-source #{source} + td.col-time #{timespan} + td.col-actions + a.delete-metric-button.close(href="#") × + .activity-arrow: div.inner + diff --git a/lib/template/dataset.jade b/lib/template/dataset.jade new file mode 100644 index 0000000..89b5bb7 --- /dev/null +++ b/lib/template/dataset.jade @@ -0,0 +1,26 @@ +section.dataset-ui.dataset + .inner + h4 Graph Data Set + + .dataset-buttons + a.new-metric-button.btn.btn-success.btn-small(href="#") + i.icon-plus-sign.icon-white + | Add Metric + //- + a.clear-metrics-button.btn.btn-danger.btn-small(href="#") + i.icon-remove-sign.icon-white + | Remove All Metrics + + .dataset-metrics + table.table.table-striped + thead + tr + th.col-label Label + th.col-source Source + th.col-times Timespan + th.col-actions Actions + tbody.metrics + //- DataSetMetricViews attach here + + + diff --git a/lib/template/datasource-ui.jade b/lib/template/datasource-ui.jade new file mode 100644 index 0000000..870c864 --- /dev/null +++ b/lib/template/datasource-ui.jade @@ -0,0 +1,23 @@ +section.datasource-ui + + section.datasource-summary(data-toggle="collapse", data-target="##{graph_id} .datasource-ui .datasource-selector") + ul.breadcrumb + li #{source_summary} + span.divider + li #{metric_summary} + span.divider + li #{timespan_summary} + + i.icon-chevron-down.pull-right + i.icon-chevron-up.pull-right + + section.datasource-selector.collapse.in + .tabbable.tabs-left + ul.datasource-sources-list.nav.nav-tabs + li: h4 Data Sources + //- li: a(href="#", data-toggle="tab", data-target="") + .datasource-sources-details.tab-content + .datasource-source.tab + .datasource-source-details + .datasource-source-time + .datasource-source-metrics diff --git a/lib/template/datasource.jade b/lib/template/datasource.jade new file mode 100644 index 0000000..e69de29 diff --git a/lib/template/graph-edit.jade b/lib/template/graph-edit.jade index 574a32d..ba1ae30 100644 --- a/lib/template/graph-edit.jade +++ b/lib/template/graph-edit.jade @@ -1,11 +1,10 @@ -- var id = model.id || model.cid || view.id -- var graph_id = view.id || id +- var graph_id = view.id || model.id || model.cid section.graph.graph-edit(id=graph_id) form.details.form-horizontal .name-row.row-fluid.control-group //- label.name.control-label(for="#{id}_name"): h3 Graph Name - input.span6.name(type='text', id="#{id}_name", name="name", placeholder='Graph Name', value=name) + input.span6.name(type='text', id="#{graph_id}_name", name="name", placeholder='Graph Name', value=name) .row-fluid .viewport @@ -32,41 +31,40 @@ section.graph.graph-edit(id=graph_id) | Done ul.nav.subnav.nav-pills li: h3 Graph - li.active: a(href="#graph-#{graph_id}-info", data-toggle="tab") Info - li: a(href="#graph-#{graph_id}-data", data-toggle="tab") Data - li: a.graph-options-tab(href="#graph-#{graph_id}-options", data-toggle="tab") Options + li.active: a(href="##{graph_id}-tab-info", data-toggle="tab") Info + li: a(href="##{graph_id}-tab-data", data-toggle="tab") Data + li: a.graph-options-tab(href="##{graph_id}-tab-options", data-toggle="tab") Options .tab-content - .graph-info-pane.tab-pane.active(id="graph-#{graph_id}-info") + .graph-info-pane.tab-pane.active(id="#{graph_id}-tab-info") .row-fluid .half.control-group .control-group - label.slug.control-label(for="#{id}_slug") Slug + label.slug.control-label(for="#{graph_id}_slug") Slug .controls - input.span3.slug(type='text', id="#{id}_slug", name='slug', placeholder='graph_slug', value=slug) + input.span3.slug(type='text', id="#{graph_id}_slug", name='slug', placeholder='graph_slug', value=slug) p.help-block The slug uniquely identifies this graph and will be displayed in the URL. .control-group - label.width.control-label(for="#{id}_width") Size + label.width.control-label(for="#{graph_id}_width") Size .controls - input.span1.width(type='text', id="#{id}_width", name='width', value=width) + input.span1.width(type='text', id="#{graph_id}_width", name='width', value=width) | × - input.span1.height(type='text', id="#{id}_height", name='height', value=height) + input.span1.height(type='text', id="#{graph_id}_height", name='height', value=height) p.help-block Choosing 'auto' will size the graph to the viewport bounds. .half.control-group - label.desc.control-label(for="#{id}_desc") Description + label.desc.control-label(for="#{graph_id}_desc") Description .controls //- textarea.span3.desc(id='desc', name='desc', placeholder='Graph description.') #{desc} - + p.help-block A description of the graph. - .graph-data-pane.tab-pane(id="graph-#{graph_id}-data") - //- - .row-fluid - label.dataset.control-label(for="#{id}_dataset") Data Set - .controls - input.span3.dataset(type='text', id="#{id}_dataset", name='dataset', placeholder='URL to dataset file', value=dataset) - p.help-block This dataset filename will soon be replaced by a friendly UI. + .graph-data-pane.tab-pane(id="#{graph_id}-tab-data") + .row-fluid + //- + label.dataset.control-label(for="#{graph_id}_dataset") Data Set + input.span3.dataset(type='text', id="#{graph_id}_dataset", name='dataset', placeholder='URL to dataset file', value=dataset) + p.help-block This dataset filename will soon be replaced by a friendly UI. - .graph-options-pane.tab-pane(id="graph-#{graph_id}-options") + .graph-options-pane.tab-pane(id="#{graph_id}-tab-options") diff --git a/lib/template/metric-edit.jade b/lib/template/metric-edit.jade new file mode 100644 index 0000000..f48442a --- /dev/null +++ b/lib/template/metric-edit.jade @@ -0,0 +1,17 @@ +section.metric-edit-ui + .inner: form.form-horizontal + + .metric-header.control-group.row-fluid + .color-picker + input.metric-color(type='hidden', id="#{graph_id}_metric", name="color", value=color) + input.metric-label(type='text', id="#{graph_id}_metric_label", name='label', placeholder='Metric Label', value=label) + + .metric-datasource.row-fluid + + .metric-actions.row-fluid + a.delete-button.btn.btn-danger(href="#") + i.icon-remove.icon-white + | Delete + + .metric-options.row-fluid + \ No newline at end of file diff --git a/lib/template/metric.jade b/lib/template/metric.jade new file mode 100644 index 0000000..e69de29 diff --git a/lib/util/op.co b/lib/util/op.co index 0c7c0b3..9129433 100644 --- a/lib/util/op.co +++ b/lib/util/op.co @@ -16,6 +16,7 @@ module.exports = op = # values val : (def,o) -> o ? def ok : (o) -> o? + notOk : (o) -> o!? first : (a) -> a second : (_,a) -> a diff --git a/www/css/bootstrap-variables.styl b/www/css/bootstrap-variables.styl new file mode 100644 index 0000000..a8b08dd --- /dev/null +++ b/www/css/bootstrap-variables.styl @@ -0,0 +1,201 @@ +// Bootstrap Variables +// Auto-translated to Stylus from LESS +// ----------------------------------------------------- + + + +// GLOBAL VALUES +// -------------------------------------------------- + + +// Grays +// ------------------------- +$boot_black = #000 +$boot_grayDarker = #222 +$boot_grayDark = #333 +$boot_gray = #555 +$boot_grayLight = #999 +$boot_grayLighter = #eee +$boot_white = #fff + + +// Accent colors +// ------------------------- +$boot_blue = #049cdb +$boot_blueDark = #0064cd +$boot_green = #46a546 +$boot_red = #9d261d +$boot_yellow = #ffc40d +$boot_orange = #f89406 +$boot_pink = #c3325f +$boot_purple = #7a43b6 + + +// Scaffolding +// ------------------------- +$boot_bodyBackground = $boot_white +$boot_textColor = $boot_grayDark + + +// Links +// ------------------------- +$boot_linkColor = #08c +$boot_linkColorHover = darken($boot_linkColor, 15%) + + +// Typography +// ------------------------- +$boot_baseFontSize = 13px +$boot_baseFontFamily = "Helvetica Neue", Helvetica, Arial, sans-serif +$boot_baseLineHeight = 18px +$boot_altFontFamily = Georgia, "Times New Roman", Times, serif + +$boot_headingsFontFamily = inherit // empty to use BS default, $boot_baseFontFamily +$boot_headingsFontWeight = bold // instead of browser default, bold +$boot_headingsColor = inherit // empty to use BS default, $boot_textColor + + +// Tables +// ------------------------- +$boot_tableBackground = transparent // overall background-color +$boot_tableBackgroundAccent = #f9f9f9 // for striping +$boot_tableBackgroundHover = #f5f5f5 // for hover +$boot_tableBorder = #ddd // table and cell border + + +// Buttons +// ------------------------- +$boot_btnBackground = $boot_white +$boot_btnBackgroundHighlight = darken($boot_white, 10%) +$boot_btnBorder = darken($boot_white, 20%) + +$boot_btnPrimaryBackground = $boot_linkColor +$boot_btnPrimaryBackgroundHighlight = spin($boot_btnPrimaryBackground, 15%) + +$boot_btnInfoBackground = #5bc0de +$boot_btnInfoBackgroundHighlight = #2f96b4 + +$boot_btnSuccessBackground = #62c462 +$boot_btnSuccessBackgroundHighlight = #51a351 + +$boot_btnWarningBackground = lighten($boot_orange, 15%) +$boot_btnWarningBackgroundHighlight = $boot_orange + +$boot_btnDangerBackground = #ee5f5b +$boot_btnDangerBackgroundHighlight = #bd362f + +$boot_btnInverseBackground = $boot_gray +$boot_btnInverseBackgroundHighlight = $boot_grayDarker + + +// Forms +// ------------------------- +$boot_inputBackground = $boot_white +$boot_inputBorder = #ccc +$boot_inputDisabledBackground = $boot_grayLighter + + +// Dropdowns +// ------------------------- +$boot_dropdownBackground = $boot_white +$boot_dropdownBorder = rgba(0,0,0,.2) +$boot_dropdownLinkColor = $boot_grayDark +$boot_dropdownLinkColorHover = $boot_white +$boot_dropdownLinkBackgroundHover = $boot_linkColor + + + + +// COMPONENT VARIABLES +// -------------------------------------------------- + +// Z-index master list +// ------------------------- +// Used for a bird's eye view of components dependent on the z-axis +// Try to avoid customizing these :) +$boot_zindexDropdown = 1000 +$boot_zindexPopover = 1010 +$boot_zindexTooltip = 1020 +$boot_zindexFixedNavbar = 1030 +$boot_zindexModalBackdrop = 1040 +$boot_zindexModal = 1050 + + +// Sprite icons path +// ------------------------- +$boot_iconSpritePath = "../img/glyphicons-halflings.png" +$boot_iconWhiteSpritePath = "../img/glyphicons-halflings-white.png" + + +// Input placeholder text color +// ------------------------- +$boot_placeholderText = $boot_grayLight + + +// Hr border color +// ------------------------- +$boot_hrBorder = $boot_grayLighter + + +// Navbar +// ------------------------- +$boot_navbarHeight = 40px +$boot_navbarBackground = $boot_grayDarker +$boot_navbarBackgroundHighlight = $boot_grayDark + +$boot_navbarText = $boot_grayLight +$boot_navbarLinkColor = $boot_grayLight +$boot_navbarLinkColorHover = $boot_white +$boot_navbarLinkColorActive = $boot_navbarLinkColorHover +$boot_navbarLinkBackgroundHover = transparent +$boot_navbarLinkBackgroundActive = $boot_navbarBackground + +$boot_navbarSearchBackground = lighten($boot_navbarBackground, 25%) +$boot_navbarSearchBackgroundFocus = $boot_white +$boot_navbarSearchBorder = darken($boot_navbarSearchBackground, 30%) +$boot_navbarSearchPlaceholderColor = #ccc + + +// Hero unit +// ------------------------- +$boot_heroUnitBackground = $boot_grayLighter +$boot_heroUnitHeadingColor = inherit +$boot_heroUnitLeadColor = inherit + + +// Form states and alerts +// ------------------------- +$boot_warningText = #c09853 +$boot_warningBackground = #fcf8e3 +$boot_warningBorder = darken(spin($boot_warningBackground, -10), 3%) + +$boot_errorText = #b94a48 +$boot_errorBackground = #f2dede +$boot_errorBorder = darken(spin($boot_errorBackground, -10), 3%) + +$boot_successText = #468847 +$boot_successBackground = #dff0d8 +$boot_successBorder = darken(spin($boot_successBackground, -10), 5%) + +$boot_infoText = #3a87ad +$boot_infoBackground = #d9edf7 +$boot_infoBorder = darken(spin($boot_infoBackground, -10), 7%) + + + + +// GRID +// -------------------------------------------------- + +// Default 940px grid +// ------------------------- +$boot_gridColumns = 12 +$boot_gridColumnWidth = 60px +$boot_gridGutterWidth = 20px +$boot_gridRowWidth = ($boot_gridColumns * $boot_gridColumnWidth) + ($boot_gridGutterWidth * ($boot_gridColumns - 1)) + + +// Fluid grid +// ------------------------- +$boot_fluidGridColumnWidth = 6.382978723% +$boot_fluidGridGutterWidth = 2.127659574% diff --git a/www/css/colors.styl b/www/css/colors.styl index a486891..3ada78f 100644 --- a/www/css/colors.styl +++ b/www/css/colors.styl @@ -4,8 +4,8 @@ invert($v) hsla( 180deg * hue($v), 100% - saturation($v), 100% - lightness($v), alpha($v) ) contrast_direction($v) - // ((lightness($v) * (100% - saturation($v))) < 50%) ? 1 : -1 (lightness($v) < 50%) ? 1 : -1 + // ((lightness($v) * (100% - saturation($v))) < 50%) ? 1 : -1 /** Project Colors **/ diff --git a/www/css/data.styl b/www/css/data.styl new file mode 100644 index 0000000..e971d11 --- /dev/null +++ b/www/css/data.styl @@ -0,0 +1,105 @@ +@import 'nib' +@import 'colors' +@import 'bootstrap-variables' + + + +section.graph section.data-ui + h4 + display none + + .dataset-controls + padding 1em + + /* * * * DataSet UI * * * */ + section.dataset-ui + width 40% + border-right 1px solid #ccc + + .inner + padding 1em 0 1em 1em + + .dataset-buttons + margin-bottom 1em + + .dataset-metrics + thead + display none + td + line-height 1 + vertical-align middle + tr:last-child td + border-bottom 1px solid #ddd + + .col-label + width 40% + .col-source + width 25% + .col-time + width 25% + .col-actions + padding-right 14px + + .col-source, .col-time + font-size 80% + color $dark + + .dataset-metric + cursor pointer + // &:hover + // cursor pointer + + &.metric-active + font-weight bold + td + border 1px solid #ccc + border-width 1px 0 + .activity-arrow + display block + + .activity-arrow + display none + absolute top -1px right -1px + width 0 + height 0 + border-top 0.5em solid transparent + // border-right 0.25em solid #ccc + border-right 10px solid #ccc + border-bottom 0.5em solid transparent + .inner + absolute top -0.5em left 1px + width 0 + height 0 + padding 0 + border-top 0.5em solid transparent + // border-right 0.25em solid white + border-right 10px solid white + border-bottom 0.5em solid transparent + + .delete-metric-button + color $dark + &:hover + color $boot_red + opacity 1 + + + + + + .dataset-metrics-empty + display none + + + + /* * * * Edit Metric UI * * * */ + section.metric-edit-ui + position absolute + top 0 + left 40% + + .inner + padding 1em + + + + diff --git a/www/modules.yaml b/www/modules.yaml index 3670ee7..e97060b 100644 --- a/www/modules.yaml +++ b/www/modules.yaml @@ -62,10 +62,13 @@ dev: - template: - chart-option.jade - chart-scaffold.jade + - dataset.jade + - dataset-metric.jade - metric.jade + - metric-edit.jade - datasource.jade - - dataset-metric.jade - - dataset.jade + - datasource-ui.jade + - data.jade - graph-edit.jade - graph-display.jade - chart: @@ -77,10 +80,13 @@ dev: - dataset: - metric-model - metric-view + - metric-edit-view - datasource-model - datasource-view + - datasource-ui-view - dataset-model - dataset-view + - data-view - index - graph: - graph-model