<commit_before>const webpack = require("webpack");
const env = require("./env");
const HTMLPlugin = require("html-webpack-plugin");
const StylelintPlugin = require("stylelint-webpack-plugin");
const ForkTSCheckerPlugin = require("fork-ts-checker-webpack-plugin");
const TSImportPlugin = require("ts-import-plugin");

const config = {
    mode: "development",
    entry: ["webpack-dev-server/client?https://0.0.0.0:7443", "webpack/hot/dev-server", `${env.src}/index.tsx`],
    output: {
        filename: "static/js/[name].js",
        publicPath: "/",
    },
    resolve: {
        extensions: [".ts", ".tsx", ".js", ".jsx", ".less"],
        modules: [env.src, "node_modules"],
        alias: {
            conf: env.conf,
            lib: env.lib,
        },
    },
    devtool: "cheap-module-source-map",
    optimization: {
        splitChunks: {
            automaticNameDelimiter: "-",
        },
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/,
                include: env.src,
                loader: "ts-loader",
                options: {
                    configFile: env.tsConfig,
                    transpileOnly: true,
                    getCustomTransformers: () => ({
                        before: [TSImportPlugin({libraryName: "antd", libraryDirectory: "es", style: true})],
                    }),
                },
            },
            {
                test: /\.(css|less)$/,
                use: [
                    "style-loader",
                    "css-loader",
                    {
                        loader: "less-loader",
                        options: {
                            javascriptEnabled: true,
                        },
                    },
                ],
            },
            {
                test: /\.(png|jpe?g|gif)$/,
                loader: "url-loader",
                query: {
                    limit: 1024,
                    name: "static/img/[name].[hash:8].[ext]", // resources from different folder can have same file name, use hash to differentiate
                },
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                loader: "file-loader",
                options: {
                    name: "static/font/[name].[hash:8].[ext]", // resources from different folder can have same file name, use hash to differentiate
                },
            },
        ],
    },
    plugins: [
        new StylelintPlugin({
            configFile: env.stylelintConfig,
            context: env.src,
            files: "**/*.less",
            syntax: "less",
        }),
        new ForkTSCheckerPlugin({
            tsconfig: env.tsConfig,
            tslint: env.tslintConfig,
            workers: ForkTSCheckerPlugin.TWO_CPUS_FREE,
        }),
        new HTMLPlugin({
            template: `${env.src}/index.html`,
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.ProgressPlugin(),
    ],
};

module.exports = config;
<commit_msg>update framework/setup to latest<commit_after>const webpack = require("webpack");
const env = require("./env");
const HTMLPlugin = require("html-webpack-plugin");
const StylelintPlugin = require("stylelint-webpack-plugin");
const ForkTSCheckerPlugin = require("fork-ts-checker-webpack-plugin");
const TSImportPlugin = require("ts-import-plugin");

const config = {
    mode: "development",
    entry: ["webpack-dev-server/client?https://0.0.0.0:7443", "webpack/hot/dev-server", `${env.src}/index.tsx`],
    output: {
        filename: "static/js/[name].js",
        publicPath: "/",
    },
    resolve: {
        extensions: [".ts", ".tsx", ".js", ".jsx", ".less"],
        modules: [env.src, "node_modules"],
        alias: {
            conf: env.conf,
            lib: env.lib,
        },
    },
    devtool: "cheap-module-source-map",
    optimization: {
        splitChunks: {
            automaticNameDelimiter: "-",
            maxAsyncRequests: 10,
        },
    },
    module: {
        rules: [
            {
                test: /\.(ts|tsx)$/,
                include: env.src,
                loader: "ts-loader",
                options: {
                    configFile: env.tsConfig,
                    transpileOnly: true,
                    getCustomTransformers: () => ({
                        before: [TSImportPlugin({libraryName: "antd", libraryDirectory: "es", style: true})],
                    }),
                },
            },
            {
                test: /\.(css|less)$/,
                use: [
                    "style-loader",
                    "css-loader",
                    {
                        loader: "less-loader",
                        options: {
                            javascriptEnabled: true,
                        },
                    },
                ],
            },
            {
                test: /\.(png|jpe?g|gif)$/,
                loader: "url-loader",
                query: {
                    limit: 1024,
                    name: "static/img/[name].[hash:8].[ext]", // resources from different folder can have same file name, use hash to differentiate
                },
            },
            {
                test: /\.(woff|woff2|eot|ttf|otf)$/,
                loader: "file-loader",
                options: {
                    name: "static/font/[name].[hash:8].[ext]", // resources from different folder can have same file name, use hash to differentiate
                },
            },
        ],
    },
    plugins: [
        new StylelintPlugin({
            configFile: env.stylelintConfig,
            context: env.src,
            files: "**/*.less",
            syntax: "less",
        }),
        new ForkTSCheckerPlugin({
            tsconfig: env.tsConfig,
            tslint: env.tslintConfig,
            workers: ForkTSCheckerPlugin.TWO_CPUS_FREE,
        }),
        new HTMLPlugin({
            template: `${env.src}/index.html`,
        }),
        new webpack.HotModuleReplacementPlugin(),
        new webpack.ProgressPlugin(),
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/),
    ],
};

module.exports = config;
<|endoftext|>


<commit_before>/**
 * Summarized stats for a data series or set of series. When
 * constructed with a data series, computes the sum, min, max, and
 * mean.
 *
 * The input format is assumed to be the JSON representation returned
 * by graphite-web.
 */
ds.models.data.Summation = function(initial_data) {
  "use strict"

  var self = limivorous.observable()
                       .property('sum', { init: 0 })
                       .property('min', { init: Number.MAX_VALUE })
                       .property('max', { init: Number.MIN_VALUE })
                       .property('mean', { init: 0 })
                       .property('first', { init: 0 })
                       .property('last', { init: 0 })
                       .property('count', { init: 0 })
                       .build()
  Object.defineProperty(self, 'is_summation', {value: true})

  /**
   * Initialize the summation
   */
  var datapoints = []

  if (initial_data && (initial_data instanceof Array) && (initial_data.length)) {
    /* This assumes that all input series have the same number of data points */
    var length = initial_data[0].datapoints.length
    var summed_datapoints = []
    for (var i = 0; i < length; i++) {
      var x = 0
      for (var n in initial_data) {
        x += initial_data[n].datapoints[i][0]
      }
      datapoints.push([x, initial_data[0].datapoints[i][1]])
    }
  } else if (initial_data && initial_data.datapoints) {
    datapoints = initial_data.datapoints
  }

  if (datapoints && datapoints.length) {
    self.first = datapoints[0][0]
    self.count = datapoints.length
    if (self.first == null) {
      self.first = 0
    }
    datapoints.forEach(function(point) {
      var value = point[0] || 0
      self.last = value
      self.sum = self.sum + value
      if (value > self.max) {
        self.max = value
      }
      if (value < self.min) {
        self.min = value
      }
    })
    self.mean = self.sum / self.count
  } else if (typeof(initial_data) === 'object') {
    self.sum = initial_data.sum || self.sum
    self.min = initial_data.min || self.min
    self.max = initial_data.max || self.max
    self.first = initial_data.first || self.first
    self.last = initial_data.last || self.last
    self.mean = initial_data.mean || self.mean
    self.count = initial_data.count || self.count
  }

  /**
   * Subtract other from self
   */
  self.subtract = function(other) {
    return ds.models.data.Summation({
      sum:   self.sum   - other.sum,
      min:   self.min   - other.min,
      max:   self.max   - other.max,
      mean:  self.mean  - other.mean,
      first: self.first - other.first,
      last:  self.last  - other.last,
      count: self.count
    })
  }

  self.toJSON = function() {
    return {
      sum: self.sum,
      min: self.min,
      max: self.max,
      mean: self.mean,
      first: self.first,
      last: self.last,
      count: self.count
    }
  }

  return self
}
<commit_msg>Fixed the calculation of min in Summation<commit_after>/**
 * Summarized stats for a data series or set of series. When
 * constructed with a data series, computes the sum, min, max, and
 * mean.
 *
 * The input format is assumed to be the JSON representation returned
 * by graphite-web.
 */
ds.models.data.Summation = function(initial_data) {
  "use strict"

  var self = limivorous.observable()
                       .property('sum', { init: 0 })
                       .property('min', { init: Number.MAX_VALUE })
                       .property('max', { init: Number.MIN_VALUE })
                       .property('mean', { init: 0 })
                       .property('first', { init: 0 })
                       .property('last', { init: 0 })
                       .property('count', { init: 0 })
                       .build()
  Object.defineProperty(self, 'is_summation', {value: true})

  /**
   * Initialize the summation
   */
  var datapoints = []

  if (initial_data && (initial_data instanceof Array) && (initial_data.length)) {
    /* This assumes that all input series have the same number of data points */
    var length = initial_data[0].datapoints.length
    var summed_datapoints = []
    for (var i = 0; i < length; i++) {
      var x = 0
      for (var n in initial_data) {
        x += initial_data[n].datapoints[i][0]
      }
      datapoints.push([x, initial_data[0].datapoints[i][1]])
    }
  } else if (initial_data && initial_data.datapoints) {
    datapoints = initial_data.datapoints
  }

  if (datapoints && datapoints.length) {
    self.first = datapoints[0][0]
    self.count = datapoints.length
    if (self.first == null) {
      self.first = 0
    }
    datapoints.forEach(function(point) {
      var value = point[0] || 0
      self.last = value
      self.sum = self.sum + value
      if (value > self.max) {
        self.max = value
      }
      if (point[0] && (value < self.min)) {
        self.min = value
      }
    })
    self.mean = self.sum / self.count
  } else if (typeof(initial_data) === 'object') {
    self.sum = initial_data.sum || self.sum
    self.min = initial_data.min || self.min
    self.max = initial_data.max || self.max
    self.first = initial_data.first || self.first
    self.last = initial_data.last || self.last
    self.mean = initial_data.mean || self.mean
    self.count = initial_data.count || self.count
  }

  /**
   * Subtract other from self
   */
  self.subtract = function(other) {
    return ds.models.data.Summation({
      sum:   self.sum   - other.sum,
      min:   self.min   - other.min,
      max:   self.max   - other.max,
      mean:  self.mean  - other.mean,
      first: self.first - other.first,
      last:  self.last  - other.last,
      count: self.count
    })
  }

  self.toJSON = function() {
    return {
      sum: self.sum,
      min: self.min,
      max: self.max,
      mean: self.mean,
      first: self.first,
      last: self.last,
      count: self.count
    }
  }

  return self
}
<|endoftext|>


<commit_before>var OpenQuery = Backbone.View.extend({
    className: 'tab_container',

    events: {
        'click .sidebar a': 'view_query',
        'click .workspace_toolbar a.open': 'open_query',
        'click .workspace_toolbar a.delete': 'delete_query'
    },

    template: function() {
        return _.template($("#template-open-dialog").html())();
    },

    caption: function() {
        return "Repository";
    },

    render: function() {
        // Load template
        $(this.el).html(this.template());
        this.adjust();

        // Adjust tab when selected
        this.tab.bind('tab:select', this.adjust);
        $(window).resize(this.adjust);

        return this;
    },

    initialize: function(args) {
        // Fetch queries
        this.repository = new Repository({}, { dialog: this });
        this.repository.fetch();
    },

    populate: function(response) {
        this.queries = {};
        var $ul = $(this.el).find('.sidebar ul').html('');
        for (var i = 0; i < response.length; i++) {
            var query = response[i];
            this.queries[query.name] = query;
            var $link = $("").text(query.name)
                .attr({ href: "#" + query.name });
            $("
").append($link)
                .appendTo($ul);
        }
    },

    view_query: function(event) {
        var name = $(event.target).attr('href').replace('#', '');
        var query = this.queries[name];

        $(this.el).find('.workspace_toolbar').removeClass('hide');

        $results = $(this.el).find('.workspace_results')
            .html('
' + query.name + '
');
        var $properties = $('
').appendTo($results);

        // Iterate through properties and show a key=>value set in the information pane
        for (var property in query) {
            if (query.hasOwnProperty(property) && property != "name") {
                $properties.append($('
').html("" +
                        property + " : " + query[property]));
            }
        }

        this.selected_query = new SavedQuery({ name: name });

        return false;
    },

    open_query: function(event) {
        this.selected_query.fetch();

        return false;
    },

    delete_query: function(event) {
        this.selected_query.destroy();

        return false;
    },

    adjust: function() {
        // Adjust the height of the separator
        $separator = $(this.el).find('.sidebar_separator');
        $separator.height($("body").height() - 87);
        $(this.el).find('.sidebar').height($("body").height() - 87);

        // Adjust the dimensions of the results window
        $(this.el).find('.workspace_results').css({
            width: $(document).width() - $(this.el).find('.sidebar').width() - 30,
            height: $(document).height() - $("#header").height() -
                $(this.el).find('.workspace_toolbar').height() -
                $(this.el).find('.workspace_fields').height() - 40
        });
    },

    toggle_sidebar: function() {
        // Toggle sidebar
        $(this.el).find('.sidebar').toggleClass('hide');
        var new_margin = $(this.el).find('.sidebar').hasClass('hide') ?
                5 : 265;
        $(this.el).find('.workspace_inner').css({ 'margin-left': new_margin });
    }
});<commit_msg>Double click to open query<commit_after>var OpenQuery = Backbone.View.extend({
    className: 'tab_container',

    events: {
        'click .sidebar a': 'view_query',
        'dblclick .sidebar a': 'open_query',
        'click .workspace_toolbar a.open': 'open_query',
        'click .workspace_toolbar a.delete': 'delete_query'
    },

    template: function() {
        return _.template($("#template-open-dialog").html())();
    },

    caption: function() {
        return "Repository";
    },

    render: function() {
        // Load template
        $(this.el).html(this.template());
        this.adjust();

        // Adjust tab when selected
        this.tab.bind('tab:select', this.adjust);
        $(window).resize(this.adjust);

        return this;
    },

    initialize: function(args) {
        // Fetch queries
        this.repository = new Repository({}, { dialog: this });
        this.repository.fetch();
    },

    populate: function(response) {
        this.queries = {};
        var $ul = $(this.el).find('.sidebar ul').html('');
        for (var i = 0; i < response.length; i++) {
            var query = response[i];
            this.queries[query.name] = query;
            var $link = $("").text(query.name)
                .attr({ href: "#" + query.name });
            $("
").append($link)
                .appendTo($ul);
        }
    },

    view_query: function(event) {
        var name = $(event.target).attr('href').replace('#', '');
        var query = this.queries[name];

        $(this.el).find('.workspace_toolbar').removeClass('hide');

        $results = $(this.el).find('.workspace_results')
            .html('
' + query.name + '
');
        var $properties = $('
').appendTo($results);

        // Iterate through properties and show a key=>value set in the information pane
        for (var property in query) {
            if (query.hasOwnProperty(property) && property != "name") {
                $properties.append($('
').html("" +
                        property + " : " + query[property]));
            }
        }

        this.selected_query = new SavedQuery({ name: name });

        return false;
    },

    open_query: function(event) {
        this.selected_query.fetch();

        return false;
    },

    delete_query: function(event) {
        this.selected_query.destroy();

        return false;
    },

    adjust: function() {
        // Adjust the height of the separator
        $separator = $(this.el).find('.sidebar_separator');
        $separator.height($("body").height() - 87);
        $(this.el).find('.sidebar').height($("body").height() - 87);

        // Adjust the dimensions of the results window
        $(this.el).find('.workspace_results').css({
            width: $(document).width() - $(this.el).find('.sidebar').width() - 30,
            height: $(document).height() - $("#header").height() -
                $(this.el).find('.workspace_toolbar').height() -
                $(this.el).find('.workspace_fields').height() - 40
        });
    },

    toggle_sidebar: function() {
        // Toggle sidebar
        $(this.el).find('.sidebar').toggleClass('hide');
        var new_margin = $(this.el).find('.sidebar').hasClass('hide') ?
                5 : 265;
        $(this.el).find('.workspace_inner').css({ 'margin-left': new_margin });
    }
});<|endoftext|>


<commit_before>/*global psc jQuery window Timeline */
psc.namespace("subject");

/*
 * Manages the Simile Timeline instance for the subject single schedule page.
 *
 * Known flaws:
 *   - Sync scrolling to the left in a sparse timeline doesn't update the list
 *     until a newly-visible item passes the centerline.
 *   - Timeline doesn't adapt well to schedules with lots of parallel segments
 *     (~4 or more) or events on the same day (~5).
 *   - The first time the resize event is fired, the event markers spread
 *     apart for some reason.  (Theory: they are adapting to fit non-existent
 *     text labels.  Possible solution: a modified painter which excludes
 *     text.)
 *   - The descriptions for segments and activities are pretty lacking.
 *   - The hover decorator doesn't show up when you get some distance away
 *     from "today".
 *   - Sync scrolling in the vicinity of "today" on a sparse timeline is
 *     jumpy.
 */

(function ($) {
  psc.subject.ScheduleTimeline = (function () {
    var BAND_OVERVIEW = 0;
    var BAND_SEGMENTS = 1;
    var BAND_ACTIVITIES = 2;

    var timeline;
    var saEventSource, segmentEventSource;
    var theme, segmentTheme;
    var bandInfo;
    var resizeTimer = null;

    function initializeTimelineParameters() {
      initializeEventSources();
      initializeThemes();
      initializeBandInfo();
    }

    function initializeEventSources() {
       saEventSource = new Timeline.DefaultEventSource();
       segmentEventSource = new Timeline.DefaultEventSource();
    }

    function initializeThemes() {
      theme = new Timeline.ClassicTheme.create();
      theme.firstDayOfWeek = 1;
      theme.event.track.height = 10;
      theme.event.track.gap = 1;
      theme.event.track.offset = 0;
      theme.event.instant.iconHeight = 6;
      theme.event.instant.iconWidth = 10;

      segmentTheme = new Timeline.ClassicTheme.create();
      segmentTheme.firstDayOfWeek = 1;
      segmentTheme.event.track.height = 14;
      segmentTheme.event.tape.height = 4;
    }

    function initializeBandInfo() {
      bandInfo = [
        Timeline.createBandInfo({
          eventSource: saEventSource,
          theme: theme,
          width: "15%",
          intervalUnit: Timeline.DateTime.YEAR,
          intervalPixels: 300,
          overview: true
        }),
        Timeline.createBandInfo({
          eventSource: segmentEventSource,
          theme: segmentTheme,
          width: "35%",
          intervalUnit: Timeline.DateTime.WEEK,
          intervalPixels: 150
        }),
        Timeline.createBandInfo({
          eventSource: saEventSource,
          theme: theme,
          width: "50%",
          intervalUnit: Timeline.DateTime.WEEK,
          intervalPixels: 150,
          eventPainter: psc.subject.DivIconOnlyPainter
        })
      ];
      $.each(bandInfo, function (i, bi) {
        bi.labeller = new psc.subject.DayOnlyLabeller(Timeline.getDefaultLocale(), 0);
        if (i != BAND_ACTIVITIES) {
          bandInfo[i].syncWith = BAND_ACTIVITIES;
          bandInfo[i].highlight = true;
        }
      });

      ////// today highlight

      var todayStart = psc.tools.Dates.startOfUtcDay(new Date());

      for (var i = 0 ; i < bandInfo.length ; i += 1) {
        var params = {
          cssClass: 'today',
          startDate: todayStart,
          endDate: new Date(todayStart.getTime() + psc.tools.Dates.ONE_DAY)
        };
        if (BAND_ACTIVITIES === i) {
          params.startLabel = "Today";
        } else if (BAND_OVERVIEW === i) {
          // With the actual width, it doesn't show up on IE or Safari
          params.startDate = new Date(params.startDate.getTime() - psc.tools.Dates.ONE_DAY / 2);
          params.endDate = new Date(params.endDate.getTime() + psc.tools.Dates.ONE_DAY / 2);
        }
        bandInfo[i].decorators = [ new Timeline.SpanHighlightDecorator(params) ];
      }

      ////// hover highlight (doesn't work well)

      $.each([BAND_SEGMENTS, BAND_ACTIVITIES], function (i, band) {
        bandInfo[band].decorators.push(new Timeline.SpanHighlightDecorator({
          cssClass: 'hover-date',
          startDate: todayStart,
          endDate: new Date(todayStart.getTime() + psc.tools.Dates.ONE_DAY)
        }));
      });
    }

    function scheduleLoadStart() {
      timeline.showLoadingMessage();
    }

    function scheduleReady() {
      var schedule = psc.subject.ScheduleData.current();

      if (null !== schedule) {
        var saEvents = [];
        $.each(schedule['days'], function (date, dayObject) {
          $.each(dayObject['activities'], function (idx, sa) {
            saEvents.push(psc.subject.ScheduleTimeline.eventForScheduledActivity(sa));
          });
        });

        saEventSource.clear()
        saEventSource.loadJSON({
          "events": saEvents
        }, '.');

        var segEvents = [];
        $.each(schedule.study_segments, function (idx, segment) {
          segEvents.push(psc.subject.ScheduleTimeline.eventForScheduledStudySegment(segment));
        });

        segmentEventSource.clear()
        segmentEventSource.loadJSON({
          "events": segEvents
        }, '.');
      }

      timeline.hideLoadingMessage();
    }

    function scheduleError() {
      timeline.hideLoadingMessage();
    }

    return {
      bandInfo: function () {
        return bandInfo;
      },

      timeline: function () {
        return timeline;
      },

      eventForScheduledActivity: function (sa) {
        return {
          durationEvent: false,
          start: new Date(sa.currentDate().getTime() + psc.tools.Dates.ONE_DAY / 2),
          title: sa.activity && sa.activity.name,
          description: sa.study + " / " + sa.study_segment,
          classname: sa.isOpen() ? "outstanding open" : "complete closed",
          image: psc.tools.Uris.relative("/images/" + sa.current_state.name + ".png")
        };
      },

      eventForScheduledStudySegment: function (segment) {
        return {
          durationEvent: true,
          start: segment.startDate(),
          end: segment.stopDate(),
          title: segment.assignmentName() + " / " + segment.name,
          description: ""
        };
      },

      create: function () {
        initializeTimelineParameters();
        timeline = Timeline.create(document.getElementById("schedule-timeline"), this.bandInfo());
        timeline.getBand(BAND_ACTIVITIES).setCenterVisibleDate(
          new Date(psc.tools.Dates.middleOfUtcDay(new Date())));

        $('#schedule').bind('schedule-load-start', scheduleLoadStart);
        $('#schedule').bind('schedule-ready', scheduleReady);
        $('#schedule').bind('schedule-error', scheduleError);
      },

      resize: function () {
        if (resizeTimer === null && timeline !== null) {
          resizeTimer = window.setTimeout(function () {
            resizeTimer = null;
            var center = timeline.getBand(BAND_ACTIVITIES).getCenterVisibleDate();
            timeline.layout();
            timeline.getBand(BAND_ACTIVITIES).setCenterVisibleDate(center);
          }, 100);
        }
      },

      FocusHandler: (function () {
        var SOURCE_NAME = "timeline";
        var lastHoverPixel = null;
        var hoverUpdating = false,
            positionUpdatingByMouse = false,
            positionUpdatingByKeyboard = false,
            programaticallyScrolling = false;

        ///// HOVERING

        var hoverAnimator = new psc.tools.AsyncUpdater(function (offsetPx) {
          if (offsetPx) {
            $('#schedule-timeline .hover-date').show().css('left', offsetPx + "px");
          } else {
            $('#schedule-timeline .hover-date').hide();
          }
        });

        function hoverDateByLocation(bandOffset, hoverPixel) {
          var px = hoverPixel || lastHoverPixel;
          if (px) {
            hoverUpdating = true;
            var hoverDay = psc.tools.Dates.startOfUtcDay(timeline.getBand(BAND_ACTIVITIES).getEther().pixelOffsetToDate(px));
            psc.subject.ScheduleData.hoverDate(new Date(hoverDay.getTime() + psc.tools.Dates.ONE_DAY / 2), SOURCE_NAME);
            hoverAnimator.update(timeline.getBand(BAND_ACTIVITIES).getEther().dateToPixelOffset(hoverDay) - bandOffset);
            lastHoverPixel = px;
            hoverUpdating = false;
          }
        }

        function mouseHoverMove(evt) {
          hoverDateByLocation(parseInt(this.style.left),
            evt.clientX - $('#schedule-timeline').offset().left);
        }

        function mouseHoverStop(evt) {
          hoverUpdating = true;
          stopHovering();
          psc.subject.ScheduleData.hoverDate(null, SOURCE_NAME);
          hoverUpdating = false;
        }

        function stopHovering() {
          lastHoverPixel = null;
          hoverAnimator.update(null);
        }

        function hoverBand(band) {
          // ensure that the hover continues to lie under the mouse pointer,
          // even if the band is scrolled using the wheel or keyboard
          hoverDateByLocation(band.getViewOffset())
        }

        function hoverEvent(evt, triggerData) {
          if (!hoverUpdating) {
            if (triggerData.date) {
              hoverDateByLocation(
                timeline.getBand(BAND_ACTIVITIES).getViewOffset(),
                timeline.getBand(BAND_ACTIVITIES).getEther().dateToPixelOffset(psc.tools.Dates.startOfUtcDay(triggerData.date))
              );
            } else {
              stopHovering()
            }
          }
        }

        ////// SCROLLING

        var focusAnimator = new psc.tools.AsyncUpdater(function (focusDate) {
          var band = timeline.getBand(BAND_ACTIVITIES);
          if (focusDate < band.getMinVisibleDate() || focusDate > band.getMaxVisibleDate()) {
            programaticallyScrolling = true;
            band.setCenterVisibleDate(focusDate);
            programaticallyScrolling = false;
          }
        }, function (v) {
          return v === null ? null : v.getTime();
        });

        function internalPositionUpdating() {
          return positionUpdatingByKeyboard || positionUpdatingByMouse;
        }

        function startScrollingByKeyboard() {
          positionUpdatingByKeyboard = true;
        }

        function stopScrollingByKeyboard() {
          positionUpdatingByKeyboard = false;
        }

        function startScrollingByMouse() {
          positionUpdatingByMouse = true;
        }

        function stopScrollingByMouse() {
          positionUpdatingByMouse = false;
        }

        function recenter(evt, triggerData) {
          if (triggerData.source !== SOURCE_NAME) {
            focusAnimator.update(triggerData.date);
          }
        }

        function focusChangedByScroll() {
          if (internalPositionUpdating() && !programaticallyScrolling) {
            var band = timeline.getBand(BAND_ACTIVITIES);
            psc.subject.ScheduleData.focusDate(
              band.getCenterVisibleDate(),
              SOURCE_NAME,
              new psc.tools.Range(band.getMinVisibleDate(), band.getMaxVisibleDate())
            );
          }
        }

        return {
          init: function () {
            var hoverBandsSelector = $.map([BAND_SEGMENTS, BAND_ACTIVITIES], function (i) {
              return "#timeline-band-" + i
            }).join(", ");
            $(hoverBandsSelector).mousemove(mouseHoverMove).mouseout(mouseHoverStop);
            timeline.getBand(BAND_ACTIVITIES).addOnScrollListener(hoverBand);

            $('#schedule-timeline').
              mouseover(startScrollingByMouse).
              mouseout(stopScrollingByMouse);
            $('.timeline-band-input input').
              focus(startScrollingByKeyboard).
              blur(stopScrollingByKeyboard);
            timeline.getBand(BAND_ACTIVITIES).addOnScrollListener(focusChangedByScroll);

            $('#schedule').bind('hover-date-changed', hoverEvent);
            $('#schedule').bind('focus-date-changed', recenter);
          }
        }
      }())
    };
  }());
}(jQuery));<commit_msg>Bug #902: made slight modifications to enhance timeline's performance.<commit_after>/*global psc jQuery window Timeline */
psc.namespace("subject");

/*
 * Manages the Simile Timeline instance for the subject single schedule page.
 *
 * Known flaws:
 *   - Sync scrolling to the left in a sparse timeline doesn't update the list
 *     until a newly-visible item passes the centerline.
 *   - Timeline doesn't adapt well to schedules with lots of parallel segments
 *     (~4 or more) or events on the same day (~5).
 *   - The first time the resize event is fired, the event markers spread
 *     apart for some reason.  (Theory: they are adapting to fit non-existent
 *     text labels.  Possible solution: a modified painter which excludes
 *     text.)
 *   - The descriptions for segments and activities are pretty lacking.
 *   - The hover decorator doesn't show up when you get some distance away
 *     from "today".
 *   - Sync scrolling in the vicinity of "today" on a sparse timeline is
 *     jumpy.
 */

(function ($) {
  psc.subject.ScheduleTimeline = (function () {
    var BAND_OVERVIEW = 0;
    var BAND_SEGMENTS = 1;
    var BAND_ACTIVITIES = 2;

    var timeline;
    var saEventSource, segmentEventSource;
    var theme, segmentTheme;
    var bandInfo;
    var resizeTimer = null;

    function initializeTimelineParameters() {
      initializeEventSources();
      initializeThemes();
      initializeBandInfo();
    }

    function initializeEventSources() {
       saEventSource = new Timeline.DefaultEventSource();
       segmentEventSource = new Timeline.DefaultEventSource();
    }

    function initializeThemes() {
      theme = new Timeline.ClassicTheme.create();
      theme.firstDayOfWeek = 1;
      theme.event.track.height = 10;
      theme.event.track.gap = 1;
      theme.event.track.offset = 0;
      theme.event.instant.iconHeight = 6;
      theme.event.instant.iconWidth = 10;

      segmentTheme = new Timeline.ClassicTheme.create();
      segmentTheme.firstDayOfWeek = 1;
      segmentTheme.event.track.height = 14;
      segmentTheme.event.tape.height = 4;
    }

    function initializeBandInfo() {
      bandInfo = [
        Timeline.createBandInfo({
          eventSource: saEventSource,
          theme: theme,
          width: "15%",
          intervalUnit: Timeline.DateTime.YEAR,
          intervalPixels: 300,
          overview: true
        }),
        Timeline.createBandInfo({
          eventSource: segmentEventSource,
          theme: segmentTheme,
          width: "35%",
          intervalUnit: Timeline.DateTime.WEEK,
          intervalPixels: 150
        }),
        Timeline.createBandInfo({
          eventSource: saEventSource,
          theme: theme,
          width: "50%",
          intervalUnit: Timeline.DateTime.WEEK,
          intervalPixels: 150,
          eventPainter: psc.subject.DivIconOnlyPainter
        })
      ];
      $.each(bandInfo, function (i, bi) {
        bi.labeller = new psc.subject.DayOnlyLabeller(Timeline.getDefaultLocale(), 0);
        if (i != BAND_ACTIVITIES) {
          bandInfo[i].syncWith = BAND_ACTIVITIES;
          bandInfo[i].highlight = true;
        }
      });

      ////// today highlight

      var todayStart = psc.tools.Dates.startOfUtcDay(new Date());

      for (var i = 0 ; i < bandInfo.length ; i += 1) {
        var params = {
          cssClass: 'today',
          startDate: todayStart,
          endDate: new Date(todayStart.getTime() + psc.tools.Dates.ONE_DAY)
        };
        if (BAND_ACTIVITIES === i) {
          params.startLabel = "Today";
        } else if (BAND_OVERVIEW === i) {
          // With the actual width, it doesn't show up on IE or Safari
          params.startDate = new Date(params.startDate.getTime() - psc.tools.Dates.ONE_DAY / 2);
          params.endDate = new Date(params.endDate.getTime() + psc.tools.Dates.ONE_DAY / 2);
        }
        bandInfo[i].decorators = [ new Timeline.SpanHighlightDecorator(params) ];
      }

      ////// hover highlight (doesn't work well)

      $.each([BAND_SEGMENTS, BAND_ACTIVITIES], function (i, band) {
        bandInfo[band].decorators.push(new Timeline.SpanHighlightDecorator({
          cssClass: 'hover-date',
          startDate: todayStart,
          endDate: new Date(todayStart.getTime() + psc.tools.Dates.ONE_DAY)
        }));
      });
    }

    function scheduleLoadStart() {
      timeline.showLoadingMessage();
    }

    function scheduleReady() {
      var schedule = psc.subject.ScheduleData.current();

      if (null !== schedule) {
        var saEvents = [];
        $.each(schedule['days'], function (date, dayObject) {
          $.each(dayObject['activities'], function (idx, sa) {
            saEvents.push(psc.subject.ScheduleTimeline.eventForScheduledActivity(sa));
          });
        });

        saEventSource.clear()
        saEventSource.loadJSON({
          "events": saEvents
        }, '.');

        var segEvents = [];
        $.each(schedule.study_segments, function (idx, segment) {
          segEvents.push(psc.subject.ScheduleTimeline.eventForScheduledStudySegment(segment));
        });

        segmentEventSource.clear()
        segmentEventSource.loadJSON({
          "events": segEvents
        }, '.');
      }

      timeline.hideLoadingMessage();
    }

    function scheduleError() {
      timeline.hideLoadingMessage();
    }

    return {
      bandInfo: function () {
        return bandInfo;
      },

      timeline: function () {
        return timeline;
      },

      eventForScheduledActivity: function (sa) {
        return {
          durationEvent: false,
          start: new Date(sa.currentDate().getTime() + psc.tools.Dates.ONE_DAY / 2),
          title: sa.activity && sa.activity.name,
          description: sa.study + " / " + sa.study_segment,
          classname: sa.isOpen() ? "outstanding open" : "complete closed",
          image: psc.tools.Uris.relative("/images/" + sa.current_state.name + ".png")
        };
      },

      eventForScheduledStudySegment: function (segment) {
        return {
          durationEvent: true,
          start: segment.startDate(),
          end: segment.stopDate(),
          title: segment.assignmentName() + " / " + segment.name,
          description: ""
        };
      },

      create: function () {
        initializeTimelineParameters();
        timeline = Timeline.create(document.getElementById("schedule-timeline"), this.bandInfo());
        timeline.getBand(BAND_ACTIVITIES).setCenterVisibleDate(
          new Date(psc.tools.Dates.middleOfUtcDay(new Date())));

        $('#schedule').bind('schedule-load-start', scheduleLoadStart);
        $('#schedule').bind('schedule-ready', scheduleReady);
        $('#schedule').bind('schedule-error', scheduleError);
      },

      resize: function () {
        if (resizeTimer === null && timeline !== null) {
          resizeTimer = window.setTimeout(function () {
            resizeTimer = null;
            var center = timeline.getBand(BAND_ACTIVITIES).getCenterVisibleDate();
            timeline.layout();
            timeline.getBand(BAND_ACTIVITIES).setCenterVisibleDate(center);
          }, 100);
        }
      },

      FocusHandler: (function () {
        var SOURCE_NAME = "timeline";
        var lastHoverPixel = null;
        var hoverUpdating = false,
            positionUpdatingByMouse = false,
            positionUpdatingByKeyboard = false,
            programaticallyScrolling = false;

        ///// HOVERING

        var hoverAnimator = new psc.tools.AsyncUpdater(function (offsetPx) {
          if (offsetPx) {
            $('#schedule-timeline div.hover-date').show().css('left', offsetPx + "px");
          } else {
            $('#schedule-timeline div.hover-date').hide();
          }
        });

        function hoverDateByLocation(bandOffset, hoverPixel) {
          var px = hoverPixel || lastHoverPixel;
          if (px) {
            hoverUpdating = true;
            var hoverDay = psc.tools.Dates.startOfUtcDay(timeline.getBand(BAND_ACTIVITIES).getEther().pixelOffsetToDate(px));
            psc.subject.ScheduleData.hoverDate(new Date(hoverDay.getTime() + psc.tools.Dates.ONE_DAY / 2), SOURCE_NAME);
            hoverAnimator.update(timeline.getBand(BAND_ACTIVITIES).getEther().dateToPixelOffset(hoverDay) - bandOffset);
            lastHoverPixel = px;
            hoverUpdating = false;
          }
        }

        function mouseHoverMove(evt) {
          hoverDateByLocation(parseInt(this.style.left),
            evt.clientX - $('#schedule-timeline').offset().left);
        }

        function mouseHoverStop(evt) {
          hoverUpdating = true;
          stopHovering();
          psc.subject.ScheduleData.hoverDate(null, SOURCE_NAME);
          hoverUpdating = false;
        }

        function stopHovering() {
          lastHoverPixel = null;
          hoverAnimator.update(null);
        }

        function hoverBand(band) {
          // ensure that the hover continues to lie under the mouse pointer,
          // even if the band is scrolled using the wheel or keyboard
          hoverDateByLocation(band.getViewOffset())
        }

        function hoverEvent(evt, triggerData) {
          if (!hoverUpdating) {
            if (triggerData.date) {
              hoverDateByLocation(
                timeline.getBand(BAND_ACTIVITIES).getViewOffset(),
                timeline.getBand(BAND_ACTIVITIES).getEther().dateToPixelOffset(psc.tools.Dates.startOfUtcDay(triggerData.date))
              );
            } else {
              stopHovering()
            }
          }
        }

        ////// SCROLLING

        var focusAnimator = new psc.tools.AsyncUpdater(function (focusDate) {
          var band = timeline.getBand(BAND_ACTIVITIES);
          if (focusDate < band.getMinVisibleDate() || focusDate > band.getMaxVisibleDate()) {
            programaticallyScrolling = true;
            band.setCenterVisibleDate(focusDate);
            programaticallyScrolling = false;
          }
        }, function (v) {
          return v === null ? null : v.getTime();
        });

        function internalPositionUpdating() {
          return positionUpdatingByKeyboard || positionUpdatingByMouse;
        }

        function startScrollingByKeyboard() {
          positionUpdatingByKeyboard = true;
        }

        function stopScrollingByKeyboard() {
          positionUpdatingByKeyboard = false;
        }

        function startScrollingByMouse() {
          positionUpdatingByMouse = true;
        }

        function stopScrollingByMouse() {
          positionUpdatingByMouse = false;
        }

        function recenter(evt, triggerData) {
          if (triggerData.source !== SOURCE_NAME) {
            focusAnimator.update(triggerData.date);
          }
        }

        function focusChangedByScroll() {
          if (internalPositionUpdating() && !programaticallyScrolling) {
            var band = timeline.getBand(BAND_ACTIVITIES);
            psc.subject.ScheduleData.focusDate(
              band.getCenterVisibleDate(),
              SOURCE_NAME,
              new psc.tools.Range(band.getMinVisibleDate(), band.getMaxVisibleDate())
            );
          }
        }

        return {
          init: function () {
            var hoverBandsSelector = $.map([BAND_SEGMENTS, BAND_ACTIVITIES], function (i) {
              return "#timeline-band-" + i
            }).join(", ");
            $(hoverBandsSelector).mousemove(mouseHoverMove).mouseout(mouseHoverStop);
            timeline.getBand(BAND_ACTIVITIES).addOnScrollListener(hoverBand);

            $('#schedule-timeline').
              mouseover(startScrollingByMouse).
              mouseout(stopScrollingByMouse);
            $('div.timeline-band-input input').
              focus(startScrollingByKeyboard).
              blur(stopScrollingByKeyboard);
            timeline.getBand(BAND_ACTIVITIES).addOnScrollListener(focusChangedByScroll);

            $('#schedule').bind('hover-date-changed', hoverEvent);
            $('#schedule').bind('focus-date-changed', recenter);
          }
        }
      }())
    };
  }());
}(jQuery));<|endoftext|>


<commit_before>var Workspace = Backbone.View.extend({
    className: 'tab_container',

    events: {
        'click .sidebar_separator': 'toggle_sidebar',
        'change .cubes': 'new_query',
        'drop': 'remove_dimension'
    },

    initialize: function(args) {
        // Maintain `this` in jQuery event handlers
        _.bindAll(this, "adjust", "toggle_sidebar",
                "prepare", "new_query", "init_query");

        // Attach an event bus to the workspace
        _.extend(this, Backbone.Events);

        // Generate toolbar and append to workspace
        this.toolbar = new WorkspaceToolbar({ workspace: this });
        this.toolbar.render();

        // Create drop zones
        this.drop_zones = new WorkspaceDropZone({ workspace: this });
        this.drop_zones.render();

        // Generate table
        this.table = new Table({ workspace: this });

        // Pull query from args
        if (args && args.query) {
            this.query = args.query;
            this.query.workspace = this;
            this.query.save({}, { success: this.init_query });
        }

        // Flash cube navigation when rendered
        Saiku.session.bind('tab:add', this.prepare);
    },

    caption: function() {
        if (this.query && this.query.name) {
            return this.query.name;
        }

        return "Unsaved query (" + Saiku.tabs.queryCount + ")";
    },

    template: function() {
        return _.template($("#template-workspace").html())({
            cube_navigation: Saiku.session.cube_navigation
        });
    },

    render: function() {
        // Load template
        $(this.el).html(this.template());

        // Show toolbar
        $(this.el).find('.workspace_toolbar').append($(this.toolbar.el));

        // Show drop zones
        $(this.drop_zones.el)
            .insertAfter($(this.el).find('.workspace_toolbar'));

        // Activate sidebar for removing elements
        $(this.el).find('.sidebar')
            .droppable({
                accept: '.d_measure, .d_dimension'
            });

        // Add results table
        $(this.el).find('.workspace_results')
            .append($(this.table.el));

        // Adjust tab when selected
        this.tab.bind('tab:select', this.adjust);
        $(window).resize(this.adjust);

        // Fire off new workspace event
        Saiku.session.trigger('workspace:new', { workspace: this });
        return this;
    },

    clear: function() {
        // Prepare the workspace for a new query
        $(this.el).find('.workspace_results table,.connectable')
            .html('');

        // Trigger clear event
        this.trigger('workspace:clear');
    },

    adjust: function() {
        // Adjust the height of the separator
        $separator = $(this.el).find('.sidebar_separator');
        $separator.height($("body").height() - 87);
        $(this.el).find('.sidebar').height($("body").height() - 87);

        // Adjust the dimensions of the results window
        $(this.el).find('.workspace_results').css({
            width: $(document).width() - $(this.el).find('.sidebar').width() - 30,
            height: $(document).height() - $("#header").height() -
                $(this.el).find('.workspace_toolbar').height() -
                $(this.el).find('.workspace_fields').height() - 40
        });

        // Fire off the adjust event
        this.trigger('workspace:adjust', { workspace: this });
    },

    toggle_sidebar: function() {
        // Toggle sidebar
        $(this.el).find('.sidebar').toggleClass('hide');
        var new_margin = $(this.el).find('.sidebar').hasClass('hide') ?
                5 : 265;
        $(this.el).find('.workspace_inner').css({ 'margin-left': new_margin });
    },

    prepare: function() {
        // Draw user's attention to cube navigation
        $(this.el).find('.cubes')
            .parent()
            .animate({ backgroundColor: '#AC1614' }, 'fast')
            .animate({ backgroundColor: '#fff' }, 'fast');
    },

    new_query: function() {
        // Delete the existing query
        if (this.query) {
            this.query.destroy();
        }

        // Initialize the new query
        var selected_cube = $(this.el).find('.cubes').val();
        var parsed_cube = selected_cube.split('/');
        this.query = new Query({
            connection: parsed_cube[0],
            catalog: parsed_cube[1],
            schema: parsed_cube[2],
            cube: parsed_cube[3]
        }, {
            workspace: this
        });

        // Save the query to the server and init the UI
        this.query.save();
        this.init_query();
    },

    init_query: function() {
        // Find the selected cube
        var selected_cube = $(this.el).find('.cubes').val();
        if (selected_cube === "") {
            selected_cube = this.query.get('connection') + "/" +
                this.query.get('catalog') + "/" +
                this.query.get('schema') + "/" + this.query.get('cube');
            $(this.el).find('.cubes')
                .val(selected_cube);
        }

        // Create new DimensionList and MeasureList
        this.dimension_list = new DimensionList({
            workspace: this,
            dimension: Saiku.session.dimensions[selected_cube]
        });
        $(this.el).find('.dimension_tree').html('').append($(this.dimension_list.el));

        this.measure_list = new DimensionList({
            workspace: this,
            dimension: Saiku.session.measures[selected_cube]
        });
        $(this.el).find('.measure_tree').html('').append($(this.measure_list.el));

        // Clear workspace
        this.clear();

        // Populate selections - trust me, this is prettier than it was :-/
        var axes = this.query ? this.query.get('axes') : false;
        if (axes) {
            for (var axis_iter = 0; axis_iter < axes.length; axis_iter++) {
                var axis = axes[axis_iter];
                var $axis = $(this.el).find('.' +
                    axis.name.toLowerCase() + ' ul');
                for (var dim_iter = 0; dim_iter < axis.dimensionSelections.length; dim_iter++) {
                    var dimension = axis.dimensionSelections[dim_iter];
                    var levels = [];
                    for (var sel_iter = 0; sel_iter < dimension.selections.length; sel_iter++) {
                        var selection = dimension.selections[sel_iter];
                        if (selection.dimensionUniqueName != "Measures") {
                            if (levels.indexOf(selection.levelUniqueName) === -1) {
                                console.log(selection.levelUniqueName);
                                var $dim = $(this.el).find('.dimension_tree')
                                    .find('a[title="' + selection.levelUniqueName + '"]')
                                    .parent();
                                $dim.clone()
                                    .addClass('d_dimension')
                                    .appendTo($axis);
                                $dim.css({fontWeight: "bold"})
                                    .draggable('disable')
                                    .parents('.parent_dimension')
                                    .find('.root')
                                    .css({fontWeight: "bold"})
                                    .draggable('disable');
                                levels.push(selection.levelUniqueName);
                            }
                        }
                    }
                }
            }

            this.query.run();
        }

        // Make sure appropriate workspace buttons are enabled
        this.trigger('query:new', { workspace: this });
    },

    remove_dimension: function(event, ui) {
        this.drop_zones.remove_dimension(event, ui);
    }
});<commit_msg>Drag measures as well<commit_after>var Workspace = Backbone.View.extend({
    className: 'tab_container',

    events: {
        'click .sidebar_separator': 'toggle_sidebar',
        'change .cubes': 'new_query',
        'drop': 'remove_dimension'
    },

    initialize: function(args) {
        // Maintain `this` in jQuery event handlers
        _.bindAll(this, "adjust", "toggle_sidebar",
                "prepare", "new_query", "init_query");

        // Attach an event bus to the workspace
        _.extend(this, Backbone.Events);

        // Generate toolbar and append to workspace
        this.toolbar = new WorkspaceToolbar({ workspace: this });
        this.toolbar.render();

        // Create drop zones
        this.drop_zones = new WorkspaceDropZone({ workspace: this });
        this.drop_zones.render();

        // Generate table
        this.table = new Table({ workspace: this });

        // Pull query from args
        if (args && args.query) {
            this.query = args.query;
            this.query.workspace = this;
            this.query.save({}, { success: this.init_query });
        }

        // Flash cube navigation when rendered
        Saiku.session.bind('tab:add', this.prepare);
    },

    caption: function() {
        if (this.query && this.query.name) {
            return this.query.name;
        }

        return "Unsaved query (" + Saiku.tabs.queryCount + ")";
    },

    template: function() {
        return _.template($("#template-workspace").html())({
            cube_navigation: Saiku.session.cube_navigation
        });
    },

    render: function() {
        // Load template
        $(this.el).html(this.template());

        // Show toolbar
        $(this.el).find('.workspace_toolbar').append($(this.toolbar.el));

        // Show drop zones
        $(this.drop_zones.el)
            .insertAfter($(this.el).find('.workspace_toolbar'));

        // Activate sidebar for removing elements
        $(this.el).find('.sidebar')
            .droppable({
                accept: '.d_measure, .d_dimension'
            });

        // Add results table
        $(this.el).find('.workspace_results')
            .append($(this.table.el));

        // Adjust tab when selected
        this.tab.bind('tab:select', this.adjust);
        $(window).resize(this.adjust);

        // Fire off new workspace event
        Saiku.session.trigger('workspace:new', { workspace: this });
        return this;
    },

    clear: function() {
        // Prepare the workspace for a new query
        $(this.el).find('.workspace_results table,.connectable')
            .html('');

        // Trigger clear event
        this.trigger('workspace:clear');
    },

    adjust: function() {
        // Adjust the height of the separator
        $separator = $(this.el).find('.sidebar_separator');
        $separator.height($("body").height() - 87);
        $(this.el).find('.sidebar').height($("body").height() - 87);

        // Adjust the dimensions of the results window
        $(this.el).find('.workspace_results').css({
            width: $(document).width() - $(this.el).find('.sidebar').width() - 30,
            height: $(document).height() - $("#header").height() -
                $(this.el).find('.workspace_toolbar').height() -
                $(this.el).find('.workspace_fields').height() - 40
        });

        // Fire off the adjust event
        this.trigger('workspace:adjust', { workspace: this });
    },

    toggle_sidebar: function() {
        // Toggle sidebar
        $(this.el).find('.sidebar').toggleClass('hide');
        var new_margin = $(this.el).find('.sidebar').hasClass('hide') ?
                5 : 265;
        $(this.el).find('.workspace_inner').css({ 'margin-left': new_margin });
    },

    prepare: function() {
        // Draw user's attention to cube navigation
        $(this.el).find('.cubes')
            .parent()
            .animate({ backgroundColor: '#AC1614' }, 'fast')
            .animate({ backgroundColor: '#fff' }, 'fast');
    },

    new_query: function() {
        // Delete the existing query
        if (this.query) {
            this.query.destroy();
        }

        // Initialize the new query
        var selected_cube = $(this.el).find('.cubes').val();
        var parsed_cube = selected_cube.split('/');
        this.query = new Query({
            connection: parsed_cube[0],
            catalog: parsed_cube[1],
            schema: parsed_cube[2],
            cube: parsed_cube[3]
        }, {
            workspace: this
        });

        // Save the query to the server and init the UI
        this.query.save();
        this.init_query();
    },

    init_query: function() {
        // Find the selected cube
        var selected_cube = $(this.el).find('.cubes').val();
        if (selected_cube === "") {
            selected_cube = this.query.get('connection') + "/" +
                this.query.get('catalog') + "/" +
                this.query.get('schema') + "/" + this.query.get('cube');
            $(this.el).find('.cubes')
                .val(selected_cube);
        }

        // Create new DimensionList and MeasureList
        this.dimension_list = new DimensionList({
            workspace: this,
            dimension: Saiku.session.dimensions[selected_cube]
        });
        $(this.el).find('.dimension_tree').html('').append($(this.dimension_list.el));

        this.measure_list = new DimensionList({
            workspace: this,
            dimension: Saiku.session.measures[selected_cube]
        });
        $(this.el).find('.measure_tree').html('').append($(this.measure_list.el));

        // Clear workspace
        this.clear();

        // Populate selections - trust me, this is prettier than it was :-/
        var axes = this.query ? this.query.get('axes') : false;
        if (axes) {
            for (var axis_iter = 0; axis_iter < axes.length; axis_iter++) {
                var axis = axes[axis_iter];
                var $axis = $(this.el).find('.' +
                    axis.name.toLowerCase() + ' ul');
                for (var dim_iter = 0; dim_iter < axis.dimensionSelections.length; dim_iter++) {
                    var dimension = axis.dimensionSelections[dim_iter];
                    var levels = [];
                    var members = {};
                    for (var sel_iter = 0; sel_iter < dimension.selections.length; sel_iter++) {
                        var selection = dimension.selections[sel_iter];

                        // Drag over dimensions and measures
                        var type, name;
                        if (selection.dimensionUniqueName == "Measures") {
                            type = "measure";
                            name = selection.uniqueName;
                        } else {
                            type = "dimension";
                            name = selection.levelUniqueName;
                        }

                        if (levels.indexOf(name) === -1) {
                            console.log(name);
                            var $dim = $(this.el).find('.' + type + '_tree')
                                .find('a[title="' + name + '"]')
                                .parent();
                            $dim.clone()
                                .addClass('d_' + type)
                                .appendTo($axis);
                            $dim.css({fontWeight: "bold"})
                                .draggable('disable')
                                .parents('.parent_dimension')
                                .find('.root')
                                .css({fontWeight: "bold"})
                                .draggable('disable');
                            levels.push(name);
                        }

                        // FIXME - something needs to be done about selections here
                    }
                }
            }

            this.query.run();
        }

        // Make sure appropriate workspace buttons are enabled
        this.trigger('query:new', { workspace: this });
    },

    remove_dimension: function(event, ui) {
        this.drop_zones.remove_dimension(event, ui);
    }
});<|endoftext|>


<commit_before>/* global describe, it, before, beforeEach, after, afterEach */

import { call, select } from 'redux-saga/effects';
import { api } from '@steemit/steem-js';
import {
    preBroadcast_comment,
    createPermlink,
    createPatch,
    watchForBroadcast,
    watchForUpdateAuthorities,
    watchForUpdateMeta,
    watchForRecoverAccount,
    preBroadcast_transfer,
} from './TransactionSaga';
import { DEBT_TICKER } from 'app/client_config';

const operation = {
    type: 'comment',
    author: 'Alice',
    body:
        "The Body is a pretty long chunck of text that represents the user's voice, it seems they have much to say, and this is one place where they can do that.",
    category: 'hi',
    json_metadata: {
        tags: ['hi'],
        app: 'steemit/0.1',
        format: 'markdown',
    },
    parent_author: 'candide',
    parent_permlink: 'cool',
    title: 'test',
    __config: {},
    errorCallback: () => '',
    successCallback: () => '',
    memo: '#testing',
};

const username = 'Beatrice';

describe('TransactionSaga', () => {
    describe('createPatch', () => {
        it('should return undefined if empty arguments are passed', () => {
            const actual = createPatch('', '');
            expect(actual).toEqual(undefined);
        });
        it('should return the patch that reconciles two different strings', () => {
            const testString =
                'there is something interesting going on here that I do not fully understand it is seemingly complex but it is actually quite simple';
            const actual = createPatch(testString, testString + 'ILU');
            expect(actual).toEqual(
                '@@ -120,12 +120,15 @@\n quite simple\n+ILU\n'
            );
        });
    });

    describe('watchForBroadcast', () => {
        const gen = watchForBroadcast();
        it('should call takeEvery with BROADCAST_OPERATION', () => {
            const actual = gen.next().value;
            const expected = {
                '@@redux-saga/IO': true,
                TAKE: 'transaction/BROADCAST_OPERATION',
            };
            expect(actual).toEqual(expected);
        });
    });

    describe('watchForUpdateAuthorities', () => {
        const gen = watchForUpdateAuthorities();
        it('should call takeEvery with UPDATE_AUTHORITIES', () => {
            const actual = gen.next().value;
            const expected = {
                '@@redux-saga/IO': true,
                TAKE: 'transaction/UPDATE_AUTHORITIES',
            };
            expect(actual).toEqual(expected);
        });
    });

    describe('watchForUpdateMeta', () => {
        const gen = watchForUpdateMeta();
        it('should call takeEvery with UPDATE_META', () => {
            const actual = gen.next().value;
            const expected = {
                '@@redux-saga/IO': true,
                TAKE: 'transaction/UPDATE_META',
            };
            expect(actual).toEqual(expected);
        });
    });

    describe('preBroadcast_transfer', () => {
        const operationSansMemo = {
            ...operation,
            memo: undefined,
        };
        const arg = { operation: operationSansMemo };
        it('should return select object if it has a memo attribute with string value starting with #', () => {
            const genR = preBroadcast_transfer({ operation });
            const actual = genR.next().value;
            const expected = select(state =>
                state.user.getIn(['current', 'private_keys', 'memo_private'])
            );
            expect(Object.keys(actual)).toEqual(['@@redux-saga/IO', 'SELECT']);
        });
        it('should return the operation unchanged if it has no memo attribute', () => {
            let gen = preBroadcast_transfer(arg);
            const actual = gen.next().value;
            expect(actual).toEqual(operationSansMemo);
        });
    });

    describe('createPermlink', () => {
        const gen = createPermlink(
            operation.title,
            operation.author,
            operation.parent_author,
            operation.parent_permlink
        );
        it('should call the api to get a permlink if the title is valid', () => {
            const actual = gen.next().value;
            const mockCall = call(
                [api, api.getContentAsync],
                operation.author,
                operation.title
            );
            expect(actual).toEqual(mockCall);
        });
        it('should return a string containing the transformed data from the api', () => {
            const permlink = gen.next({ body: 'test' }).value;
            expect(permlink.indexOf('test')).toEqual(7); // TODO: cannot deep equal due to date stamp at runtime.
        });
        it('should generate own permlink, independent of api if title is empty', () => {
            const gen2 = createPermlink(
                '',
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen2.next().value;
            expect(
                actual.indexOf(
                    `re-${operation.parent_author}-${
                        operation.parent_permlink
                    }-`
                )
            ).toEqual(0); // TODO: cannot deep equal due to random hash at runtime.
        });
    });

    describe('preBroadcast_comment', () => {
        let gen = preBroadcast_comment({ operation, username });

        it('should call createPermlink', () => {
            const permlink = gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            ).value;
            const actual = permlink.next().value;
            const expected = call(
                [api, api.getContentAsync],
                operation.author,
                operation.title
            );
            expect(expected).toEqual(actual);
        });
        it('should return the comment options array.', () => {
            let actual = gen.next('mock-permlink-123').value;
            const expected = [
                [
                    'comment',
                    {
                        author: operation.author,
                        category: operation.category,
                        errorCallback: operation.errorCallback,
                        successCallback: operation.successCallback,
                        parent_author: operation.parent_author,
                        parent_permlink: operation.parent_permlink,
                        type: operation.type,
                        __config: operation.__config,
                        memo: operation.memo,
                        permlink: 'mock-permlink-123',
                        json_metadata: JSON.stringify(operation.json_metadata),
                        title: new Buffer(
                            (operation.title || '').trim(),
                            'utf-8'
                        ),
                        body: new Buffer(operation.body, 'utf-8'), // TODO: new Buffer is deprecated, prefer Buffer.from()
                    },
                ],
            ];
            expect(actual).toEqual(expected);
        });
        it('should return a patch as body value if patch is smaller than body.', () => {
            const originalBod = operation.body + 'minor difference';
            operation.__config.originalBody = originalBod;
            gen = preBroadcast_comment({ operation, username });
            gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen.next('mock-permlink-123').value;
            const expected = Buffer.from(
                createPatch(originalBod, operation.body),
                'utf-8'
            );
            expect(actual[0][1].body).toEqual(expected);
        });
        it('should return body as body value if patch is larger than body.', () => {
            const originalBod = 'major difference';
            operation.__config.originalBody = originalBod;
            gen = preBroadcast_comment({ operation, username });
            gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen.next('mock-permlink-123').value;
            const expected = Buffer.from(operation.body, 'utf-8');
            expect(actual[0][1].body).toEqual(expected, 'utf-8');
        });
        it('should include comment_options and autoVote if specified.', () => {
            operation.__config.comment_options = true;
            operation.__config.autoVote = true;
            gen = preBroadcast_comment({ operation, username });
            gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen.next('mock-permlink-123').value;
            const expectedCommentOptions = [
                'comment_options',
                {
                    author: operation.author,
                    permlink: 'mock-permlink-123',
                    max_accepted_payout: ['1000000.000', DEBT_TICKER].join(' '),
                    percent_steem_dollars: 10000,
                    allow_votes: true,
                    allow_curation_rewards: true,
                    extensions: [],
                },
            ];
            const expectedAutoVoteOptions = [
                'vote',
                {
                    voter: operation.author,
                    author: operation.author,
                    permlink: 'mock-permlink-123',
                    weight: 10000,
                },
            ];
            expect(actual[1]).toEqual(expectedCommentOptions);
            expect(actual[2]).toEqual(expectedAutoVoteOptions);
        });
    });
});
<commit_msg>deal with hard to test functions a bit more gracefully<commit_after>/* global describe, it, before, beforeEach, after, afterEach */

import { call, select } from 'redux-saga/effects';
import { api } from '@steemit/steem-js';
import {
    preBroadcast_comment,
    createPermlink,
    createPatch,
    watchForBroadcast,
    watchForUpdateAuthorities,
    watchForUpdateMeta,
    watchForRecoverAccount,
    preBroadcast_transfer,
} from './TransactionSaga';
import { DEBT_TICKER } from 'app/client_config';

const operation = {
    type: 'comment',
    author: 'Alice',
    body:
        "The Body is a pretty long chunck of text that represents the user's voice, it seems they have much to say, and this is one place where they can do that.",
    category: 'hi',
    json_metadata: {
        tags: ['hi'],
        app: 'steemit/0.1',
        format: 'markdown',
    },
    parent_author: 'candide',
    parent_permlink: 'cool',
    title: 'test',
    __config: {},
    errorCallback: () => '',
    successCallback: () => '',
    memo: '#testing',
};

const username = 'Beatrice';

describe('TransactionSaga', () => {
    describe('createPatch', () => {
        it('should return undefined if empty arguments are passed', () => {
            const actual = createPatch('', '');
            expect(actual).toEqual(undefined);
        });
        it('should return the patch that reconciles two different strings', () => {
            const testString =
                'there is something interesting going on here that I do not fully understand it is seemingly complex but it is actually quite simple';
            const actual = createPatch(testString, testString + 'ILU');
            expect(actual).toEqual(
                '@@ -120,12 +120,15 @@\n quite simple\n+ILU\n'
            );
        });
    });

    describe('watchForBroadcast', () => {
        const gen = watchForBroadcast();
        it('should call takeEvery with BROADCAST_OPERATION', () => {
            const actual = gen.next().value;
            const expected = {
                '@@redux-saga/IO': true,
                TAKE: 'transaction/BROADCAST_OPERATION',
            };
            expect(actual).toEqual(expected);
        });
    });

    describe('watchForUpdateAuthorities', () => {
        const gen = watchForUpdateAuthorities();
        it('should call takeEvery with UPDATE_AUTHORITIES', () => {
            const actual = gen.next().value;
            const expected = {
                '@@redux-saga/IO': true,
                TAKE: 'transaction/UPDATE_AUTHORITIES',
            };
            expect(actual).toEqual(expected);
        });
    });

    describe('watchForUpdateMeta', () => {
        const gen = watchForUpdateMeta();
        it('should call takeEvery with UPDATE_META', () => {
            const actual = gen.next().value;
            const expected = {
                '@@redux-saga/IO': true,
                TAKE: 'transaction/UPDATE_META',
            };
            expect(actual).toEqual(expected);
        });
    });

    describe('preBroadcast_transfer', () => {
        const operationSansMemo = {
            ...operation,
            memo: undefined,
        };
        const arg = { operation: operationSansMemo };
        it('should return select object if it has a memo attribute with string value starting with #', () => {
            const genR = preBroadcast_transfer({ operation });
            const actual = genR.next().value;
            const expected = select(state =>
                state.user.getIn(['current', 'private_keys', 'memo_private'])
            );
            expect(Object.keys(actual)).toEqual(['@@redux-saga/IO', 'SELECT']);
        });
        it('should return the operation unchanged if it has no memo attribute', () => {
            let gen = preBroadcast_transfer(arg);
            const actual = gen.next().value;
            expect(actual).toEqual(operationSansMemo);
        });
    });

    describe('createPermlink', () => {
        const gen = createPermlink(
            operation.title,
            operation.author,
            operation.parent_author,
            operation.parent_permlink
        );
        it('should call the api to get a permlink if the title is valid', () => {
            const actual = gen.next().value;
            const mockCall = call(
                [api, api.getContentAsync],
                operation.author,
                operation.title
            );
            expect(actual).toEqual(mockCall);
        });
        it('should return a string containing the transformed data from the api', () => {
            const permlink = gen.next({ body: 'test' }).value;
            expect(permlink.indexOf('test') > -1).toEqual(true); // TODO: cannot deep equal due to date stamp at runtime.
        });
        it('should generate own permlink, independent of api if title is empty', () => {
            const gen2 = createPermlink(
                '',
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen2.next().value;
            expect(
                actual.indexOf(
                    `re-${operation.parent_author}-${
                        operation.parent_permlink
                    }-`
                ) > -1
            ).toEqual(true); // TODO: cannot deep equal due to random hash at runtime.
        });
    });

    describe('preBroadcast_comment', () => {
        let gen = preBroadcast_comment({ operation, username });

        it('should call createPermlink', () => {
            const permlink = gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            ).value;
            const actual = permlink.next().value;
            const expected = call(
                [api, api.getContentAsync],
                operation.author,
                operation.title
            );
            expect(expected).toEqual(actual);
        });
        it('should return the comment options array.', () => {
            let actual = gen.next('mock-permlink-123').value;
            const expected = [
                [
                    'comment',
                    {
                        author: operation.author,
                        category: operation.category,
                        errorCallback: operation.errorCallback,
                        successCallback: operation.successCallback,
                        parent_author: operation.parent_author,
                        parent_permlink: operation.parent_permlink,
                        type: operation.type,
                        __config: operation.__config,
                        memo: operation.memo,
                        permlink: 'mock-permlink-123',
                        json_metadata: JSON.stringify(operation.json_metadata),
                        title: new Buffer(
                            (operation.title || '').trim(),
                            'utf-8'
                        ),
                        body: new Buffer(operation.body, 'utf-8'), // TODO: new Buffer is deprecated, prefer Buffer.from()
                    },
                ],
            ];
            expect(actual).toEqual(expected);
        });
        it('should return a patch as body value if patch is smaller than body.', () => {
            const originalBod = operation.body + 'minor difference';
            operation.__config.originalBody = originalBod;
            gen = preBroadcast_comment({ operation, username });
            gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen.next('mock-permlink-123').value;
            const expected = Buffer.from(
                createPatch(originalBod, operation.body),
                'utf-8'
            );
            expect(actual[0][1].body).toEqual(expected);
        });
        it('should return body as body value if patch is larger than body.', () => {
            const originalBod = 'major difference';
            operation.__config.originalBody = originalBod;
            gen = preBroadcast_comment({ operation, username });
            gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen.next('mock-permlink-123').value;
            const expected = Buffer.from(operation.body, 'utf-8');
            expect(actual[0][1].body).toEqual(expected, 'utf-8');
        });
        it('should include comment_options and autoVote if specified.', () => {
            operation.__config.comment_options = true;
            operation.__config.autoVote = true;
            gen = preBroadcast_comment({ operation, username });
            gen.next(
                operation.title,
                operation.author,
                operation.parent_author,
                operation.parent_permlink
            );
            const actual = gen.next('mock-permlink-123').value;
            const expectedCommentOptions = [
                'comment_options',
                {
                    author: operation.author,
                    permlink: 'mock-permlink-123',
                    max_accepted_payout: ['1000000.000', DEBT_TICKER].join(' '),
                    percent_steem_dollars: 10000,
                    allow_votes: true,
                    allow_curation_rewards: true,
                    extensions: [],
                },
            ];
            const expectedAutoVoteOptions = [
                'vote',
                {
                    voter: operation.author,
                    author: operation.author,
                    permlink: 'mock-permlink-123',
                    weight: 10000,
                },
            ];
            expect(actual[1]).toEqual(expectedCommentOptions);
            expect(actual[2]).toEqual(expectedAutoVoteOptions);
        });
    });
});
<|endoftext|>