<commit_before>/* globals clearTimeout */
import { NodeComponent, FontAwesomeIcon, isEqual } from 'substance'
import ValueComponent from '../shared/ValueComponent'
import CodeEditor from '../shared/CodeEditor'
import { getCellState, getError, getErrorMessage } from '../shared/cellHelpers'
import { toString as stateToString, BROKEN, FAILED, OK } from '../engine/CellStates'
import NodeMenu from './NodeMenu'

const LANG_LABELS = {
  'mini': 'Mini',
  'js': 'JS',
  'node': 'Node',
  'sql': 'SQL',
  'py': 'Py',
  'r': 'R',
}

export default
class CellComponent extends NodeComponent {

  constructor(...args) {
    super(...args)

    this.handleActions({
      // triggered by CodeEditorComponent and MiniLangEditor
      'execute': this._onExecute,
      'break': this._onBreak
    })
  }

  didMount() {
    this.context.editorSession.onRender('document', this._onNodeChange, this, { path: [this.props.node.id]})
  }

  getInitialState() {
    return {
      hideCode: false,
      forceOutput: false
    }
  }

  _renderStatus($$) {
    const cellState = getCellState(this.props.node)
    let statusName = cellState ? stateToString(cellState.status) : 'unknown'
    return $$('div').addClass(`se-status sm-${statusName}`)
  }

  render($$) {
    const cell = this.props.node
    const cellState = getCellState(cell)
    let el = $$('div').addClass('sc-cell')
    el.attr('data-id', cell.id)

    if (!this.state.hideCode) {
      let source = cell.find('source-code')
      let cellEditorContainer = $$('div').addClass('se-cell-editor-container')
      cellEditorContainer.append(
        this._renderStatus($$),
        $$('div').addClass('se-expression').append(
          $$(CodeEditor, {
            path: source.getPath(),
            excludedCommands: this._getBlackListedCommands(),
            language: source.attributes.language,
            multiline: true
          }).ref('expressionEditor')
            .on('escape', this._onEscapeFromCodeEditor)
        )
      )
      el.append(cellEditorContainer)
      el.append(
        this._renderEllipsis($$)
      )
      el.append(
        $$('div').addClass('se-language').append(
          LANG_LABELS[source.attributes.language]
        )
      )
    } else {
      // TODO: Create proper visual style
      el.append(
        $$('button').append(
          this._renderStatus($$),
          $$(FontAwesomeIcon, { icon: 'fa-code' })
        )
          .addClass('se-show-code')
          .attr('title', 'Show Code')
          .on('click', this._showCode)
      )
    }

    if (cellState) {
      const status = cellState.status
      if(status === FAILED || status === BROKEN) {
        let errEl = $$('div').addClass('se-error').append(
          getErrorMessage(getError(cell))
        ).ref('error')
        if (this._hideError) {
          errEl.setStyle('visibility', 'hidden')
        }
        el.append(errEl)
      } else if (status === OK) {
        if (this._showOutput()) {
          el.append(
            $$(ValueComponent, cellState.value).ref('value')
          )
        }
      } else if (this.oldValue && this._showOutput()) {
        el.append(
          $$(ValueComponent, this.oldValue).ref('value')
        ).addClass('sm-pending')
      }
    }
    return el
  }

  /*
    Move this into an overlay, shown depending on app state
  */
  _renderEllipsis($$) {
    let Button = this.getComponent('button')
    let el = $$('div').addClass('se-ellipsis')
    let configurator = this.context.editorSession.getConfigurator()
    let button = $$(Button, {
      icon: 'ellipsis',
      active: false,
      theme: 'light'
    }).on('click', this._toggleMenu)
    el.append(button)

    let sel = this.context.editorSession.getSelection()
    if (sel.isNodeSelection() && sel.getNodeId() === this.props.node.id) {
      el.append(
        $$(NodeMenu, {
          toolPanel: configurator.getToolPanel('node-menu')
        }).ref('menu')
      )
    }
    return el
  }

  getExpression() {
    return this.refs.expressionEditor.getContent()
  }

  _renderMenu($$) {
    let menuEl = $$('div').addClass('se-menu')
    menuEl.append(
      this._renderToggleCode($$),
      this._renderToggleOutput($$)
    )
    return menuEl
  }

  _getBlackListedCommands() {
    const commandGroups = this.context.commandGroups
    let result = []
    ;['annotations', 'insert', 'prompt', 'text-types'].forEach((name) => {
      if (commandGroups[name]) {
        result = result.concat(commandGroups[name])
      }
    })
    return result
  }

  _showCode() {
    this.extendState({
      hideCode: false
    })
  }

  /*
    Generally output is shown when cell is not a definition, however it can be
    enforced
  */
  _showOutput() {
    return !this._isDefinition() || this.state.forceOutput
  }

  _isDefinition() {
    const cellState = getCellState(this.props.node)
    return cellState && cellState.hasOutput()
  }

  _toggleMenu() {
    this.context.editorSession.setSelection({
      type: 'node',
      containerId: 'body-content-1',
      surfaceId: 'bodyEditor',
      nodeId: this.props.node.id,
    })
  }

  _onNodeChange() {
    const cell = this.props.node
    const cellState = getCellState(cell)
    const oldCellState = this._oldCellState
    if (cellState) {
      // 1. does the cell have an error?
      // 2. did the status or the errors change?
      let showError = true
      const status = cellState.status
      if(status === BROKEN || status === FAILED) {
        if (oldCellState) {
          showError = (
            oldCellState.status !== status ||
            !isEqual(oldCellState.errors.map(e => e.message), cellState.errors.map(e => e.message))
          )
        }
      }
      clearTimeout(this.delayError)
      if (showError) {
        this._hideError = true
        this.delayError = setTimeout(() => {
          const errEl = this.refs.error
          if(errEl) {
            errEl.setStyle('visibility', 'visible')
          }
          this._hideError = false
        }, 500)
      }
      this._oldCellState = {
        status,
        errors: cellState.errors.slice()
      }
      // keep the last valid value to be able to reduce flickering in most of the cases
      if (status === OK) {
        this.oldValue = cellState.value
      }
    }
    this.rerender()
  }

  _onExecute() {
    this.context.cellEngine.recompute(this.props.node.id)
  }

  _onBreak() {
    this.context.editorSession.transaction((tx) => {
      tx.selection = this._afterNode()
      tx.insertBlockNode({
        type: 'p'
      })
    })
  }

  _onEscapeFromCodeEditor(event) {
    event.stopPropagation()
    this.send('escape')
  }

  _afterNode() {
    // TODO: not too happy about how difficult it is to set the selection
    const node = this.props.node
    const isolatedNode = this.context.isolatedNodeComponent
    const parentSurface = isolatedNode.getParentSurface()
    return {
      type: 'node',
      nodeId: node.id,
      mode: 'after',
      containerId: parentSurface.getContainerId(),
      surfaceId: parentSurface.id
    }
  }

}

CellComponent.noBlocker = true
<commit_msg>Use display:none to hide early errors.<commit_after>/* globals clearTimeout */
import { NodeComponent, FontAwesomeIcon, isEqual } from 'substance'
import ValueComponent from '../shared/ValueComponent'
import CodeEditor from '../shared/CodeEditor'
import { getCellState, getError, getErrorMessage } from '../shared/cellHelpers'
import { toString as stateToString, BROKEN, FAILED, OK } from '../engine/CellStates'
import NodeMenu from './NodeMenu'

const LANG_LABELS = {
  'mini': 'Mini',
  'js': 'JS',
  'node': 'Node',
  'sql': 'SQL',
  'py': 'Py',
  'r': 'R',
}

export default
class CellComponent extends NodeComponent {

  constructor(...args) {
    super(...args)

    this.handleActions({
      // triggered by CodeEditorComponent and MiniLangEditor
      'execute': this._onExecute,
      'break': this._onBreak
    })
  }

  didMount() {
    this.context.editorSession.onRender('document', this._onNodeChange, this, { path: [this.props.node.id]})
  }

  getInitialState() {
    return {
      hideCode: false,
      forceOutput: false
    }
  }

  _renderStatus($$) {
    const cellState = getCellState(this.props.node)
    let statusName = cellState ? stateToString(cellState.status) : 'unknown'
    return $$('div').addClass(`se-status sm-${statusName}`)
  }

  render($$) {
    const cell = this.props.node
    const cellState = getCellState(cell)
    let el = $$('div').addClass('sc-cell')
    el.attr('data-id', cell.id)

    if (!this.state.hideCode) {
      let source = cell.find('source-code')
      let cellEditorContainer = $$('div').addClass('se-cell-editor-container')
      cellEditorContainer.append(
        this._renderStatus($$),
        $$('div').addClass('se-expression').append(
          $$(CodeEditor, {
            path: source.getPath(),
            excludedCommands: this._getBlackListedCommands(),
            language: source.attributes.language,
            multiline: true
          }).ref('expressionEditor')
            .on('escape', this._onEscapeFromCodeEditor)
        )
      )
      el.append(cellEditorContainer)
      el.append(
        this._renderEllipsis($$)
      )
      el.append(
        $$('div').addClass('se-language').append(
          LANG_LABELS[source.attributes.language]
        )
      )
    } else {
      // TODO: Create proper visual style
      el.append(
        $$('button').append(
          this._renderStatus($$),
          $$(FontAwesomeIcon, { icon: 'fa-code' })
        )
          .addClass('se-show-code')
          .attr('title', 'Show Code')
          .on('click', this._showCode)
      )
    }

    if (cellState) {
      const status = cellState.status
      if(status === FAILED || status === BROKEN) {
        let errEl = $$('div').addClass('se-error').append(
          getErrorMessage(getError(cell))
        ).ref('error')
        if (this._hideError) {
          errEl.setStyle('display', 'none')
        }
        el.append(errEl)
      } else if (status === OK) {
        if (this._showOutput()) {
          el.append(
            $$(ValueComponent, cellState.value).ref('value')
          )
        }
      } else if (this.oldValue && this._showOutput()) {
        el.append(
          $$(ValueComponent, this.oldValue).ref('value')
        ).addClass('sm-pending')
      }
    }
    return el
  }

  /*
    Move this into an overlay, shown depending on app state
  */
  _renderEllipsis($$) {
    let Button = this.getComponent('button')
    let el = $$('div').addClass('se-ellipsis')
    let configurator = this.context.editorSession.getConfigurator()
    let button = $$(Button, {
      icon: 'ellipsis',
      active: false,
      theme: 'light'
    }).on('click', this._toggleMenu)
    el.append(button)

    let sel = this.context.editorSession.getSelection()
    if (sel.isNodeSelection() && sel.getNodeId() === this.props.node.id) {
      el.append(
        $$(NodeMenu, {
          toolPanel: configurator.getToolPanel('node-menu')
        }).ref('menu')
      )
    }
    return el
  }

  getExpression() {
    return this.refs.expressionEditor.getContent()
  }

  _renderMenu($$) {
    let menuEl = $$('div').addClass('se-menu')
    menuEl.append(
      this._renderToggleCode($$),
      this._renderToggleOutput($$)
    )
    return menuEl
  }

  _getBlackListedCommands() {
    const commandGroups = this.context.commandGroups
    let result = []
    ;['annotations', 'insert', 'prompt', 'text-types'].forEach((name) => {
      if (commandGroups[name]) {
        result = result.concat(commandGroups[name])
      }
    })
    return result
  }

  _showCode() {
    this.extendState({
      hideCode: false
    })
  }

  /*
    Generally output is shown when cell is not a definition, however it can be
    enforced
  */
  _showOutput() {
    return !this._isDefinition() || this.state.forceOutput
  }

  _isDefinition() {
    const cellState = getCellState(this.props.node)
    return cellState && cellState.hasOutput()
  }

  _toggleMenu() {
    this.context.editorSession.setSelection({
      type: 'node',
      containerId: 'body-content-1',
      surfaceId: 'bodyEditor',
      nodeId: this.props.node.id,
    })
  }

  _onNodeChange() {
    const cell = this.props.node
    const cellState = getCellState(cell)
    const oldCellState = this._oldCellState
    if (cellState) {
      // 1. does the cell have an error?
      // 2. did the status or the errors change?
      let showError = true
      const status = cellState.status
      if(status === BROKEN || status === FAILED) {
        if (oldCellState) {
          showError = (
            oldCellState.status !== status ||
            !isEqual(oldCellState.errors.map(e => e.message), cellState.errors.map(e => e.message))
          )
        }
      }
      clearTimeout(this.delayError)
      if (showError) {
        this._hideError = true
        this.delayError = setTimeout(() => {
          const errEl = this.refs.error
          if(errEl) {
            errEl.setStyle('display', 'block')
          }
          this._hideError = false
        }, 500)
      }
      this._oldCellState = {
        status,
        errors: cellState.errors.slice()
      }
      // keep the last valid value to be able to reduce flickering in most of the cases
      if (status === OK) {
        this.oldValue = cellState.value
      }
    }
    this.rerender()
  }

  _onExecute() {
    this.context.cellEngine.recompute(this.props.node.id)
  }

  _onBreak() {
    this.context.editorSession.transaction((tx) => {
      tx.selection = this._afterNode()
      tx.insertBlockNode({
        type: 'p'
      })
    })
  }

  _onEscapeFromCodeEditor(event) {
    event.stopPropagation()
    this.send('escape')
  }

  _afterNode() {
    // TODO: not too happy about how difficult it is to set the selection
    const node = this.props.node
    const isolatedNode = this.context.isolatedNodeComponent
    const parentSurface = isolatedNode.getParentSurface()
    return {
      type: 'node',
      nodeId: node.id,
      mode: 'after',
      containerId: parentSurface.getContainerId(),
      surfaceId: parentSurface.id
    }
  }

}

CellComponent.noBlocker = true
<|endoftext|>


<commit_before>/**
 * @author Vladimir Kozhin
 * @license MIT
 */

'use strict';

/**
 * @namespace Jii
 * @ignore
 */
var Jii = require('../Jii');

require('./Object');

/**
 * @class Jii.base.Controller
 * @extends Jii.base.Component
 */
Jii.defineClass('Jii.base.Controller', /** @lends Jii.base.Controller.prototype */{

	__extends: Jii.base.Component,

    __static: /** @lends Jii.base.Controller */{

        /**
         * @event Jii.base.Module#beforeAction
         * @property {Jii.base.ActionEvent} event
         */
        EVENT_BEFORE_ACTION: 'beforeAction',

        /**
         * @event Jii.base.Module#afterAction
         * @property {Jii.base.ActionEvent} event
         */
        EVENT_AFTER_ACTION: 'afterAction'

    },

	/**
	 * @type {string} The ID of this controller.
	 */
	id: null,

	/**
	 * @type {Jii.base.Module} The module that this controller belongs to.
	 */
	module: null,

	/**
	 * @type {string} The ID of the action that is used when the action ID is not specified
	 * in the request. Defaults to 'index'.
	 */
	defaultAction: null,

	/**
	 * @type {string|boolean} the name of the layout to be applied to this controller's views.
	 * This property mainly affects the behavior of [[render()]].
	 * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value.
	 * If false, no layout will be applied.
	 */
	layout: null,

	/**
	 * The view object that can be used to render views or view files.
	 * @type {Jii.view.View}
	 */
	_view: null,

	/**
	 * @constructor
	 */
	constructor: function (id, moduleObject, config) {
		this.id = id;
		this.module = moduleObject;
		this.__super(config);
	},

	/**
	 * Declares external actions for the controller.
	 * This method is meant to be overwritten to declare external actions for the controller.
	 * It should return an array, with array keys being action IDs, and array values the corresponding
	 * action class names or action configuration arrays. For example,
	 *
	 * ~~~
	 * return {
     *     'action1': 'app\components\Action1',
     *     'action2': {
     *         'className': 'app\components\Action2',
     *         'property1': 'value1',
     *         'property2': 'value2'
     *     }
     * };
	 * ~~~
	 *
	 * [[Jii.createObject()]] will be used later to create the requested action
	 * using the configuration provided here.
	 * @returns {object}
	 */
	actions: function () {
		return {};
	},

	/**
	 * Runs a request specified in terms of a route.
	 * @param {string} route the route to be handled, e.g., 'view', 'comment/view', 'admin/comment/view'.
	 * @param {Jii.base.Context} context
	 * @return {Promise}
	 */
	run: function (route, context) {
		var slashIndex = route.indexOf('/');
		if (slashIndex === -1) {
			return this.runAction(route, context);
		} else if (slashIndex > 0) {
			return this.module.runAction(route, context);
		}

		route = Jii._s.ltrim(route, '/');
		return Jii.app.runAction(route, context);
	},

	/**
	 * Runs an action within this controller with the specified action ID and parameters.
	 * If the action ID is empty, the method will use [[defaultAction]].
	 * @param {string} id The ID of the action to be executed.
	 * @param {Jii.base.Context} context
	 * @return {Promise} The result of the action.
	 * @throws {Jii.exceptions.InvalidRouteException} if the requested action ID cannot be resolved into an action successfully.
	 */
	runAction: function (id, context) {
		var action = this.createAction(id);
		if (action === null) {
			throw new Jii.exceptions.InvalidRouteException(Jii.t('jii', 'Unable to resolve the request: ' + this.getUniqueId() + '/' + id));
		}

		return Promise.all([
				this.module.beforeAction(action, context),
				this.beforeAction(action, context)
			]).then(function (results) {
				if (results.indexOf(false) !== -1) {
					return Promise.reject();
				}

				return action.runWithParams(context).then(function(data) {
                    if (context.response instanceof Jii.base.Response) {
                        context.response.data = data;
                        context.response.send();
                    }

                    return Promise.all([
                        this.module.afterAction(action, context),
                        this.afterAction(action, context)
                    ]).then(function() {
                        return data;
                    })
                }.bind(this));
        }.bind(this)).catch(Jii.catchHandler);
	},

	/**
	 * Creates an action based on the given action ID.
	 * The method first checks if the action ID has been declared in [[actions()]]. If so,
	 * it will use the configuration declared there to create the action object.
	 * If not, it will look for a controller method whose name is in the format of `actionXyz`
	 * where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that
	 * method will be created and returned.
	 * @param {string} id the action ID.
	 * @return {Jii.base.Action} the newly created action instance. Null if the ID doesn't resolve into any action.
	 */
	createAction: function (id) {
		if (id === '') {
			id = this.defaultAction;
		}

		var actionMap = this.actions();
		if (Jii._.has(actionMap, id)) {
			return Jii.createObject(actionMap[id], id, this);
		} else if (/^[a-z0-9\\-_]+$/.test(id)) {
			var method = id.charAt(0).toUpperCase() + id.slice(1);
			method = 'action' + method.replace('-', ' ');

			if (Jii._.isFunction(this[method])) {
				return new Jii.request.InlineAction(id, this, method);
			}
		}

		return null;
	},

	/**
	 *
	 * @param {string} id
	 * @return {boolean}
	 */
	hasAction: function(id) {
		if (id === '') {
			id = this.defaultAction;
		}

		var actionMap = this.actions();
		if (Jii._.has(actionMap, id)) {
			return true;
		} else if (/^[a-z0-9\\-_]+$/.test(id)) {
			var method = id.charAt(0).toUpperCase() + id.slice(1);
			method = 'action' + method.replace('-', ' ');

			return Jii._.isFunction(this[method]);
		}

		return false;
	},

	/**
	 * @return string the controller ID that is prefixed with the module ID (if any).
	 */
	getUniqueId: function () {
		return this.module instanceof Jii.base.Application ? this.id : this.module.getUniqueId() + '/' + this.id;
	},

	/**
	 * This method is invoked right before an action is to be executed (after all possible filters).
	 * @param {Jii.base.Action} action
     * @param {Jii.base.Context} context
     * @return {Promise}
	 */
	beforeAction: function (action, context) {
        this.trigger(this.__static.EVENT_BEFORE_ACTION, new Jii.base.ActionEvent({
            action: action,
            context: context
        }));
		return Promise.resolve(true);
	},

	/**
	 * This method is invoked right after an action is executed.
	 * @param {Jii.base.Action} action
	 * @param {Jii.base.Context} context
     * @return {Promise}
	 */
	afterAction: function (action, context) {
        this.trigger(this.__static.EVENT_AFTER_ACTION, new Jii.base.ActionEvent({
            action: action,
            context: context
        }));
        return Promise.resolve();
	},

	/**
	 * Renders a view and applies layout if available.
	 *
	 * The view to be rendered can be specified in one of the following formats:
	 *
	 * - path alias (e.g. "@app/views/site/index");
	 * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
	 *   The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
	 * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
	 *   The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
	 * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
	 *
	 * To determine which layout should be applied, the following two steps are conducted:
	 *
	 * 1. In the first step, it determines the layout name and the context module:
	 *
	 * - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
	 * - If [[layout]] is null, search through all ancestor modules of this controller and find the first
	 *   module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
	 *   are used as the layout name and the context module, respectively. If such a module is not found
	 *   or the corresponding layout is not a string, it will return false, meaning no applicable layout.
	 *
	 * 2. In the second step, it determines the actual layout file according to the previously found layout name
	 *    and context module. The layout name can be:
	 *
	 * - a path alias (e.g. "@app/views/layouts/main");
	 * - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
	 *   looked for under the [[Application::layoutPath|layout path]] of the application;
	 * - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
	 *   [[Module::layoutPath|layout path]] of the context module.
	 *
	 * If the layout name does not contain a file extension, it will use the default one `.php`.
	 *
	 * @param {string} view   the view name. Please refer to [[findViewFile()]] on how to specify a view name.
	 * @param {object} [params] the parameters (name-value pairs) that should be made available in the view.
	 * These parameters will not be available in the layout.
	 * @return {string} the rendering result.
	 */
	render: function (view, params) {
		params = params || {};

		var output = this.getView().render(view, params, this);
		if (this.getView() instanceof Jii.view.WebView) {
			var layoutFile = this._findLayoutFile(this.getView());
			if (layoutFile !== false) {
				params.content = output;
				return this.getView().renderLayout(layoutFile, {content: output}, this);
			}
		}

		return output;
	},

	/**
	 * Renders a view.
	 * This method differs from [[render()]] in that it does not apply any layout.
	 * @param  {string} view   the view name. Please refer to [[render()]] on how to specify a view name.
	 * @param  {object} [params] the parameters (name-value pairs) that should be made available in the view.
	 * @return {string} the rendering result.
	 */
	renderPartial: function (view, params) {
		params = params || {};

		return this.getView().render(view, params, this);
	},

	/**
	 * Renders a view file.
	 * @param  {string} file   the view file to be rendered. This can be either a file path or a path alias.
	 * @param  {object} [params] the parameters (name-value pairs) that should be made available in the view.
	 * @return {string} the rendering result.
	 */
	renderFile: function (file, params) {
		params = params || {};

		return this.getView().renderFile(file, params, this);
	},

	/**
	 * Returns the view object that can be used to render views or view files.
	 * The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
	 * this view object to implement the actual view rendering.
	 * If not set, it will default to the "view" application component.
	 * @return {Jii.view.View} the view object that can be used to render views or view files.
	 */
	getView: function () {
		if (this._view === null) {
			this._view = Jii.app.view;
		}

		return this._view;
	},

	/**
	 * Sets the view object to be used by this controller.
	 * @param {Jii.view.View} view the view object that can be used to render views or view files.
	 */
	setView: function (view) {
		this._view = view;
	},

	/**
	 * Returns the directory containing view files for this controller.
	 * The default implementation returns the directory named as controller [[id]] under the [[module]]'s
	 * [[viewPath]] directory.
	 * @return {string} the directory containing the view files for this controller.
	 */
	getViewPath: function () {
		return this.module.getViewPath() + '/' + this.id;
	},

	/**
	 * Finds the view file based on the given view name.
	 * @param {string} view the view name or the path alias of the view file. Please refer to [[render()]]
	 * on how to specify this parameter.
	 * @return {string} the view file path. Note that the file may not exist.
	 */
	findViewFile: function (view) {
		return this.getViewPath() + '/' + view;
	},

	/**
	 * Finds the applicable layout file.
	 * @param {Jii.view.View} view the view object to render the layout file.
	 * @return {string|boolean} the layout file path, or false if layout is not needed.
	 * Please refer to [[render()]] on how to specify this parameter.
	 */
	_findLayoutFile: function (view) {
		var module = this.module;
		var layout = null;

		if (Jii._.isString(this.layout)) {
			layout = this.layout;
		} else if (this.layout === null) {
			while (module !== null && module.layout === null) {
				module = module.module;
			}
			if (module !== null && Jii._.isString(module.layout)) {
				layout = module.layout;
			}
		}

		if (!layout) {
			return false;
		}

		var file = null;
		if (layout.indexOf('@') === 0) {
			file = Jii.getAlias(layout);
		} else if (layout.indexOf('/') === 0) {
			file = Jii.app.getLayoutPath() + '/' + layout.substr(1);
		} else {
			file = module.getLayoutPath() + '/' + layout;
		}

		var ext = Jii.helpers.File.getFileExtension(file);
		if (ext !== '') {
			return file;
		}

		var path = file + '.' + view.defaultExtension;
		if (view.defaultExtension !== 'ejs' && ext === '') {
			path = file + '.ejs';
		}

		return path;
	}

});

<commit_msg>#19 action id match bug<commit_after>/**
 * @author Vladimir Kozhin
 * @license MIT
 */

'use strict';

/**
 * @namespace Jii
 * @ignore
 */
var Jii = require('../Jii');

require('./Object');

/**
 * @class Jii.base.Controller
 * @extends Jii.base.Component
 */
Jii.defineClass('Jii.base.Controller', /** @lends Jii.base.Controller.prototype */{

	__extends: Jii.base.Component,

    __static: /** @lends Jii.base.Controller */{

        /**
         * @event Jii.base.Module#beforeAction
         * @property {Jii.base.ActionEvent} event
         */
        EVENT_BEFORE_ACTION: 'beforeAction',

        /**
         * @event Jii.base.Module#afterAction
         * @property {Jii.base.ActionEvent} event
         */
        EVENT_AFTER_ACTION: 'afterAction'

    },

	/**
	 * @type {string} The ID of this controller.
	 */
	id: null,

	/**
	 * @type {Jii.base.Module} The module that this controller belongs to.
	 */
	module: null,

	/**
	 * @type {string} The ID of the action that is used when the action ID is not specified
	 * in the request. Defaults to 'index'.
	 */
	defaultAction: null,

	/**
	 * @type {string|boolean} the name of the layout to be applied to this controller's views.
	 * This property mainly affects the behavior of [[render()]].
	 * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value.
	 * If false, no layout will be applied.
	 */
	layout: null,

	/**
	 * The view object that can be used to render views or view files.
	 * @type {Jii.view.View}
	 */
	_view: null,

	/**
	 * @constructor
	 */
	constructor: function (id, moduleObject, config) {
		this.id = id;
		this.module = moduleObject;
		this.__super(config);
	},

	/**
	 * Declares external actions for the controller.
	 * This method is meant to be overwritten to declare external actions for the controller.
	 * It should return an array, with array keys being action IDs, and array values the corresponding
	 * action class names or action configuration arrays. For example,
	 *
	 * ~~~
	 * return {
     *     'action1': 'app\components\Action1',
     *     'action2': {
     *         'className': 'app\components\Action2',
     *         'property1': 'value1',
     *         'property2': 'value2'
     *     }
     * };
	 * ~~~
	 *
	 * [[Jii.createObject()]] will be used later to create the requested action
	 * using the configuration provided here.
	 * @returns {object}
	 */
	actions: function () {
		return {};
	},

	/**
	 * Runs a request specified in terms of a route.
	 * @param {string} route the route to be handled, e.g., 'view', 'comment/view', 'admin/comment/view'.
	 * @param {Jii.base.Context} context
	 * @return {Promise}
	 */
	run: function (route, context) {
		var slashIndex = route.indexOf('/');
		if (slashIndex === -1) {
			return this.runAction(route, context);
		} else if (slashIndex > 0) {
			return this.module.runAction(route, context);
		}

		route = Jii._s.ltrim(route, '/');
		return Jii.app.runAction(route, context);
	},

	/**
	 * Runs an action within this controller with the specified action ID and parameters.
	 * If the action ID is empty, the method will use [[defaultAction]].
	 * @param {string} id The ID of the action to be executed.
	 * @param {Jii.base.Context} context
	 * @return {Promise} The result of the action.
	 * @throws {Jii.exceptions.InvalidRouteException} if the requested action ID cannot be resolved into an action successfully.
	 */
	runAction: function (id, context) {
		var action = this.createAction(id);
		if (action === null) {
			throw new Jii.exceptions.InvalidRouteException(Jii.t('jii', 'Unable to resolve the request: ' + this.getUniqueId() + '/' + id));
		}

		return Promise.all([
				this.module.beforeAction(action, context),
				this.beforeAction(action, context)
			]).then(function (results) {
				if (results.indexOf(false) !== -1) {
					return Promise.reject();
				}

				return action.runWithParams(context).then(function(data) {
                    if (context.response instanceof Jii.base.Response) {
                        context.response.data = data;
                        context.response.send();
                    }

                    return Promise.all([
                        this.module.afterAction(action, context),
                        this.afterAction(action, context)
                    ]).then(function() {
                        return data;
                    })
                }.bind(this));
        }.bind(this)).catch(Jii.catchHandler);
	},

	/**
	 * Creates an action based on the given action ID.
	 * The method first checks if the action ID has been declared in [[actions()]]. If so,
	 * it will use the configuration declared there to create the action object.
	 * If not, it will look for a controller method whose name is in the format of `actionXyz`
	 * where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that
	 * method will be created and returned.
	 * @param {string} id the action ID.
	 * @return {Jii.base.Action} the newly created action instance. Null if the ID doesn't resolve into any action.
	 */
	createAction: function (id) {
		if (id === '') {
			id = this.defaultAction;
		}

		var actionMap = this.actions();
		if (Jii._.has(actionMap, id)) {
			return Jii.createObject(actionMap[id], id, this);
		} else if (/^[a-z0-9\\-_]+$/.test(id)) {
			var method = 'action' + ('-' + id).replace(/-([a-z])/g, function(m, v) { return v.toUpperCase(); })

			if (Jii._.isFunction(this[method])) {
				return new Jii.request.InlineAction(id, this, method);
			}
		}

		return null;
	},

	/**
	 *
	 * @param {string} id
	 * @return {boolean}
	 */
	hasAction: function(id) {
		if (id === '') {
			id = this.defaultAction;
		}

		var actionMap = this.actions();
		if (Jii._.has(actionMap, id)) {
			return true;
		} else if (/^[a-z0-9\\-_]+$/.test(id)) {
			var method = id.charAt(0).toUpperCase() + id.slice(1);
			method = 'action' + method.replace('-', ' ');

			return Jii._.isFunction(this[method]);
		}

		return false;
	},

	/**
	 * @return string the controller ID that is prefixed with the module ID (if any).
	 */
	getUniqueId: function () {
		return this.module instanceof Jii.base.Application ? this.id : this.module.getUniqueId() + '/' + this.id;
	},

	/**
	 * This method is invoked right before an action is to be executed (after all possible filters).
	 * @param {Jii.base.Action} action
     * @param {Jii.base.Context} context
     * @return {Promise}
	 */
	beforeAction: function (action, context) {
        this.trigger(this.__static.EVENT_BEFORE_ACTION, new Jii.base.ActionEvent({
            action: action,
            context: context
        }));
		return Promise.resolve(true);
	},

	/**
	 * This method is invoked right after an action is executed.
	 * @param {Jii.base.Action} action
	 * @param {Jii.base.Context} context
     * @return {Promise}
	 */
	afterAction: function (action, context) {
        this.trigger(this.__static.EVENT_AFTER_ACTION, new Jii.base.ActionEvent({
            action: action,
            context: context
        }));
        return Promise.resolve();
	},

	/**
	 * Renders a view and applies layout if available.
	 *
	 * The view to be rendered can be specified in one of the following formats:
	 *
	 * - path alias (e.g. "@app/views/site/index");
	 * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
	 *   The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
	 * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
	 *   The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
	 * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
	 *
	 * To determine which layout should be applied, the following two steps are conducted:
	 *
	 * 1. In the first step, it determines the layout name and the context module:
	 *
	 * - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
	 * - If [[layout]] is null, search through all ancestor modules of this controller and find the first
	 *   module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
	 *   are used as the layout name and the context module, respectively. If such a module is not found
	 *   or the corresponding layout is not a string, it will return false, meaning no applicable layout.
	 *
	 * 2. In the second step, it determines the actual layout file according to the previously found layout name
	 *    and context module. The layout name can be:
	 *
	 * - a path alias (e.g. "@app/views/layouts/main");
	 * - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
	 *   looked for under the [[Application::layoutPath|layout path]] of the application;
	 * - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
	 *   [[Module::layoutPath|layout path]] of the context module.
	 *
	 * If the layout name does not contain a file extension, it will use the default one `.php`.
	 *
	 * @param {string} view   the view name. Please refer to [[findViewFile()]] on how to specify a view name.
	 * @param {object} [params] the parameters (name-value pairs) that should be made available in the view.
	 * These parameters will not be available in the layout.
	 * @return {string} the rendering result.
	 */
	render: function (view, params) {
		params = params || {};

		var output = this.getView().render(view, params, this);
		if (this.getView() instanceof Jii.view.WebView) {
			var layoutFile = this._findLayoutFile(this.getView());
			if (layoutFile !== false) {
				params.content = output;
				return this.getView().renderLayout(layoutFile, {content: output}, this);
			}
		}

		return output;
	},

	/**
	 * Renders a view.
	 * This method differs from [[render()]] in that it does not apply any layout.
	 * @param  {string} view   the view name. Please refer to [[render()]] on how to specify a view name.
	 * @param  {object} [params] the parameters (name-value pairs) that should be made available in the view.
	 * @return {string} the rendering result.
	 */
	renderPartial: function (view, params) {
		params = params || {};

		return this.getView().render(view, params, this);
	},

	/**
	 * Renders a view file.
	 * @param  {string} file   the view file to be rendered. This can be either a file path or a path alias.
	 * @param  {object} [params] the parameters (name-value pairs) that should be made available in the view.
	 * @return {string} the rendering result.
	 */
	renderFile: function (file, params) {
		params = params || {};

		return this.getView().renderFile(file, params, this);
	},

	/**
	 * Returns the view object that can be used to render views or view files.
	 * The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
	 * this view object to implement the actual view rendering.
	 * If not set, it will default to the "view" application component.
	 * @return {Jii.view.View} the view object that can be used to render views or view files.
	 */
	getView: function () {
		if (this._view === null) {
			this._view = Jii.app.view;
		}

		return this._view;
	},

	/**
	 * Sets the view object to be used by this controller.
	 * @param {Jii.view.View} view the view object that can be used to render views or view files.
	 */
	setView: function (view) {
		this._view = view;
	},

	/**
	 * Returns the directory containing view files for this controller.
	 * The default implementation returns the directory named as controller [[id]] under the [[module]]'s
	 * [[viewPath]] directory.
	 * @return {string} the directory containing the view files for this controller.
	 */
	getViewPath: function () {
		return this.module.getViewPath() + '/' + this.id;
	},

	/**
	 * Finds the view file based on the given view name.
	 * @param {string} view the view name or the path alias of the view file. Please refer to [[render()]]
	 * on how to specify this parameter.
	 * @return {string} the view file path. Note that the file may not exist.
	 */
	findViewFile: function (view) {
		return this.getViewPath() + '/' + view;
	},

	/**
	 * Finds the applicable layout file.
	 * @param {Jii.view.View} view the view object to render the layout file.
	 * @return {string|boolean} the layout file path, or false if layout is not needed.
	 * Please refer to [[render()]] on how to specify this parameter.
	 */
	_findLayoutFile: function (view) {
		var module = this.module;
		var layout = null;

		if (Jii._.isString(this.layout)) {
			layout = this.layout;
		} else if (this.layout === null) {
			while (module !== null && module.layout === null) {
				module = module.module;
			}
			if (module !== null && Jii._.isString(module.layout)) {
				layout = module.layout;
			}
		}

		if (!layout) {
			return false;
		}

		var file = null;
		if (layout.indexOf('@') === 0) {
			file = Jii.getAlias(layout);
		} else if (layout.indexOf('/') === 0) {
			file = Jii.app.getLayoutPath() + '/' + layout.substr(1);
		} else {
			file = module.getLayoutPath() + '/' + layout;
		}

		var ext = Jii.helpers.File.getFileExtension(file);
		if (ext !== '') {
			return file;
		}

		var path = file + '.' + view.defaultExtension;
		if (view.defaultExtension !== 'ejs' && ext === '') {
			path = file + '.ejs';
		}

		return path;
	}

});

<|endoftext|>


<commit_before>/*
 * biojs-xpression
 * https://github.com/bene200/biojs-xpression
 *
 * Copyright (c) 2015 Benedikt Rauscher
 * Licensed under the MIT license.
 */

//imports
var Backbone = require("backbone");
var scale = require("chroma-js").scale(["green", "red"]).mode("lab");
var d3 = require("d3");
var tip = require("d3-tip")(d3);

//Tissue color model. One color for each tissue
var TairTissue = Backbone.Model.extend({
    initialize: function(args){
        this.set("stem", args.stem);
        this.set("flower", args.flower);
        this.set("roots", args.roots);
        this.set("leaves", args.leaves);
    }
})

//Every gene model has the expression values, a gene name
//and an additional model that has the tissue specific colors
//as well as a flag describing whether it is currently rendered.
var Gene = Backbone.Model.extend({
    initialize: function(args){
        var cols = this.getCols();
        this.set("colors", new TairTissue(cols));
        this.set("active", false);
    },
    getCols: function(){
        var col = {};
        var t = this.get("tissues");
        for(var key in t){
            hex = scale(t[key]).hex();
            col[key] = hex;
        }
        return col;
    }
})

//Collection of all the genes. The one that is set as active is rendered
var Genes = Backbone.Collection.extend({
    model: Gene,
    active: function(){
        return this.where({active: true})[0];
    },
    setActive: function(gn){
        this.where({name: gn})[0].set("active", true);
    }
})

//This collection describes the legend. It consists of 10 elements
//I use chroma-js to assign colors
var legend = new Backbone.Collection([
    { val: 0.0, col: scale(0.0).hex() },
    { val: 0.1, col: scale(0.1).hex() },
    { val: 0.2, col: scale(0.2).hex() },
    { val: 0.3, col: scale(0.3).hex() },
    { val: 0.4, col: scale(0.4).hex() },
    { val: 0.5, col: scale(0.5).hex() },
    { val: 0.6, col: scale(0.6).hex() },
    { val: 0.7, col: scale(0.7).hex() },
    { val: 0.8, col: scale(0.8).hex() },
    { val: 0.9, col: scale(0.9).hex() },
    { val: 1, col: scale(1).hex() }
])

//The view. Consists of a legend made up of squares and the
//picture of the organism loaded from an HTML file in ../data
//the parts of which are filled according to gene expression levels
module.exports = TairView = Backbone.View.extend({
    initialize: function(args){
        this.el = args.el;
        var genes = [];
        for(var i=0; i<args.data.length; i++){
          genes.push(new Gene({
              name: args.data[i].name,
              tissues: args.data[i].tissues
          }));
        }
        this.genecol = new Genes(genes);
        this.legend = legend;
    },
    show: function(gene){
        this.genecol.setActive(gene);
        this.render();
    },
    render: function(){
        //This is messy but oh well..
        //One Tooltip event is defined for each tissue and the legend
        var self = this;
        var elid = "#" + self.el.id
        var ltip = tip()
            .attr('class', 'd3-tip')
            .offset([-10, 0])
            .html(function(d) {
                return " " + d.val + "";
            });
        var tipLeaves = d3.tip()
            .attr('class', 'd3-tip')
            .offset([0, 0])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in leaves:  " + self.genecol.active().get("tissues").leaves + "";
            });
        var tipRoots = d3.tip()
            .attr('class', 'd3-tip')
            .offset([0, 0])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in roots:  " + self.genecol.active().get("tissues").roots + "";
            });
        var tipStem = d3.tip()
            .attr('class', 'd3-tip')
            .offset([200, -30])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in shoots:  " + self.genecol.active().get("tissues").stem + "";
            });
        var tipFlower = d3.tip()
            .attr('class', 'd3-tip')
            .offset([30, 0])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in the flower:  " + self.genecol.active().get("tissues").flower + "";
            });
        self.el.innerHTML = require("../data/tair");
        var svg = d3.select(elid).selectAll("svg");
        var cols = self.genecol.active().get("colors");

        svg.call(ltip);
        svg.call(tipLeaves);
        svg.call(tipFlower);
        svg.call(tipRoots);
        svg.call(tipStem);

        //Tissues are select, colored according to expression
        //and mouseover/mouseout events are assigned to show
        //tooltips.
        d3.select("#roots")
            .attr("fill", cols.get("roots"))
            .on('mouseover', tipRoots.show)
            .on('mouseout', tipRoots.hide);

        d3.select("#leaves")
            .attr("fill", cols.get("leaves"))
            .on('mouseover', tipLeaves.show)
            .on('mouseout', tipLeaves.hide);

        d3.select("#bloom")
            .attr("fill", cols.get("flower"))
            .on('mouseover', tipFlower.show)
            .on('mouseout', tipFlower.hide);

        d3.select("#stem")
            .attr("fill", cols.get("stem"))
            .on('mouseover', tipStem.show)
            .on('mouseout', tipStem.hide);

        svg.append("svg")
          .attr("width", 600)
          .attr("height", 600)
          .attr("y", 0)
          .attr("x", 230)
          .selectAll("rect")
            .data(self.legend.toJSON()).enter()
            .append("rect")
              .attr("x", function(e) {
                  var value = e.val * 10 * 12;
                  value += 10;
                  return value; })
              .attr("y", 50)
              .attr("width", 12)
              .attr("height", 50)
              .attr("fill", function(e) { return e.col; })
              .on('mouseover', ltip.show)
              .on('mouseout', ltip.hide);
    }
})
<commit_msg>fixed several actives bug<commit_after>/*
 * biojs-xpression
 * https://github.com/bene200/biojs-xpression
 *
 * Copyright (c) 2015 Benedikt Rauscher
 * Licensed under the MIT license.
 */

//imports
var Backbone = require("backbone");
var scale = require("chroma-js").scale(["green", "red"]).mode("lab");
var d3 = require("d3");
var tip = require("d3-tip")(d3);

//Tissue color model. One color for each tissue
var TairTissue = Backbone.Model.extend({
    initialize: function(args){
        this.set("stem", args.stem);
        this.set("flower", args.flower);
        this.set("roots", args.roots);
        this.set("leaves", args.leaves);
    }
})

//Every gene model has the expression values, a gene name
//and an additional model that has the tissue specific colors
//as well as a flag describing whether it is currently rendered.
var Gene = Backbone.Model.extend({
    initialize: function(args){
        var cols = this.getCols();
        this.set("colors", new TairTissue(cols));
        this.set("active", false);
    },
    getCols: function(){
        var col = {};
        var t = this.get("tissues");
        for(var key in t){
            hex = scale(t[key]).hex();
            col[key] = hex;
        }
        return col;
    }
})

//Collection of all the genes. The one that is set as active is rendered
var Genes = Backbone.Collection.extend({
    model: Gene,
    active: function(){
        return this.where({active: true})[0];
    },
    setActive: function(gn){
        this.where({name: gn})[0].set("active", true);
    },
    setInactive: function(){
        var active = this.where({active: true});
        if(active){
            active[0].set("active", false);
        }
    }
})

//This collection describes the legend. It consists of 10 elements
//I use chroma-js to assign colors
var legend = new Backbone.Collection([
    { val: 0.0, col: scale(0.0).hex() },
    { val: 0.1, col: scale(0.1).hex() },
    { val: 0.2, col: scale(0.2).hex() },
    { val: 0.3, col: scale(0.3).hex() },
    { val: 0.4, col: scale(0.4).hex() },
    { val: 0.5, col: scale(0.5).hex() },
    { val: 0.6, col: scale(0.6).hex() },
    { val: 0.7, col: scale(0.7).hex() },
    { val: 0.8, col: scale(0.8).hex() },
    { val: 0.9, col: scale(0.9).hex() },
    { val: 1, col: scale(1).hex() }
])

//The view. Consists of a legend made up of squares and the
//picture of the organism loaded from an HTML file in ../data
//the parts of which are filled according to gene expression levels
module.exports = TairView = Backbone.View.extend({
    initialize: function(args){
        this.el = args.el;
        var genes = [];
        for(var i=0; i<args.data.length; i++){
          genes.push(new Gene({
              name: args.data[i].name,
              tissues: args.data[i].tissues
          }));
        }
        this.genecol = new Genes(genes);
        this.legend = legend;
    },
    show: function(gene){
        this.genecol.setInactive();
        this.genecol.setActive(gene);
        this.render();
    },
    render: function(){
        //This is messy but oh well..
        //One Tooltip event is defined for each tissue and the legend
        var self = this;
        var elid = "#" + self.el.id
        var ltip = tip()
            .attr('class', 'd3-tip')
            .offset([-10, 0])
            .html(function(d) {
                return " " + d.val + "";
            });
        var tipLeaves = d3.tip()
            .attr('class', 'd3-tip')
            .offset([0, 0])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in leaves:  " + self.genecol.active().get("tissues").leaves + "";
            });
        var tipRoots = d3.tip()
            .attr('class', 'd3-tip')
            .offset([0, 0])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in roots:  " + self.genecol.active().get("tissues").roots + "";
            });
        var tipStem = d3.tip()
            .attr('class', 'd3-tip')
            .offset([200, -30])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in shoots:  " + self.genecol.active().get("tissues").stem + "";
            });
        var tipFlower = d3.tip()
            .attr('class', 'd3-tip')
            .offset([30, 0])
            .html(function(d) {
                return "Expression of Gene " + self.genecol.active().get("name") + " in the flower:  " + self.genecol.active().get("tissues").flower + "";
            });
        self.el.innerHTML = require("../data/tair");
        var svg = d3.select(elid).selectAll("svg");
        var cols = self.genecol.active().get("colors");

        svg.call(ltip);
        svg.call(tipLeaves);
        svg.call(tipFlower);
        svg.call(tipRoots);
        svg.call(tipStem);

        //Tissues are select, colored according to expression
        //and mouseover/mouseout events are assigned to show
        //tooltips.
        d3.select("#roots")
            .attr("fill", cols.get("roots"))
            .on('mouseover', tipRoots.show)
            .on('mouseout', tipRoots.hide);

        d3.select("#leaves")
            .attr("fill", cols.get("leaves"))
            .on('mouseover', tipLeaves.show)
            .on('mouseout', tipLeaves.hide);

        d3.select("#bloom")
            .attr("fill", cols.get("flower"))
            .on('mouseover', tipFlower.show)
            .on('mouseout', tipFlower.hide);

        d3.select("#stem")
            .attr("fill", cols.get("stem"))
            .on('mouseover', tipStem.show)
            .on('mouseout', tipStem.hide);

        svg.append("svg")
          .attr("width", 600)
          .attr("height", 600)
          .attr("y", 0)
          .attr("x", 230)
          .selectAll("rect")
            .data(self.legend.toJSON()).enter()
            .append("rect")
              .attr("x", function(e) {
                  var value = e.val * 10 * 12;
                  value += 10;
                  return value; })
              .attr("y", 50)
              .attr("width", 12)
              .attr("height", 50)
              .attr("fill", function(e) { return e.col; })
              .on('mouseover', ltip.show)
              .on('mouseout', ltip.hide);
    }
})
<|endoftext|>


<commit_before>//@auth
//@req(name)

import com.hivext.api.core.utils.Transport;

var url = getParam('url'),
    description = "start-stop-scheduler",
    resp, tasks, envName = '${env.envName}', envAppid = '${env.appid}', version;

version = jelastic.system.service.GetVersion().version.split("-").shift();

if (url) {
    //reading script from URL
    var body = new Transport().get(url);

    //delete the script if it exists already
    jelastic.dev.scripting.DeleteScript({appid: appid, session: session, name:name});

    //create a new script
    resp = jelastic.dev.scripting.CreateScript({appid: appid, session: session, name: name, type: 'js', code: body});
    if (resp.result != 0) return buildErrorMessage(resp);
}

resp = jelastic.utils.scheduler.GetTasks({appid: appid, session: session});
if (resp.result != 0) return resp;

tasks = resp.objects;
for (var i = 0, l = tasks.length; i < l; i++)
if (tasks[i].script == name) jelastic.utils.scheduler.RemoveTask({appid: appid, session:session, id: tasks[i].id});

var startCron = getParam('start'),
    stopCron = getParam('stop');

if (startCron) {
    resp = addTask(startCron, 'start')
    if (resp.result != 0) return buildErrorMessage(resp);
}

if (stopCron) {
    resp = addTask(stopCron, 'stop');
}

if (getParam('action') && getParam('action') == 'update') {
    resp.type = 'info';
    return resp;
}

resp.appid = appid;

return resp

function addTask(cron, taskName) {
    var quartz = convert(cron);
    var params = toJSON({
        action: taskName,
        envName: envName
    });

    for (var i = 0, l = quartz.length; i < l; i++) {
        if (compareVersions(version, '5.3') >= 0 || version.indexOf('trunk') != -1) {
            var resp = jelastic.utils.scheduler.CreateEnvTask({appid: appid, envName: envName, session: session, script: name, trigger: "cron:" + quartz[i], description: description, params: params})
        } else {
            var resp = jelastic.utils.scheduler.AddTask({appid: appid, session: session, script: name, trigger: "cron:" + quartz[i], description: description, params: params})
        }
        if (resp.result != 0) return buildErrorMessage(resp)
    }

    return {result: 0}
}

function buildErrorMessage(resp) {
    resp.type = 'error';
    resp.message = resp.error;
    return resp;
}

function compareVersions(a, b) {
   a = a.split("."), b = b.split(".")
   for (var i = 0, l = Math.max(a.length, b.length); i < l; i++) {x = parseInt(a[i], 10) || 0; y = parseInt(b[i], 10) || 0; if (x != y) return x > y ? 1 : -1 }
   return 0;
 }

function convert(cron) {
    //conversion is based on https://github.com/lirantal/cron-to-quartz
    /**
     * Object declaration
     * C2Q - Cron 2 Quartz conversion library
     *
     */
    var C2Q = {};

    /**
     * Get a Quartz CRON notation from provided Unix CRON syntax
     * getQuartz
     *
     * Expects to get a Unix CRON syntax format, for example: 00 11,13 * * *
     *
     * @param {string} unix CRON format
     * @return {array} array of arrays
     *   for example:
     *    [
     *     ['0', '00', '11,13', '*', '*', '?', '*' ]
     *    ]
     *
     *
     */
    C2Q.getQuartz = function (crontab) {

        var data = [];
        var quartzEntry = [];

        // check for cron magic entries
        quartzEntry = parseCronMagics(crontab);

        if (quartzEntry) {
            data.push(quartzEntry);
        } else {

            // if cron magic entries not found, proceed to parsing normal cron format
            var crontabEntry = crontab.split(' ');
            quartzEntry = parseCronSyntax(crontabEntry);

            data.push(quartzEntry);

            // Quartz doesn't support both DOM and DOW definitions so if we find such ocurrence we'll need to
            // create 2 Quartz entries, one with DOM and one with DOW to create an OR expression
            if (crontabEntry[2] !== '*' && crontabEntry[4] !== '*') {

                // by default, parseCronSyntax() gives priority to parse the DOM first so we reset it now to * to
                // make sure we also have a variant of the CRON expression with DOW
                crontabEntry[2] = '*';

                quartzEntry = parseCronSyntax(crontabEntry);
                data.push(quartzEntry);
            }

        }

        return data;
    };

    function advanceNumber(str) {

        var quartzCompatibleStr = '';
        var num;
        str.split('').forEach(function (chr) {

            num = parseInt(chr);

            // char is an actual number
            if (!isNaN(num)) {
                // number is in allowed range
                if (num >= 0 && num <= 7) {
                    quartzCompatibleStr += num + 1;
                } else {
                    // otherwise default to 1, beginning of the week
                    quartzCompatibleStr = 1;
                }
            } else {
                quartzCompatibleStr += chr;
            }

        });

        return quartzCompatibleStr;
    }

    /**
     * parse cron
     * parseCronMagics
     *
     * @param {string} a string representation of a unix CRON entry
     * @return {array} an array representation of a Quartz CRON entry
     *
     */

    function parseCronSyntax(crontabEntry) {

        var quartzEntry = [];

        // first we initialize the seconds to 0 by default because linux CRON entries do not include a seconds definition
        quartzEntry.push('0');

        // quartz scheduler can't handle an OR definition, and so it doesn't support both DOM and DOW fields to be defined
        // for this reason we need to shift one of them to be the value or * and the other to be ?
        var toggleQuartzCompat = false;

        crontabEntry.forEach(function (item, index, array) {

            // index 0 = minutes
            // index 1 = hours
            // these cron definitions should be compatible with quartz so we push them as is
            if (index === 0 || index === 1) {
                quartzEntry.push(item);
            }

            // index 2 = DOM = Day of Month
            if (index === 2) {
                if (item !== '?') {
                    toggleQuartzCompat = true;
                }

                if (item === '*') {
                    toggleQuartzCompat = false;
                    item = '?';
                }

                quartzEntry.push(item);
            }

            // index 3 = Month
            if (index === 3) {
                quartzEntry.push(item);
            }

            // index 4 = DOW = Day of Week
            if (index === 4) {

                // day of week needs another adjustments - it is specified as 1-7 in quartz but 0-6 in crontab
                var itemAbbreviated = advanceNumber(item);

                if (toggleQuartzCompat === true) {
                    quartzEntry.push('?');
                } else {
                    quartzEntry.push(itemAbbreviated);
                }
            }

            // beyond index 4 we don't care and exit the loop
            if (index >= 5) {
                return true;
            }

        });

        // quartz expect a last 7th parameter for scheduling yearly recurrence so we pass * by default for all years
        quartzEntry.push('*');

        return quartzEntry;

    }

    /**
     * parse cron format specified with the shorthand magic @ entries, for example: @hourly
     * parseCronMagics
     *
     * @param {string} a string representation of a unix CRON entry
     * @return {array} an array representation of a Quartz CRON entry
     *
     */

    function parseCronMagics(crontab) {

        var quartzEntry = false;

        // @hourly
        if (crontab.indexOf('@hourly') === 0) {
            quartzEntry = ['0', '0', '*', '*', '*', '?', '*'];
        }

        // @daily and @midnight
        if (crontab.indexOf('@daily') === 0 || crontab.indexOf('@midnight') === 0) {
            quartzEntry = ['0', '0', '0', '*', '*', '?', '*'];
        }

        // @weekly
        if (crontab.indexOf('@weekly') === 0) {
            quartzEntry = ['0', '0', '0', '?', '*', '1', '*'];
        }

        // @monthly
        if (crontab.indexOf('@monthly') === 0) {
            quartzEntry = ['0', '0', '0', '1', '*', '?', '*'];
        }

        // @yearly and @annually
        if (crontab.indexOf('@yearly') === 0 || crontab.indexOf('@annually') === 0) {
            quartzEntry = ['0', '0', '0', '1', '1', '?', '*'];
        }

        return quartzEntry || false;
    }

    var arr = C2Q.getQuartz(cron);

    for (var i = 0, l = arr.length; i < l; i++) {
        arr[i] = arr[i].join(' ');
    }
    return arr;
}
<commit_msg>Update create-service.js<commit_after>//@auth
//@req(name)

import com.hivext.api.core.utils.Transport;

var url = getParam('url'),
    description = "start-stop-scheduler",
    resp, tasks, envName = '${env.envName}', envAppid = '${env.appid}', version;

version = jelastic.system.service.GetVersion().version.split("-").shift();

if (url) {
    //reading script from URL
    var body = new Transport().get(url);

    //delete the script if it exists already
    jelastic.dev.scripting.DeleteScript({appid: appid, session: session, name:name});

    //create a new script
    resp = jelastic.dev.scripting.CreateScript({appid: appid, session: session, name: name, type: 'js', code: body});
    if (resp.result != 0) return buildErrorMessage(resp);
}

resp = jelastic.utils.scheduler.GetTasks({appid: appid, session: session});
if (resp.result != 0) return resp;

tasks = resp.objects;
for (var i = 0, l = tasks.length; i < l; i++)
if (tasks[i].script == name) jelastic.utils.scheduler.RemoveTask({appid: appid, session:session, id: tasks[i].id});

var startCron = getParam('start'),
    stopCron = getParam('stop');

if (startCron) {
    resp = addTask(startCron, 'start')
    if (resp.result != 0) return buildErrorMessage(resp);
}

if (stopCron) {
    resp = addTask(stopCron, 'stop');
}

if (getParam('action') && getParam('action') == 'update') {
    resp.type = 'info';
    return resp;
}

resp.appid = appid;
resp.session = session;

return resp;

function addTask(cron, taskName) {
    var quartz = convert(cron);
    var params = toJSON({
        action: taskName,
        envName: envName
    });

    for (var i = 0, l = quartz.length; i < l; i++) {
        if (compareVersions(version, '5.3') >= 0 || version.indexOf('trunk') != -1) {
            var resp = jelastic.utils.scheduler.CreateEnvTask({appid: appid, envName: envName, session: session, script: name, trigger: "cron:" + quartz[i], description: description, params: params})
        } else {
            var resp = jelastic.utils.scheduler.AddTask({appid: appid, session: session, script: name, trigger: "cron:" + quartz[i], description: description, params: params})
        }
        if (resp.result != 0) return buildErrorMessage(resp)
    }

    return {result: 0}
}

function buildErrorMessage(resp) {
    resp.type = 'error';
    resp.message = resp.error;
    return resp;
}

function compareVersions(a, b) {
   a = a.split("."), b = b.split(".")
   for (var i = 0, l = Math.max(a.length, b.length); i < l; i++) {x = parseInt(a[i], 10) || 0; y = parseInt(b[i], 10) || 0; if (x != y) return x > y ? 1 : -1 }
   return 0;
 }

function convert(cron) {
    //conversion is based on https://github.com/lirantal/cron-to-quartz
    /**
     * Object declaration
     * C2Q - Cron 2 Quartz conversion library
     *
     */
    var C2Q = {};

    /**
     * Get a Quartz CRON notation from provided Unix CRON syntax
     * getQuartz
     *
     * Expects to get a Unix CRON syntax format, for example: 00 11,13 * * *
     *
     * @param {string} unix CRON format
     * @return {array} array of arrays
     *   for example:
     *    [
     *     ['0', '00', '11,13', '*', '*', '?', '*' ]
     *    ]
     *
     *
     */
    C2Q.getQuartz = function (crontab) {

        var data = [];
        var quartzEntry = [];

        // check for cron magic entries
        quartzEntry = parseCronMagics(crontab);

        if (quartzEntry) {
            data.push(quartzEntry);
        } else {

            // if cron magic entries not found, proceed to parsing normal cron format
            var crontabEntry = crontab.split(' ');
            quartzEntry = parseCronSyntax(crontabEntry);

            data.push(quartzEntry);

            // Quartz doesn't support both DOM and DOW definitions so if we find such ocurrence we'll need to
            // create 2 Quartz entries, one with DOM and one with DOW to create an OR expression
            if (crontabEntry[2] !== '*' && crontabEntry[4] !== '*') {

                // by default, parseCronSyntax() gives priority to parse the DOM first so we reset it now to * to
                // make sure we also have a variant of the CRON expression with DOW
                crontabEntry[2] = '*';

                quartzEntry = parseCronSyntax(crontabEntry);
                data.push(quartzEntry);
            }

        }

        return data;
    };

    function advanceNumber(str) {

        var quartzCompatibleStr = '';
        var num;
        str.split('').forEach(function (chr) {

            num = parseInt(chr);

            // char is an actual number
            if (!isNaN(num)) {
                // number is in allowed range
                if (num >= 0 && num <= 7) {
                    quartzCompatibleStr += num + 1;
                } else {
                    // otherwise default to 1, beginning of the week
                    quartzCompatibleStr = 1;
                }
            } else {
                quartzCompatibleStr += chr;
            }

        });

        return quartzCompatibleStr;
    }

    /**
     * parse cron
     * parseCronMagics
     *
     * @param {string} a string representation of a unix CRON entry
     * @return {array} an array representation of a Quartz CRON entry
     *
     */

    function parseCronSyntax(crontabEntry) {

        var quartzEntry = [];

        // first we initialize the seconds to 0 by default because linux CRON entries do not include a seconds definition
        quartzEntry.push('0');

        // quartz scheduler can't handle an OR definition, and so it doesn't support both DOM and DOW fields to be defined
        // for this reason we need to shift one of them to be the value or * and the other to be ?
        var toggleQuartzCompat = false;

        crontabEntry.forEach(function (item, index, array) {

            // index 0 = minutes
            // index 1 = hours
            // these cron definitions should be compatible with quartz so we push them as is
            if (index === 0 || index === 1) {
                quartzEntry.push(item);
            }

            // index 2 = DOM = Day of Month
            if (index === 2) {
                if (item !== '?') {
                    toggleQuartzCompat = true;
                }

                if (item === '*') {
                    toggleQuartzCompat = false;
                    item = '?';
                }

                quartzEntry.push(item);
            }

            // index 3 = Month
            if (index === 3) {
                quartzEntry.push(item);
            }

            // index 4 = DOW = Day of Week
            if (index === 4) {

                // day of week needs another adjustments - it is specified as 1-7 in quartz but 0-6 in crontab
                var itemAbbreviated = advanceNumber(item);

                if (toggleQuartzCompat === true) {
                    quartzEntry.push('?');
                } else {
                    quartzEntry.push(itemAbbreviated);
                }
            }

            // beyond index 4 we don't care and exit the loop
            if (index >= 5) {
                return true;
            }

        });

        // quartz expect a last 7th parameter for scheduling yearly recurrence so we pass * by default for all years
        quartzEntry.push('*');

        return quartzEntry;

    }

    /**
     * parse cron format specified with the shorthand magic @ entries, for example: @hourly
     * parseCronMagics
     *
     * @param {string} a string representation of a unix CRON entry
     * @return {array} an array representation of a Quartz CRON entry
     *
     */

    function parseCronMagics(crontab) {

        var quartzEntry = false;

        // @hourly
        if (crontab.indexOf('@hourly') === 0) {
            quartzEntry = ['0', '0', '*', '*', '*', '?', '*'];
        }

        // @daily and @midnight
        if (crontab.indexOf('@daily') === 0 || crontab.indexOf('@midnight') === 0) {
            quartzEntry = ['0', '0', '0', '*', '*', '?', '*'];
        }

        // @weekly
        if (crontab.indexOf('@weekly') === 0) {
            quartzEntry = ['0', '0', '0', '?', '*', '1', '*'];
        }

        // @monthly
        if (crontab.indexOf('@monthly') === 0) {
            quartzEntry = ['0', '0', '0', '1', '*', '?', '*'];
        }

        // @yearly and @annually
        if (crontab.indexOf('@yearly') === 0 || crontab.indexOf('@annually') === 0) {
            quartzEntry = ['0', '0', '0', '1', '1', '?', '*'];
        }

        return quartzEntry || false;
    }

    var arr = C2Q.getQuartz(cron);

    for (var i = 0, l = arr.length; i < l; i++) {
        arr[i] = arr[i].join(' ');
    }
    return arr;
}
<|endoftext|>


<commit_before>(function() {
    'use strict';

    Game.init = function(game) {
        this.maxWidth = 1280;
        this.maxHeight = 720;
    };

    Game.init.prototype = {

        /**
         * Load content here, e.g. this.load.image('myDude', '/assets/images/myDude.png')
         */
        preload: function() {},

        /**
         * Initialization logic here
         */
        create: function() {
            this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

            this.scale.minWidth = 256;
            this.scale.minHeight = 196;
            this.scale.maxWidth = this.maxWidth;
            this.scale.maxHeight = this.maxHeight;

            this.scale.pageAlignHorizontally = true;
            this.scale.pageAlignVertically = true;
            this.scale.setScreenSize(true);

            //Fullscreen mode = maintain aspect radio
            this.game.scale.fullScreenTarget = document.getElementById('game');
            this.game.scale.fullScreenScaleMode = Phaser.ScaleManager.SHOW_ALL;
            this.game.scale.enterFullScreen.add(function() {
                delete this.scale.maxWidth;
                delete this.scale.maxHeight;
            }, this);
            this.game.scale.leaveFullScreen.add(function() {
                this.scale.maxWidth = this.maxWidth;
                this.scale.maxHeight = this.maxHeight;
            }, this);

            var x = this.game.width * 0.35;
            var y = this.game.height * 0.3;
            var width = this.game.width * 0.65 - x;
            var height = this.game.height * 0.7 - y;
            var deadzone = new Phaser.Rectangle(x, y, width, height);
            window.Game.cameraDeadzone = deadzone;

            window.Game.debugMode = this.game.net.getQueryString('debug') === 'true';

            this.state.start('Game.level');
        }

    };

})();
<commit_msg>Removed legacy code<commit_after>(function() {
    'use strict';

    Game.init = function(game) {
        this.maxWidth = 1280;
        this.maxHeight = 720;
    };

    Game.init.prototype = {

        /**
         * Load content here, e.g. this.load.image('myDude', '/assets/images/myDude.png')
         */
        preload: function() {},

        /**
         * Initialization logic here
         */
        create: function() {
            this.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;

            this.scale.minWidth = 256;
            this.scale.minHeight = 196;
            this.scale.maxWidth = this.maxWidth;
            this.scale.maxHeight = this.maxHeight;

            this.scale.pageAlignHorizontally = true;
            this.scale.pageAlignVertically = true;
            this.scale.setScreenSize(true);

            //Fullscreen mode = maintain aspect radio
            this.game.scale.fullScreenTarget = document.getElementById('game');
            this.game.scale.fullScreenScaleMode = Phaser.ScaleManager.SHOW_ALL;
            this.game.scale.enterFullScreen.add(function() {
                delete this.scale.maxWidth;
                delete this.scale.maxHeight;
            }, this);
            this.game.scale.leaveFullScreen.add(function() {
                this.scale.maxWidth = this.maxWidth;
                this.scale.maxHeight = this.maxHeight;
            }, this);

            window.Game.debugMode = this.game.net.getQueryString('debug') === 'true';

            this.state.start('Game.level');
        }

    };

})();
<|endoftext|>


<commit_before>/**
 *
 *  Web Starter Kit
 *  Copyright 2014 Google Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License
 *
 */

'use strict';

// Include Gulp & Tools We'll Use
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var rimraf = require('rimraf');
var runSequence = require('run-sequence');
var browserSync = require('browser-sync');
var pagespeed = require('psi');
var reload = browserSync.reload;

// Lint JavaScript
gulp.task('jshint', function () {
  return gulp.src('app/scripts/**/*.js')
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.jshint.reporter('fail'))
    .pipe(reload({stream: true, once: true}));
});

// Optimize Images
gulp.task('images', function () {
  return gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin({
      progressive: true,
      interlaced: true
    })))
    .pipe(gulp.dest('dist/images'))
    .pipe(reload({stream: true, once: true}))
    .pipe($.size({title: 'images'}));
});

// Automatically Prefix CSS
gulp.task('styles:css', function () {
  return gulp.src('app/styles/**/*.css')
    .pipe($.autoprefixer('last 1 version'))
    .pipe(gulp.dest('app/styles'))
    .pipe(reload({stream: true}))
    .pipe($.size({title: 'styles:css'}));
});

// Compile Sass For Style Guide Components (app/styles/components)
gulp.task('styles:components', function () {
  return gulp.src('app/styles/components/components.scss')
    .pipe($.rubySass({
      style: 'expanded',
      precision: 10,
      loadPath: ['app/styles/components']
    }))
    .pipe($.autoprefixer('last 1 version'))
    .pipe(gulp.dest('app/styles/components'))
    .pipe($.size({title: 'styles:components'}));
});

// Compile Any Other Sass Files You Added (app/styles)
gulp.task('styles:scss', function () {
  return gulp.src(['app/styles/**/*.scss', '!app/styles/components/components.scss'])
    .pipe($.rubySass({
      style: 'expanded',
      precision: 10,
      loadPath: ['app/styles']
    }))
    .pipe($.autoprefixer('last 1 version'))
    .pipe(gulp.dest('.tmp/styles'))
    .pipe($.size({title: 'styles:scss'}));
});

// Output Final CSS Styles
gulp.task('styles', ['styles:components', 'styles:scss', 'styles:css']);

// Scan Your HTML For Assets & Optimize Them
gulp.task('html', function () {
  return gulp.src('app/**/*.html')
    .pipe($.useref.assets({searchPath: '{.tmp,app}'}))
    // Concatenate And Minify JavaScript
    .pipe($.if('*.js', $.uglify()))
    // Concatenate And Minify Styles
    .pipe($.if('*.css', $.csso()))
    // Remove Any Unused CSS
    // Note: If not using the Style Guide, you can delete it from
    // the next line to only include styles your project uses.
    .pipe($.if('*.css', $.uncss({ html: ['app/index.html','app/styleguide/index.html'] })))
    .pipe($.useref.restore())
    .pipe($.useref())
    // Update Production Style Guide Paths
    .pipe($.replace('components/components.css', 'components/main.min.css'))
    // Minify Any HTML
    .pipe($.minifyHtml())
    // Output Files
    .pipe(gulp.dest('dist'))
    .pipe($.size({title: 'html'}));
});

// Clean Output Directory
gulp.task('clean', function (cb) {
  rimraf('dist', rimraf.bind({}, '.tmp', cb));
});

// Watch Files For Changes & Reload
gulp.task('serve', function () {
  browserSync.init({
    server: {
      baseDir: ['app', '.tmp']
    },
    notify: false
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.{css,scss}'], ['styles']);
  gulp.watch(['.tmp/styles/**/*.css'], reload);
  gulp.watch(['app/scripts/**/*.js'], ['jshint']);
  gulp.watch(['app/images/**/*'], ['images']);
});

// Build Production Files, the Default Task
gulp.task('default', ['clean'], function (cb) {
  runSequence('styles', ['jshint', 'html', 'images'], cb);
});

// Run PageSpeed Insights
// Update `url` below to the public URL for your site
gulp.task('pagespeed', pagespeed.bind(null, {
  // By default, we use the PageSpeed Insights
  // free (no API key) tier. You can use a Google
  // Developer API key if you have one. See
  // http://goo.gl/RkN0vE for info key: 'YOUR_API_KEY'
  url: 'https://example.com',
  strategy: 'mobile'
}));
<commit_msg>Fixed so that gulp runs jshint more than once<commit_after>/**
 *
 *  Web Starter Kit
 *  Copyright 2014 Google Inc. All rights reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License
 *
 */

'use strict';

// Include Gulp & Tools We'll Use
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var rimraf = require('rimraf');
var runSequence = require('run-sequence');
var browserSync = require('browser-sync');
var pagespeed = require('psi');
var reload = browserSync.reload;

// Lint JavaScript
gulp.task('jshint', function () {
  return gulp.src('app/scripts/**/*.js')
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.jshint.reporter('fail'))
    .pipe(reload({stream: true}));
});

// Optimize Images
gulp.task('images', function () {
  return gulp.src('app/images/**/*')
    .pipe($.cache($.imagemin({
      progressive: true,
      interlaced: true
    })))
    .pipe(gulp.dest('dist/images'))
    .pipe(reload({stream: true, once: true}))
    .pipe($.size({title: 'images'}));
});

// Automatically Prefix CSS
gulp.task('styles:css', function () {
  return gulp.src('app/styles/**/*.css')
    .pipe($.autoprefixer('last 1 version'))
    .pipe(gulp.dest('app/styles'))
    .pipe(reload({stream: true}))
    .pipe($.size({title: 'styles:css'}));
});

// Compile Sass For Style Guide Components (app/styles/components)
gulp.task('styles:components', function () {
  return gulp.src('app/styles/components/components.scss')
    .pipe($.rubySass({
      style: 'expanded',
      precision: 10,
      loadPath: ['app/styles/components']
    }))
    .pipe($.autoprefixer('last 1 version'))
    .pipe(gulp.dest('app/styles/components'))
    .pipe($.size({title: 'styles:components'}));
});

// Compile Any Other Sass Files You Added (app/styles)
gulp.task('styles:scss', function () {
  return gulp.src(['app/styles/**/*.scss', '!app/styles/components/components.scss'])
    .pipe($.rubySass({
      style: 'expanded',
      precision: 10,
      loadPath: ['app/styles']
    }))
    .pipe($.autoprefixer('last 1 version'))
    .pipe(gulp.dest('.tmp/styles'))
    .pipe($.size({title: 'styles:scss'}));
});

// Output Final CSS Styles
gulp.task('styles', ['styles:components', 'styles:scss', 'styles:css']);

// Scan Your HTML For Assets & Optimize Them
gulp.task('html', function () {
  return gulp.src('app/**/*.html')
    .pipe($.useref.assets({searchPath: '{.tmp,app}'}))
    // Concatenate And Minify JavaScript
    .pipe($.if('*.js', $.uglify()))
    // Concatenate And Minify Styles
    .pipe($.if('*.css', $.csso()))
    // Remove Any Unused CSS
    // Note: If not using the Style Guide, you can delete it from
    // the next line to only include styles your project uses.
    .pipe($.if('*.css', $.uncss({ html: ['app/index.html','app/styleguide/index.html'] })))
    .pipe($.useref.restore())
    .pipe($.useref())
    // Update Production Style Guide Paths
    .pipe($.replace('components/components.css', 'components/main.min.css'))
    // Minify Any HTML
    .pipe($.minifyHtml())
    // Output Files
    .pipe(gulp.dest('dist'))
    .pipe($.size({title: 'html'}));
});

// Clean Output Directory
gulp.task('clean', function (cb) {
  rimraf('dist', rimraf.bind({}, '.tmp', cb));
});

// Watch Files For Changes & Reload
gulp.task('serve', function () {
  browserSync.init({
    server: {
      baseDir: ['app', '.tmp']
    },
    notify: false
  });

  gulp.watch(['app/**/*.html'], reload);
  gulp.watch(['app/styles/**/*.{css,scss}'], ['styles']);
  gulp.watch(['.tmp/styles/**/*.css'], reload);
  gulp.watch(['app/scripts/**/*.js'], ['jshint']);
  gulp.watch(['app/images/**/*'], ['images']);
});

// Build Production Files, the Default Task
gulp.task('default', ['clean'], function (cb) {
  runSequence('styles', ['jshint', 'html', 'images'], cb);
});

// Run PageSpeed Insights
// Update `url` below to the public URL for your site
gulp.task('pagespeed', pagespeed.bind(null, {
  // By default, we use the PageSpeed Insights
  // free (no API key) tier. You can use a Google
  // Developer API key if you have one. See
  // http://goo.gl/RkN0vE for info key: 'YOUR_API_KEY'
  url: 'https://example.com',
  strategy: 'mobile'
}));
<|endoftext|>


<commit_before>angular.module('ontrack.extension.notifications', [
    'ot.service.core',
    'ot.service.graphql'
])
    .config(function ($stateProvider) {
        $stateProvider.state('entity-subscriptions', {
            url: '/extension/notifications/entity-subscriptions/{type}/{id}',
            templateUrl: 'extension/notifications/entity-subscriptions.tpl.html',
            controller: 'EntitySubscriptionsCtrl'
        });
    })
    .controller('EntitySubscriptionsCtrl', function ($scope, $stateParams, $http, ot, otGraphqlService) {
        const view = ot.view();
        const type = $stateParams.type;
        const id = $stateParams.id;

        let viewInitialized = false;

        const queryVariables = {
            type: type,
            id: id
        };
        const query = `query(
            $type: ProjectEntityType!,
            $id: Int!,
        ) {
            entity(type: $type, id: $id) {
                entityName
                entity {
                    ... on Project {
                        links {
                            _page
                        }
                    }
                    ... on Branch {
                        links {
                            _page
                        }
                    }
                    ... on PromotionLevel {
                        links {
                            _page
                        }
                    }
                    ... on ValidationStamp {
                        links {
                            _page
                        }
                    }
                }
            }
        }`;

        $scope.loadingSubscriptions = false;
        const loadSubscriptions = () => {
            $scope.loadingSubscriptions = true;
            otGraphqlService.pageGraphQLCall(query, queryVariables).then((data) => {
                $scope.entityInfo = data.entity;
                if (!viewInitialized) {
                    view.title = `Subscriptions for ${data.entity.entityName}`;
                    const page = data.entity.entity.links._page
                    // Breadcrumbs to the entity
                    let bc = ot.homeBreadcrumbs();
                    bc.push([data.entity.entityName, data.entity.entity.links._page]);
                    view.breadcrumbs = bc;
                    // Close command to the entity
                    view.commands = [
                        ot.viewCloseCommand(page.substring(2))
                    ];
                    viewInitialized = true;
                }
            }).finally(() => {
                $scope.loadingSubscriptions = false;
            });
        };

        loadSubscriptions();

    })
;<commit_msg>#951 UI / Starting the collection of the list of subscriptions<commit_after>angular.module('ontrack.extension.notifications', [
    'ot.service.core',
    'ot.service.graphql'
])
    .config(function ($stateProvider) {
        $stateProvider.state('entity-subscriptions', {
            url: '/extension/notifications/entity-subscriptions/{type}/{id}',
            templateUrl: 'extension/notifications/entity-subscriptions.tpl.html',
            controller: 'EntitySubscriptionsCtrl'
        });
    })
    .controller('EntitySubscriptionsCtrl', function ($scope, $stateParams, $http, ot, otGraphqlService) {
        const view = ot.view();
        const type = $stateParams.type;
        const id = $stateParams.id;

        let viewInitialized = false;

        const queryVariables = {
            type: type,
            id: id
        };
        const query = `query(
            $type: ProjectEntityType!,
            $id: Int!,
        ) {
            entity(type: $type, id: $id) {
                entityName
                entity {
                    ... on Project {
                        links {
                            _page
                        }
                    }
                    ... on Branch {
                        links {
                            _page
                        }
                    }
                    ... on PromotionLevel {
                        links {
                            _page
                        }
                    }
                    ... on ValidationStamp {
                        links {
                            _page
                        }
                    }
                }
            }
            eventSubscriptions(filter: {
                entity: {
                    type: $type,
                    id: $id
                }
            }) {
                pageItems {
                    id
                }
            }
        }`;

        $scope.loadingSubscriptions = false;
        const loadSubscriptions = () => {
            $scope.loadingSubscriptions = true;
            otGraphqlService.pageGraphQLCall(query, queryVariables).then((data) => {
                $scope.entityInfo = data.entity;
                if (!viewInitialized) {
                    view.title = `Subscriptions for ${data.entity.entityName}`;
                    const page = data.entity.entity.links._page
                    // Breadcrumbs to the entity
                    let bc = ot.homeBreadcrumbs();
                    bc.push([data.entity.entityName, data.entity.entity.links._page]);
                    view.breadcrumbs = bc;
                    // Close command to the entity
                    view.commands = [
                        ot.viewCloseCommand(page.substring(2))
                    ];
                    viewInitialized = true;
                }
            }).finally(() => {
                $scope.loadingSubscriptions = false;
            });
        };

        loadSubscriptions();

    })
;<|endoftext|>