<commit_before>import { fakeDataset } from './data/fake-data'
import { processUSGSGeoJSON, copyAndShiftLng } from './data/helpers'
import { wrapTileX, lngDiff, tile2LatLngBounds } from './map-tile-helpers'
import { MAX_TIME, MIN_TIME } from './earthquake-properties'
import Cache from './cache'

const USGS_LIMIT = 12000

function getUSGSPath(tile) {
  const formatDate = date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
  const bb = tile2LatLngBounds(tile)
  const minDate = formatDate(new Date(MIN_TIME))
  const maxDate = formatDate(new Date(MAX_TIME))
  const minMag = Math.max(7 - tile.zoom, 0) // so 5 for the world view (zoom = 2) and lower values for next ones.
  return `http://d1wr4s9s1xsblb.cloudfront.net/fdsnws/event/1/query.geojson?starttime=${minDate}&endtime=${maxDate}` +
    `&maxlatitude=${bb.maxLat}&minlatitude=${bb.minLat}&maxlongitude=${bb.maxLng}&minlongitude=${bb.minLng}` +
    `&minmagnitude=${minMag}&orderby=magnitude&limit=${USGS_LIMIT}`
}

function fakeData(tile, count) {
  const options = tile2LatLngBounds(tile)
  options.minDep = (idx % 6) * 100
  options.maxDep = (idx % 6) * 100
  return new Promise(resolve => {
    setTimeout(function() {
      resolve(fakeDataset(count, options))
    }, Math.random() * 1000)
  })
}

export class RequestAborted {}

export class APIError {
  constructor(statusText, response) {
    this.message = statusText
    this.response = response
  }
}

export default class EarthquakeDataAPI {
  constructor() {
    this.cache = new Cache()
    this.currentRequests = new Set()
  }

  isInCache(tile) {
    // Tile can be outside [-180, 180] range. Limit it to [-180, 180]. That's the convention we follow in
    // #getFromCache and #fetchTile.
    const wrappedTile = wrapTileX(tile)
    return this.cache.has(wrappedTile)
  }

  getFromCache(tile) {
    // Tile can be outside [-180, 180] range. Limit it to [-180, 180], ask cache for this modified tile,
    // copy it then shift longitude values back to the initial range.
    const wrappedTile = wrapTileX(tile)
    const lngOffset = lngDiff(tile, wrappedTile)
    const data = this.cache.get(wrappedTile)
    // Copy and shift so we don't modify cached data!
    return data && lngOffset !== 0 ? copyAndShiftLng(data, lngOffset) : data
  }

  fetchTile(tile) {
    // Tile can be outside [-180, 180] range. Limit it to [-180, 180], ask API for this modified tile,
    // copy it then shift longitude values back to the initial range.
    const wrappedTile = wrapTileX(tile)
    const lngOffset = lngDiff(tile, wrappedTile)
    // Use fake data if there's hash parameter: #fake=<value>, e.g. #fake=2000, #fake=5000, etc.
    const useFakeData = window.location.hash.startsWith('#fake')
    const fakeDataTileCount = useFakeData && parseInt(window.location.hash.split('#fake=')[1])
    const dataPromise = useFakeData ? fakeData(wrappedTile, fakeDataTileCount) : this._fetchData(getUSGSPath(wrappedTile))
    return dataPromise
      .then(response => processUSGSGeoJSON(response))
      .then(data => this.cache.set(wrappedTile, data))
      // Copy and shift so we don't modify cached data!
      .then(data => lngOffset !== 0 ? copyAndShiftLng(data, lngOffset) : data)
  }

  abortAllRequests() {
    this.currentRequests.forEach(request => request.abort())
    this.currentRequests.clear()
  }

  _fetchData(url) {
    return new Promise((resolve, reject) => {
      const oReq = new XMLHttpRequest()
      oReq.addEventListener('load', function () {
        if (this.status >= 200 && this.status < 300) {
          try {
            resolve(JSON.parse(this.responseText))
          } catch(e) {
            // Sometimes USGS API returns malformed JSONs. Report that.
            reject(new APIError('Malformed GeoJSON', e))
          }
        } else {
          reject(new APIError(this.statusText, this))
        }
      })
      oReq.addEventListener('abort', function () {
        reject(new RequestAborted())
      })
      oReq.addEventListener('error', function () {
        reject(new APIError(this.statusText, this))
      })
      oReq.open('GET', url)
      oReq.send()
      // Add it do the set so it can be aborted using #abortAllRequests.
      this.currentRequests.add(oReq)
    })
  }
}<commit_msg>Use custom CC API to download earthquakes data<commit_after>import { fakeDataset } from './data/fake-data'
import { processUSGSGeoJSON, copyAndShiftLng } from './data/helpers'
import { wrapTileX, lngDiff, tile2LatLngBounds } from './map-tile-helpers'
import { MAX_TIME, MIN_TIME } from './earthquake-properties'
import Cache from './cache'

const USGS_LIMIT = 12000

const USGS_API = 'http://earthquake.usgs.gov/fdsnws/event/1/query.geojson'
const CLOUDFRONT_USGS_API = 'http://d1wr4s9s1xsblb.cloudfront.net/fdsnws/event/1/query.geojson'
const CONCORD_API = 'https://e401fd4io0.execute-api.us-east-1.amazonaws.com/production/earthquakes'
const CLOUDFRONT_CONCORD_API = 'http://d876rjgss4hzs.cloudfront.net/production/earthquakes'

function getUSGSPath(tile) {
  const formatDate = date => `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`
  const bb = tile2LatLngBounds(tile)
  const minDate = formatDate(new Date(MIN_TIME))
  const maxDate = formatDate(new Date(MAX_TIME))
  const minMag = Math.max(7 - tile.zoom, 0) // so 5 for the world view (zoom = 2) and lower values for next ones.
  return `${CLOUDFRONT_CONCORD_API}?starttime=${minDate}&endtime=${maxDate}` +
    `&maxlatitude=${bb.maxLat}&minlatitude=${bb.minLat}&maxlongitude=${bb.maxLng}&minlongitude=${bb.minLng}` +
    `&minmagnitude=${minMag}&orderby=magnitude&limit=${USGS_LIMIT}`
}

function fakeData(tile, count) {
  const options = tile2LatLngBounds(tile)
  options.minDep = (idx % 6) * 100
  options.maxDep = (idx % 6) * 100
  return new Promise(resolve => {
    setTimeout(function() {
      resolve(fakeDataset(count, options))
    }, Math.random() * 1000)
  })
}

export class RequestAborted {}

export class APIError {
  constructor(statusText, response) {
    this.message = statusText
    this.response = response
  }
}

export default class EarthquakeDataAPI {
  constructor() {
    this.cache = new Cache()
    this.currentRequests = new Set()
  }

  isInCache(tile) {
    // Tile can be outside [-180, 180] range. Limit it to [-180, 180]. That's the convention we follow in
    // #getFromCache and #fetchTile.
    const wrappedTile = wrapTileX(tile)
    return this.cache.has(wrappedTile)
  }

  getFromCache(tile) {
    // Tile can be outside [-180, 180] range. Limit it to [-180, 180], ask cache for this modified tile,
    // copy it then shift longitude values back to the initial range.
    const wrappedTile = wrapTileX(tile)
    const lngOffset = lngDiff(tile, wrappedTile)
    const data = this.cache.get(wrappedTile)
    // Copy and shift so we don't modify cached data!
    return data && lngOffset !== 0 ? copyAndShiftLng(data, lngOffset) : data
  }

  fetchTile(tile) {
    // Tile can be outside [-180, 180] range. Limit it to [-180, 180], ask API for this modified tile,
    // copy it then shift longitude values back to the initial range.
    const wrappedTile = wrapTileX(tile)
    const lngOffset = lngDiff(tile, wrappedTile)
    // Use fake data if there's hash parameter: #fake=<value>, e.g. #fake=2000, #fake=5000, etc.
    const useFakeData = window.location.hash.startsWith('#fake')
    const fakeDataTileCount = useFakeData && parseInt(window.location.hash.split('#fake=')[1])
    const dataPromise = useFakeData ? fakeData(wrappedTile, fakeDataTileCount) : this._fetchData(getUSGSPath(wrappedTile))
    return dataPromise
      .then(response => processUSGSGeoJSON(response))
      .then(data => this.cache.set(wrappedTile, data))
      // Copy and shift so we don't modify cached data!
      .then(data => lngOffset !== 0 ? copyAndShiftLng(data, lngOffset) : data)
  }

  abortAllRequests() {
    this.currentRequests.forEach(request => request.abort())
    this.currentRequests.clear()
  }

  _fetchData(url) {
    return new Promise((resolve, reject) => {
      const oReq = new XMLHttpRequest()
      oReq.addEventListener('load', function () {
        if (this.status >= 200 && this.status < 300) {
          try {
            resolve(JSON.parse(this.responseText))
          } catch(e) {
            // Sometimes USGS API returns malformed JSONs. Report that.
            reject(new APIError('Malformed GeoJSON', e))
          }
        } else {
          reject(new APIError(this.statusText, this))
        }
      })
      oReq.addEventListener('abort', function () {
        reject(new RequestAborted())
      })
      oReq.addEventListener('error', function () {
        reject(new APIError(this.statusText, this))
      })
      oReq.open('GET', url)
      oReq.send()
      // Add it do the set so it can be aborted using #abortAllRequests.
      this.currentRequests.add(oReq)
    })
  }
}
<|endoftext|>


<commit_before>const path = require('path');
const fse = require('fs-extra');
const spawn = require('cross-spawn');

const matmanMock = require('matman-mock');

module.exports = (configOpts) => {
  // console.log('run-by-pm2', configOpts);

  // pm2 的方式下，则需要先生成 pm2.json 文件，然后再使用 pm2 启动
  const buildPath = matmanMock.util.getMockServerBuildPath(configOpts.rootPath, configOpts.buildPath);
  const pm2ConfigFilePath = path.join(buildPath, 'pm2.json');

  // 获取配置信息
  let pm2Config = getPm2Config(configOpts);

  // 本地构建一份配置到 buildPath 下
  fse.outputJson(pm2ConfigFilePath, pm2Config)
    .then(() => {
      console.log('Generate pm2.json success!', pm2ConfigFilePath);

      // 构建成功之后启动
      run(pm2ConfigFilePath);
    })
    .catch((err) => {
      throw err;
    });
};

/**
 * 启动 pm2
 * @param {String} pm2ConfigFilePath pm2.json 配置文件绝对路径
 */
function run(pm2ConfigFilePath) {
  const runPm2 = spawn('pm2', ['start', pm2ConfigFilePath]);

  // 打印输出
  let output = '';

  // 成功信息
  runPm2.stdout.on('data', (data) => {
    output += data;
  }).pipe(process.stdout);

  // 异常信息
  runPm2.stderr.on('data', (data) => {
    output += data;
  }).pipe(process.stderr);

  // 运行结束
  runPm2.on('close', (code) => {
    // console.log({ code: code, data: output });
  });
}

/**
 * 获得最终的 pm2.json 中内容
 *
 * TODO 这里的配置项应该可以通过 configOpts 传递下来
 *
 * @param configOpts
 * @returns {{apps: *[]}}
 */
function getPm2Config(configOpts) {
  const mockServerPath = matmanMock.util.getMockServerBasePath(configOpts.rootPath, configOpts.mockServerPath);
  const buildPath = matmanMock.util.getMockServerBuildPath(configOpts.rootPath, configOpts.buildPath);
  const matmanConfigPath = path.join(configOpts.rootPath, 'matman.config.js');

  // http://pm2.keymetrics.io/docs/usage/application-declaration/
  let result = {
    apps: [
      {
        name: 'matman_app',
        script: path.join(__dirname, './start-app.js'),
        watch: [mockServerPath],
        ignore_watch: ['node_modules', buildPath],
        args: [matmanConfigPath, configOpts.rootPath],
        env: {
          COMMON_VARIABLE: 'true'
        },
        env_production: {
          NODE_ENV: 'production'
        }
      }
    ]
  };

  return result;
}
<commit_msg>fix: 文件变更之后， mock server 没有自动重启更新 #119<commit_after>const path = require('path');
const fse = require('fs-extra');
const spawn = require('cross-spawn');

const matmanMock = require('matman-mock');

module.exports = (configOpts) => {
  // console.log('run-by-pm2', configOpts);

  // pm2 的方式下，则需要先生成 pm2.json 文件，然后再使用 pm2 启动
  const buildPath = matmanMock.util.getMockServerBuildPath(configOpts.rootPath, configOpts.buildPath);
  const pm2ConfigFilePath = path.join(buildPath, 'pm2.json');

  // 获取配置信息
  let pm2Config = getPm2Config(configOpts);

  // 本地构建一份配置到 buildPath 下
  fse.outputJson(pm2ConfigFilePath, pm2Config)
    .then(() => {
      console.log('Generate pm2.json success!', pm2ConfigFilePath);

      // 注意这里一定要先删除之后再启动，否则可能造成 watch 失效  #119
      deleteTask(() => {
        startTask(pm2ConfigFilePath);
      });
    })
    .catch((err) => {
      throw err;
    });
};

/**
 * 启动 pm2
 * @param {String} pm2ConfigFilePath pm2.json 配置文件绝对路径
 */
function startTask(pm2ConfigFilePath) {
  const runPm2 = spawn('pm2', ['start', pm2ConfigFilePath]);

  // 打印输出
  let output = '';

  // 成功信息
  runPm2.stdout.on('data', (data) => {
    output += data;
  }).pipe(process.stdout);

  // 异常信息
  runPm2.stderr.on('data', (data) => {
    output += data;
  }).pipe(process.stderr);

  // 运行结束
  runPm2.on('close', (code) => {
    // console.log({ code: code, data: output });
  });
}

/**
 * 停止 pm2
 */
function deleteTask(callback) {
  const deletePm2 = spawn('pm2', ['delete', 'matman_app']);

  // 打印输出
  let output = '';

  // 成功信息
  deletePm2.stdout.on('data', (data) => {
    output += data;
  }).pipe(process.stdout);

  // 异常信息
  deletePm2.stderr.on('data', (data) => {
    output += data;
  }).pipe(process.stderr);

  // 运行结束
  deletePm2.on('close', (code) => {
    // console.log({ code: code, data: output });

    setTimeout(() => {
      callback();
    }, 200);

  });
}

/**
 * 获得最终的 pm2.json 中内容
 *
 * TODO 这里的配置项应该可以通过 configOpts 传递下来
 *
 * @param configOpts
 * @returns {{apps: *[]}}
 */
function getPm2Config(configOpts) {
  const mockServerPath = matmanMock.util.getMockServerBasePath(configOpts.rootPath, configOpts.mockServerPath);
  const buildPath = matmanMock.util.getMockServerBuildPath(configOpts.rootPath, configOpts.buildPath);
  const matmanConfigPath = path.join(configOpts.rootPath, 'matman.config.js');

  // http://pm2.keymetrics.io/docs/usage/application-declaration/
  let result = {
    apps: [
      {
        name: 'matman_app',
        script: path.join(__dirname, './start-app.js'),
        watch: [mockServerPath],
        ignore_watch: ['node_modules', buildPath],
        args: [matmanConfigPath, configOpts.rootPath],
        env: {
          COMMON_VARIABLE: 'true'
        },
        env_production: {
          NODE_ENV: 'production'
        }
      }
    ]
  };

  return result;
}
<|endoftext|>


<commit_before>
function create_cytoscape_vis(div_id, layout, style, data, f, hide_singletons, legendType) {
	// legend type defaults to false (only loci)
	legendType = legendType || false;

	var cytoscape_div = $("#" + div_id);
	var height = Math.min(.75*$(window).height(), 600);
	var width = $('#' + div_id).width();
	var offset = 100;
	cytoscape_div.height(height);

	$(".j-sgd-cyto-canvas")
		.attr("width", width)
		.attr("height", height + offset);

	var _legendOffsets = {
		protein: 0,
		go: 75,
		phenotype: 75,
		interaction: 150,
		regulation: 150,
		expression: 75
	};
	var _legendOffset = _legendOffsets[legendType];

	$(".sgd-cyto-canvas-container").parent().height(height + offset + _legendOffset);

	options = {
		showOverlay: false,
		layout: layout,
		minZoom: 0.5,
		maxZoom: 2,
		style: style,

		elements: {
		    nodes: data['nodes'],
		    edges: data['edges']
		}
    };

	$('#' + div_id).cytoscape(options);
    var cy = $('#' + div_id).cytoscape("get");

    // add date
    var $canvas = $("#j-sgd-visible-cyto-canvas")[0]
	var ctx = $canvas.getContext("2d");
	var fontSize = 14;
	ctx.font = fontSize + "pt Helvetica";
	var txt = (new Date()).toLocaleDateString();
	var txtWidth = ctx.measureText(txt).width;
	ctx.fillText(txt, width / 2 - txtWidth / 2, fontSize);

	/// *** draw legend ***

	var legendY = height + 50;
	ctx.fillText("Legend", 0, legendY);

	// helper method to draw legend nodes
	var drawLegendNode = function (ctx, text, x, y, color, isCircle, isBlackText) {
		ctx.fillStyle = color;
		if (isCircle) {
			ctx.beginPath();
			ctx.arc(x, y, 15, 0, 2 * Math.PI, false);
			ctx.closePath();
			ctx.fill();
		} else {
			ctx.beginPath();
			ctx.rect(x - 15, y - 15, 30, 30);
			ctx.closePath();
			ctx.fill();
		}

		var textX = x - ctx.measureText(text).width / 2;
		var textY = y + 3;
		ctx.font = 12 + "pt Helvetica";
		ctx.strokeStyle = isBlackText ? 'white': '#757575';
	    ctx.lineWidth = 5;
	    ctx.strokeText(text, textX, textY);
		ctx.fillStyle = isBlackText ? '#757575' : 'white';
		ctx.fillText(text, textX, textY);
	};

	// draw legend
	var nextLegendY = legendY + 30;
	drawLegendNode(ctx, "Current Locus", 60, nextLegendY, '#F9DA56', true, false);
	drawLegendNode(ctx, "Other Locus", 160, nextLegendY, '#757575', true, true);
	var nextLegendX = 245;
	if (legendType === "protein") {
		drawLegendNode(ctx, "Domain", nextLegendX, nextLegendY, '#3366cc', false, true);
	} else if (legendType === "go") {
		drawLegendNode(ctx, "GO Term", nextLegendX, nextLegendY, '#6CB665', false, true);
	} else if (legendType === "phenotype") {
		drawLegendNode(ctx, "Phenotype", nextLegendX, nextLegendY, '#C591F5', false, true);
	}

    cy.zoomingEnabled(false);
    if(f != null) {
        f();
	}
	cy.on('tap', 'node', function(evt){
  		var node = evt.cyTarget;
        if(node.data().link != null) {
  		    window.location.href = node.data().link;
        }
	});
	cy.on('layoutstop', function(evt){
		$('#cy_recenter').removeAttr('disabled');
	});
	cy.on('tap', function (evt) {
		this.zoomingEnabled(true);
	});
	cy.on('mouseout', function(evt) {
		this.zoomingEnabled(false);
	});

	cy.filters = {};
	cy.applyFilters = function() {
        var elements = cy.elements("*");
	    for(var filterKey in cy.filters) {
            elements = elements.intersect(cy.filters[filterKey]);
	    }
	    elements.show();

	    var notElements = cy.elements("*");
	    notElements = notElements.not(elements);
	    notElements.hide();

	    //Hide singleton nodes
	    if(hide_singletons) {
            var centerNode = cy.elements("node[sub_type = 'FOCUS']")[0].id();
            var singletons = cy.elements("node:visible");
            var connectedNodes = cy.elements("edge[target = '" + centerNode + "']:visible, edge[source = '" + centerNode + "']:visible").connectedNodes();
            singletons = singletons.not(connectedNodes);
            singletons.hide();
        }
	};

    var recenter_button = $('#' + div_id + '_recenter');
    recenter_button.click(function() {
		var old_zoom_value = cy.zoomingEnabled();
		cy.zoomingEnabled(true);
		cy.reset();
        if(typeof cy.layout().run === 'function') {
		    cy.layout().run();
        }
		cy.zoomingEnabled(old_zoom_value);
	});

	return cy;
}

function create_cy_download_button(cy, button_id, file_name) {
    $("#" + button_id).click(function() {

    	// get hidden canvas
    	var $hiddenCanvas = $("#j-sgd-hidden-cyto-canvas")[0];
    	var hiddenCtx = $hiddenCanvas.getContext("2d");

    	// get custom canvas, write to hidden canvas
    	var $customCanvas = $("#j-sgd-visible-cyto-canvas")[0];
    	var customImage = new Image();
    	customImage.src = $customCanvas.toDataURL();
    	hiddenCtx.drawImage(customImage, 0, 0);

    	// write cyto hidden canvas
    	var cytoImage = new Image();
    	cytoImage.src = cy.png();
		hiddenCtx.drawImage(cytoImage, 0, 16);

    	post_to_url('/download_image', { "display_name":file_name, 'data': $hiddenCanvas.toDataURL("image/png") });
    });
    $("#" + button_id).attr('disabled', false);
}

function create_slider(slider_id, graph, min, max, slide_f, stop) {
    var range;
    var start;
	if(max==min) {
		range = {'min': [min],
                'max': [min+1]};
		start = min;
	}
	else {
		range = {'min': [min],
                'max': [max]};
		start = Math.max(3, min);
	}
	var slider = $("#" + slider_id).noUiSlider({
		range: range
		,start: start
		,step: 1
		,handles: 1
		,connect: "lower"
	});
    slider.change(function() {
            var cutoff = slider.val();
            graph.filters['slider'] = slide_f(cutoff);
            graph.applyFilters();
    });

	if(max==min) {
	    slider.attr('disabled', 'disabled');
	}

	create_slider_ticks("slider_ticks", min, max, stop)

	slider.update_new_max = function(smax) {
        var slider_max = smax;
        if(slider_max == min) {
            slider_max = min+1;
        }
        $("#" + slider_id).noUiSlider({
            range: {'min': [min],
                    'max': [slider_max]}
        }, true);
        create_slider_ticks("slider_ticks", min, smax, stop);
        var cutoff = slider.val();
        graph.filters['slider'] = slide_f(cutoff);
        graph.applyFilters();
	};

	var cutoff = slider.val();
    graph.filters['slider'] = slide_f(cutoff);
    graph.applyFilters();

	return slider;
}

function create_slider_ticks(slider_tick_id, min, max, stop) {
    if(stop == null) {
        stop = 10;
    }
    $("#" + slider_tick_id).empty();
    if(max==min) {
		var spacing =  87;
	    i = min-1
	    var value = i+1;
	    if(value >= stop) {
	    	var left = (spacing * (i-min+1))+4.5
	       	$('' +stop+ '+').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
	    }
	    else {
	    	var left = (spacing * (i-min+1))+6
			$('' +value+ '').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
		}
	}
	else {
		var spacing =  87 / (max - min);
	    for (var i = min-1; i < max ; i=i+1) {
	    	var value = i+1;
	    	if(value >= stop) {
	    		var left = (spacing * (i-min+1))+4.5
	        	$('' +stop+ '+').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
	    	}
	    	else {
	    		var left = (spacing * (i-min+1))+6
				$('' +value+ '').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
	    	}
		}
	}
}

function create_discrete_filter(radio_id, graph, slider, target_filter, max_value) {
    var radio = $("#" + radio_id);
    radio.click(function() {
        if(slider != null) {
            slider.update_new_max(max_value);
        }
        graph.filters['discrete'] = target_filter();
        graph.applyFilters();
    });
}<commit_msg>better date formatting<commit_after>
function create_cytoscape_vis(div_id, layout, style, data, f, hide_singletons, legendType) {
	// legend type defaults to false (only loci)
	legendType = legendType || false;

	var cytoscape_div = $("#" + div_id);
	var height = Math.min(.75*$(window).height(), 600);
	var width = $('#' + div_id).width();
	var offset = 100;
	cytoscape_div.height(height);

	$(".j-sgd-cyto-canvas")
		.attr("width", width)
		.attr("height", height + offset);

	var _legendOffsets = {
		protein: 0,
		go: 75,
		phenotype: 75,
		interaction: 150,
		regulation: 150,
		expression: 75
	};
	var _legendOffset = _legendOffsets[legendType];
	$(".sgd-cyto-canvas-container").parent().height(height + offset + _legendOffset);

	options = {
		showOverlay: false,
		layout: layout,
		minZoom: 0.5,
		maxZoom: 2,
		style: style,

		elements: {
		    nodes: data['nodes'],
		    edges: data['edges']
		}
    };

	$('#' + div_id).cytoscape(options);
    var cy = $('#' + div_id).cytoscape("get");

    var legendY = height + 50;

    // add date
    var $canvas = $("#j-sgd-visible-cyto-canvas")[0]
	var ctx = $canvas.getContext("2d");
	var fontSize = 12;
	ctx.font = fontSize + "pt Helvetica";
	var now = new Date();
	var txt = now.getFullYear() + "-" + (now.getMonth() + 1) + "-" + now.getDate();
	var txtWidth = ctx.measureText(txt).width;
	ctx.fillText(txt, width - txtWidth - fontSize, legendY);

	/// *** draw legend ***
	ctx.fillText("Legend", 0, legendY);

	// helper method to draw legend nodes
	var drawLegendNode = function (ctx, text, x, y, color, isCircle, isBlackText) {
		ctx.fillStyle = color;
		if (isCircle) {
			ctx.beginPath();
			ctx.arc(x, y, 15, 0, 2 * Math.PI, false);
			ctx.closePath();
			ctx.fill();
		} else {
			ctx.beginPath();
			ctx.rect(x - 15, y - 15, 30, 30);
			ctx.closePath();
			ctx.fill();
		}

		var textX = x - ctx.measureText(text).width / 2;
		var textY = y + 3;
		ctx.font = 12 + "pt Helvetica";
		ctx.strokeStyle = isBlackText ? 'white': '#757575';
	    ctx.lineWidth = 5;
	    ctx.strokeText(text, textX, textY);
		ctx.fillStyle = isBlackText ? '#757575' : 'white';
		ctx.fillText(text, textX, textY);
	};

	// draw legend
	var nextLegendY = legendY + 30;
	drawLegendNode(ctx, "Current Locus", 60, nextLegendY, '#F9DA56', true, false);
	drawLegendNode(ctx, "Other Locus", 160, nextLegendY, '#757575', true, true);
	var nextLegendX = 245;
	if (legendType === "protein") {
		drawLegendNode(ctx, "Domain", nextLegendX, nextLegendY, '#3366cc', false, true);
	} else if (legendType === "go") {
		drawLegendNode(ctx, "GO Term", nextLegendX, nextLegendY, '#6CB665', false, true);
	} else if (legendType === "phenotype") {
		drawLegendNode(ctx, "Phenotype", nextLegendX, nextLegendY, '#C591F5', false, true);
	}

    cy.zoomingEnabled(false);
    if(f != null) {
        f();
	}
	cy.on('tap', 'node', function(evt){
  		var node = evt.cyTarget;
        if(node.data().link != null) {
  		    window.location.href = node.data().link;
        }
	});
	cy.on('layoutstop', function(evt){
		$('#cy_recenter').removeAttr('disabled');
	});
	cy.on('tap', function (evt) {
		this.zoomingEnabled(true);
	});
	cy.on('mouseout', function(evt) {
		this.zoomingEnabled(false);
	});

	cy.filters = {};
	cy.applyFilters = function() {
        var elements = cy.elements("*");
	    for(var filterKey in cy.filters) {
            elements = elements.intersect(cy.filters[filterKey]);
	    }
	    elements.show();

	    var notElements = cy.elements("*");
	    notElements = notElements.not(elements);
	    notElements.hide();

	    //Hide singleton nodes
	    if(hide_singletons) {
            var centerNode = cy.elements("node[sub_type = 'FOCUS']")[0].id();
            var singletons = cy.elements("node:visible");
            var connectedNodes = cy.elements("edge[target = '" + centerNode + "']:visible, edge[source = '" + centerNode + "']:visible").connectedNodes();
            singletons = singletons.not(connectedNodes);
            singletons.hide();
        }
	};

    var recenter_button = $('#' + div_id + '_recenter');
    recenter_button.click(function() {
		var old_zoom_value = cy.zoomingEnabled();
		cy.zoomingEnabled(true);
		cy.reset();
        if(typeof cy.layout().run === 'function') {
		    cy.layout().run();
        }
		cy.zoomingEnabled(old_zoom_value);
	});

	return cy;
}

function create_cy_download_button(cy, button_id, file_name) {
    $("#" + button_id).click(function() {

    	// get hidden canvas
    	var $hiddenCanvas = $("#j-sgd-hidden-cyto-canvas")[0];
    	var hiddenCtx = $hiddenCanvas.getContext("2d");

    	// get custom canvas, write to hidden canvas
    	var $customCanvas = $("#j-sgd-visible-cyto-canvas")[0];
    	var customImage = new Image();
    	customImage.src = $customCanvas.toDataURL();
    	hiddenCtx.drawImage(customImage, 0, 0);

    	// write cyto hidden canvas
    	var cytoImage = new Image();
    	cytoImage.src = cy.png();
		hiddenCtx.drawImage(cytoImage, 0, 16);

    	post_to_url('/download_image', { "display_name":file_name, 'data': $hiddenCanvas.toDataURL("image/png") });
    });
    $("#" + button_id).attr('disabled', false);
}

function create_slider(slider_id, graph, min, max, slide_f, stop) {
    var range;
    var start;
	if(max==min) {
		range = {'min': [min],
                'max': [min+1]};
		start = min;
	}
	else {
		range = {'min': [min],
                'max': [max]};
		start = Math.max(3, min);
	}
	var slider = $("#" + slider_id).noUiSlider({
		range: range
		,start: start
		,step: 1
		,handles: 1
		,connect: "lower"
	});
    slider.change(function() {
            var cutoff = slider.val();
            graph.filters['slider'] = slide_f(cutoff);
            graph.applyFilters();
    });

	if(max==min) {
	    slider.attr('disabled', 'disabled');
	}

	create_slider_ticks("slider_ticks", min, max, stop)

	slider.update_new_max = function(smax) {
        var slider_max = smax;
        if(slider_max == min) {
            slider_max = min+1;
        }
        $("#" + slider_id).noUiSlider({
            range: {'min': [min],
                    'max': [slider_max]}
        }, true);
        create_slider_ticks("slider_ticks", min, smax, stop);
        var cutoff = slider.val();
        graph.filters['slider'] = slide_f(cutoff);
        graph.applyFilters();
	};

	var cutoff = slider.val();
    graph.filters['slider'] = slide_f(cutoff);
    graph.applyFilters();

	return slider;
}

function create_slider_ticks(slider_tick_id, min, max, stop) {
    if(stop == null) {
        stop = 10;
    }
    $("#" + slider_tick_id).empty();
    if(max==min) {
		var spacing =  87;
	    i = min-1
	    var value = i+1;
	    if(value >= stop) {
	    	var left = (spacing * (i-min+1))+4.5
	       	$('' +stop+ '+').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
	    }
	    else {
	    	var left = (spacing * (i-min+1))+6
			$('' +value+ '').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
		}
	}
	else {
		var spacing =  87 / (max - min);
	    for (var i = min-1; i < max ; i=i+1) {
	    	var value = i+1;
	    	if(value >= stop) {
	    		var left = (spacing * (i-min+1))+4.5
	        	$('' +stop+ '+').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
	    	}
	    	else {
	    		var left = (spacing * (i-min+1))+6
				$('' +value+ '').css('left', left + '%').css('display', 'inline-block').css('position', 'absolute').css('margin-top', '8px').appendTo("#" + slider_tick_id);
	    	}
		}
	}
}

function create_discrete_filter(radio_id, graph, slider, target_filter, max_value) {
    var radio = $("#" + radio_id);
    radio.click(function() {
        if(slider != null) {
            slider.update_new_max(max_value);
        }
        graph.filters['discrete'] = target_filter();
        graph.applyFilters();
    });
}<|endoftext|>


<commit_before>;(function($){
/**
 * jqGrid French Translation
 * Tony Tomov tony@trirand.com
 * http://trirand.com/blog/
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
**/
$.jgrid = {
	defaults : {
		recordtext: "Enregistrements {0} - {1} sur {2}",
		emptyrecords: "Aucun enregistrement a afficher",
		loadtext: "Chargement...",
		pgtext : "Page {0} sur {1}"
	},
	search : {
		caption: "Recherche...",
		Find: "Chercher",
		Reset: "Annuler",
		odata : ['egal', 'différent', 'inférieur', 'inférieur ou égal','supérieur','supérieur ou égal', 'commence par','ne commence pas par','est dans',"n'est pas dans",'fini par','ne fini pas par','contient','ne contient pas'],
		groupOps: [	{ op: "AND", text: "tous" },	{ op: "OR",  text: "aucun" }	],
		matchText: " correspondance",
		rulesText: " règles"
	},
	edit : {
		addCaption: "Ajouter",
		editCaption: "Editer",
		bSubmit: "Valider",
		bCancel: "Annuler",
		bClose: "Fermer",
		saveData: "Les données ont changé ! Enregistrer les modifications ?",
		bYes: "Oui",
		bNo: "Non",
		bExit: "Annuler",
		msg: {
			required: "Champ obligatoire",
			number: "Saisissez un nombre correct",
			minValue: "La valeur doit être supérieure ou égal à 0 ",
			maxValue: "La valeur doit être inférieure ou égal à 0",
			email: "n'est pas un email correct",
			integer: "Saisissez un entier correct",
			url: "n'est pas une adresse correcte. Préfixe requis ('http://' or 'https://')",
			nodefined : " is not defined!",
			novalue : " return value is required!",
			customarray : "Custom function should return array!",
			customfcheck : "Custom function should be present in case of custom checking!"
		}
	},
	view : {
		caption: "Voir les enregistrement",
		bClose: "Fermer"
	},
	del : {
		caption: "Supprimer",
		msg: "Supprimer les enregistrements sélectionnés ?",
		bSubmit: "Supprimer",
		bCancel: "Annuler"
	},
	nav : {
		edittext: " ",
		edittitle: "Editer la ligne sélectionnée",
		addtext:" ",
		addtitle: "Ajouter une ligne",
		deltext: " ",
		deltitle: "Supprimer la ligne sélectionnée",
		searchtext: " ",
		searchtitle: "Chercher un enregistrement",
		refreshtext: "",
		refreshtitle: "Recharger le tableau",
		alertcap: "Avertissement",
		alerttext: "Veuillez sélectionner une ligne",
		viewtext: "",
		viewtitle: "Afficher la ligne sélectionnée"
	},
	col : {
		caption: "Afficher/Masquer les colonnes",
		bSubmit: "Valider",
		bCancel: "Annuler"
	},
	errors : {
		errcap : "Erreur",
		nourl : "Aucune adresse paramétrée",
		norecords: "Aucun enregistrement à traiter",
		model : "Nombre de titres (colNames) <> Nombre de données (colModel)!"
	},
	formatter : {
		integer : {thousandsSeparator: " ", defaultValue: '0'},
		number : {decimalSeparator:",", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0,00'},
		currency : {decimalSeparator:",", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix:"", defaultValue: '0,00'},
		date : {
			dayNames:   [
				"Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam",
				"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"
			],
			monthNames: [
				"Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec",
				"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Saptembre", "Octobre", "Novembre", "Décembre"
			],
			AmPm : ["am","pm","AM","PM"],
			S: function (j) {return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th'},
			srcformat: 'Y-m-d',
			newformat: 'd/m/Y',
			masks : {
				ISO8601Long:"Y-m-d H:i:s",
				ISO8601Short:"Y-m-d",
				ShortDate: "n/j/Y",
				LongDate: "l, F d, Y",
				FullDateTime: "l, F d, Y g:i:s A",
				MonthDay: "F d",
				ShortTime: "g:i A",
				LongTime: "g:i:s A",
				SortableDateTime: "Y-m-d\\TH:i:s",
				UniversalSortableDateTime: "Y-m-d H:i:sO",
				YearMonth: "F, Y"
			},
			reformatAfterEdit : false
		},
		baseLinkUrl: '',
		showAction: '',
		target: '',
		checkbox : {disabled:true},
		idName : 'id'
	}
};
})(jQuery);
<commit_msg>Update for French Translation<commit_after>;(function($){
/**
 * jqGrid French Translation
 * Tony Tomov tony@trirand.com
 * http://trirand.com/blog/
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
**/
$.jgrid = {
	defaults : {
		recordtext: "Enregistrements {0} - {1} sur {2}",
		emptyrecords: "Aucun enregistrement  afficher",
		loadtext: "Chargement...",
		pgtext : "Page {0} sur {1}"
	},
	search : {
		caption: "Recherche...",
		Find: "Chercher",
		Reset: "Annuler",
		odata : ['gal', 'diffrent', 'infrieur', 'infrieur ou gal','suprieur','suprieur ou gal', 'commence par','ne commence pas par','est dans',"n'est pas dans",'fini par','ne fini pas par','contient','ne contient pas'],
		groupOps: [	{ op: "ET", text: "tous" },	{ op: "OU",  text: "aucun" }	],
		matchText: " correspondance",
		rulesText: " rgles"
	},
	edit : {
		addCaption: "Ajouter",
		editCaption: "Editer",
		bSubmit: "Valider",
		bCancel: "Annuler",
		bClose: "Fermer",
		saveData: "Les donnes ont chang ! Enregistrer les modifications ?",
		bYes: "Oui",
		bNo: "Non",
		bExit: "Annuler",
		msg: {
			required: "Champ obligatoire",
			number: "Saisissez un nombre correct",
			minValue: "La valeur doit tre suprieure ou gale  0 ",
			maxValue: "La valeur doit tre infrieure ou gale  0",
			email: "n'est pas un email correct",
			integer: "Saisissez un entier correct",
			url: "n'est pas une adresse correcte. Prfixe requis ('http://' ou 'https://')",
			nodefined : " n'est pas dfini!",
			novalue : " une valeur en retour est requise!",
			customarray : "La fonction personnalise devrait retourner un array!",
			customfcheck : "La fonction personnalise devrait tre prsente en cas de contrle personnalis!"
		}
	},
	view : {
		caption: "Voir les enregistrements",
		bClose: "Fermer"
	},
	del : {
		caption: "Supprimer",
		msg: "Supprimer les enregistrements slectionns ?",
		bSubmit: "Supprimer",
		bCancel: "Annuler"
	},
	nav : {
		edittext: " ",
		edittitle: "Editer la ligne slectionne",
		addtext:" ",
		addtitle: "Ajouter une ligne",
		deltext: " ",
		deltitle: "Supprimer la ligne slectionne",
		searchtext: " ",
		searchtitle: "Chercher un enregistrement",
		refreshtext: "",
		refreshtitle: "Recharger le tableau",
		alertcap: "Avertissement",
		alerttext: "Veuillez slectionner une ligne",
		viewtext: "",
		viewtitle: "Afficher la ligne slectionne"
	},
	col : {
		caption: "Afficher/Masquer les colonnes",
		bSubmit: "Valider",
		bCancel: "Annuler"
	},
	errors : {
		errcap : "Erreur",
		nourl : "Aucune adresse paramtre",
		norecords: "Aucun enregistrement  traiter",
		model : "Nombre de titres (colNames) <> Nombre de donnes (colModel)!"
	},
	formatter : {
		integer : {thousandsSeparator: " ", defaultValue: '0'},
		number : {decimalSeparator:",", thousandsSeparator: " ", decimalPlaces: 2, defaultValue: '0,00'},
		currency : {decimalSeparator:",", thousandsSeparator: " ", decimalPlaces: 2, prefix: "", suffix:"", defaultValue: '0,00'},
		date : {
			dayNames:   [
				"Dim", "Lun", "Mar", "Mer", "Jeu", "Ven", "Sam",
				"Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"
			],
			monthNames: [
				"Jan", "Fev", "Mar", "Avr", "Mai", "Jui", "Jul", "Aou", "Sep", "Oct", "Nov", "Dec",
				"Janvier", "Fvrier", "Mars", "Avril", "Mai", "Juin", "Juillet", "Aot", "Septembre", "Octobre", "Novembre", "Dcembre"
			],
			AmPm : ["am","pm","AM","PM"],
			S: function (j) {return j < 11 || j > 13 ? ['st', 'nd', 'rd', 'th'][Math.min((j - 1) % 10, 3)] : 'th'},
			srcformat: 'Y-m-d',
			newformat: 'd/m/Y',
			masks : {
				ISO8601Long:"Y-m-d H:i:s",
				ISO8601Short:"Y-m-d",
				ShortDate: "n/j/Y",
				LongDate: "l, F d, Y",
				FullDateTime: "l, F d, Y g:i:s A",
				MonthDay: "F d",
				ShortTime: "g:i A",
				LongTime: "g:i:s A",
				SortableDateTime: "Y-m-d\\TH:i:s",
				UniversalSortableDateTime: "Y-m-d H:i:sO",
				YearMonth: "F, Y"
			},
			reformatAfterEdit : false
		},
		baseLinkUrl: '',
		showAction: '',
		target: '',
		checkbox : {disabled:true},
		idName : 'id'
	}
};
})(jQuery);<|endoftext|>


<commit_before>define([
    'bluebird',
    'common/runtime',
    'common/ui',
    'common/format',
    'kb_common/html'
], function(
    Promise,
    Runtime,
    UI,
    format,
    html
) {
    'use strict';

    var t = html.tag,
        div = t('div'),
        p = t('p'),
        span = t('span');

    function niceState(jobState) {
        var label;
        var color;
        switch (jobState) {
            case 'completed':
                label = 'success';
                color = 'green';
                break;
            case 'error':
                label = 'error';
                color = 'red';
                break;
            case 'terminated':
                label = 'cancellation';
                color = 'orange';
                break;
            case 'does_not_exist':
                label = 'does_not_exist';
                color = 'orange';
                break;
            default:
                label = jobState;
                color = 'black';
        }

        return span({
            style: {
                color: color,
                fontWeight: 'bold'
            }
        }, label);
    }

    function updateRunStats(ui, viewModel, jobState) {
        if (!jobState) {
            viewModel.launch._attrib.hidden = false;
            viewModel.launch.label = 'Determining Job State...';
        } else {
            var now = new Date().getTime();

            viewModel.launch._attrib.hidden = true;

            if (jobState.updated) {
                var creation_time = jobState.created;
                // Queue status - at least in or has been in the queue
                viewModel.queue._attrib.hidden = false;
                if (jobState.running) {
                    // Queue Status - show it, and it has finished, so show static elapsed time and
                    //   done't show position in queue.
                    viewModel.queue._attrib.style = { fontWeight: 'normal' };
                    viewModel.queue.active = false;
                    viewModel.queue.label = 'Queued for';
                    var exec_start_time = jobState.running;
                    viewModel.queue.elapsed = format.niceDuration(exec_start_time - creation_time);
                    viewModel.queue.position.label = '';
                    viewModel.queue.position.number = '';

                    // Run Status -- by definition it is running or ran, so show it.
                    viewModel.run._attrib.hidden = false;

                    if (jobState.finished) {
                        viewModel.run._attrib.style = { fontWeight: 'normal' };
                        viewModel.run.active = false;
                        viewModel.run.label = 'Ran for';
                        var finish_time = jobState.finished;
                        viewModel.run.elapsed = format.niceDuration(finish_time - exec_start_time);

                        viewModel.finish._attrib.hidden = false;
                        viewModel.finish._attrib.style = { fontWeight: 'bold' };
                        viewModel.finish.active = true;
                        viewModel.finish.state = niceState(jobState.status);
                        viewModel.finish.time = format.niceTime(finish_time);
                        viewModel.finish.elapsed = format.niceDuration(now - finish_time);

                    } else {
                        viewModel.run._attrib.style = { fontWeight: 'bold' };
                        viewModel.run.active = true;
                        viewModel.run.label = 'Running ' + ui.loading({ size: null, color: 'green' });
                        viewModel.run.elapsed = format.niceDuration(now - exec_start_time);

                        viewModel.finish._attrib.hidden = true;
                    }
                } else {
                    // Run Status - ensure not showing.
                    viewModel.run._attrib.hidden = true;
                    viewModel.run.active = false;

                    if (jobState.finished) {
                        // This can only happen when a job has been cancelled or errored out during queueing.

                        // Queue Status - it is out of the queue
                        viewModel.queue._attrib.style = { fontWeight: 'normal' };
                        viewModel.queue.active = false;
                        viewModel.queue.label = 'Queued for';
                        var finish_time = jobState.finished;
                        viewModel.queue.elapsed = format.niceDuration(finish_time - creation_time);
                        viewModel.queue.position.label = '';
                        viewModel.queue.position.number = '';

                        // Finished Status
                        viewModel.finish._attrib.hidden = false;
                        viewModel.finish._attrib.style = { fontWeight: 'bold' };
                        viewModel.finish.active = true;
                        viewModel.finish.state = niceState(jobState.status);
                        viewModel.finish.time = format.niceTime(finish_time);
                        viewModel.finish.elapsed = format.niceDuration(now - finish_time);
                    } else {
                        // Queue Status - in the queue
                        viewModel.queue._attrib.style = { fontWeight: 'bold' };
                        viewModel.queue.active = true;
                        viewModel.queue.label = 'Queued ' + ui.loading({ size: null, color: 'orange' });
                        if (jobState.position) {
                            viewModel.queue.position.label = ', currently at position ';
                            viewModel.queue.position.number = jobState.position;
                        } else {
                            viewModel.queue.position.label = '';
                            viewModel.queue.position.number = '';
                        }
                        viewModel.queue.elapsed = format.niceDuration(now - creation_time);

                        // Finished status -- ensure not showing
                        viewModel.finish._attrib.hidden = true;
                        viewModel.finish.active = false;
                    }
                }
            } else {
                viewModel.job_not_found._attrib.hidden = false;
            }
        }

        try {
            ui.updateFromViewModel(viewModel);
        } catch (err) {
            console.error('ERROR updating from view model', err);
        }
    }

    function renderRunStats() {
        return div({ dataElement: 'run-stats', style: { paddingTop: '6px' } }, [
            div({
                class: 'row',
                dataElement: 'launch'
            }, [
                span({
                    dataElement: 'label'
                }),
                ' ',
                span({
                    dataElement: 'elapsed',
                    class: 'kb-elapsed-time'
                })
            ]),
            div({
                class: 'row',
                dataElement: 'job_not_found'
            }, [
                span({
                    dataElement: 'message'
                })
            ]),
            div({
                class: 'row',
                dataElement: 'queue'
            }, [
                span({
                    dataElement: 'label'
                }, 'Queue'),
                ' ',
                span({
                    dataElement: 'elapsed',
                    class: 'kb-elapsed-time'
                }),
                span({
                    dataElement: 'position'
                }, [
                    span({
                        dataElement: 'label'
                    }),
                    span({
                        dataElement: 'number'
                    })
                ])
            ]),
            div({
                class: 'row',
                dataElement: 'run'
            }, [
                span({
                    dataElement: 'label'
                }, 'Run'),
                ' ',
                span({
                    dataElement: 'elapsed',
                    class: 'kb-elapsed-time'
                })
            ]),
            div({
                class: 'row',
                dataElement: 'finish'
            }, [
                span('Finished with '),
                span({
                    dataElement: 'state'
                }),
                ' on ',
                span({
                    dataElement: 'time'
                }),
                ' (',
                span({
                    dataElement: 'elapsed'
                }),
                ' ago)'
            ])
        ]);
    }

    function factory(config) {
        var container, ui, listeners = [],
            jobState = null,
            runtime = Runtime.make(),
            listeningForJob = false,
            jobId,
            parentJobId;

        var viewModel = {
            lastUpdated: {
                elapsed: null,
                time: null
            },
            launch: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                label: null,
                elapsed: null
            },
            queue: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                active: null,
                label: null,
                elapsed: null,
                position: {
                    label: null,
                    number: null
                }
            },
            run: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                active: null,
                label: null,
                elapsed: null
            },
            finish: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                active: null,
                state: null,
                time: null,
                elapsed: null
            },
            job_not_found: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                message: div([
                    p([
                        'This job was not found. It was probably started by another user. Only the ',
                        'user who started a job may view it\'s status or associated job logs.'
                    ]),
                    p([
                        'You will not be able to inspect the job status or view the job log'
                    ])
                ])
            }
        };

        function startJobUpdates() {
            if (listeningForJob) {
                return;
            }
            runtime.bus().emit('request-job-update', {
                jobId: jobId,
                parentJobId: parentJobId
            });
            listeningForJob = true;
        }

        function stopJobUpdates() {
            if (listeningForJob) {
                runtime.bus().emit('request-job-completion', {
                    jobId: jobId,
                    parentJobId: parentJobId
                });
                listeningForJob = false;
            }
        }

        function handleJobDoesNotExistUpdate(message) {
            stopJobUpdates();
            jobState = {
                job_state: 'does_not_exist'
            };
        }

        function handleJobStatusUpdate(message) {
            jobState = message.jobState;
            switch (jobState.status) {
                case 'queued':
                case 'created':
                case 'estimating':
                case 'running':
                    startJobUpdates();
                    break;
                case 'completed':
                case 'error':
                case 'terminated':
                    stopJobUpdates();
                    break;
                default:
                    stopJobUpdates();
                    console.error('Unknown job status', jobState.status, message);
                    throw new Error('Unknown job status ' + jobState.status);
            }
        }

        function listenForJobStatus() {
            var ev = runtime.bus().listen({
                channel: {
                    jobId: jobId
                },
                key: {
                    type: 'job-status'
                },
                handle: handleJobStatusUpdate
            });
            listeners.push(ev);

            ev = runtime.bus().listen({
                channel: {
                    jobId: jobId
                },
                key: {
                    type: 'job-canceled'
                },
                handle: function() {
                    console.warn('job cancelled');
                }
            });
            listeners.push(ev);

            ev = runtime.bus().listen({
                channel: {
                    jobId: jobId
                },
                key: {
                    type: 'job-does-not-exist'
                },
                handle: handleJobDoesNotExistUpdate
            });
            listeners.push(ev);
        }

        function stopListeningForJobStatus() {
            runtime.bus().removeListeners(listeners);
        }

        function start(arg) {
            return Promise.try(() => {
                if (container) {
                    return detach();
                }
            })
            .then(() => {
                return Promise.try(() => {
                    container = arg.node;
                    ui = UI.make({ node: container });

                    container.innerHTML = renderRunStats();

                    jobId = arg.jobId;
                    parentJobId = arg.parentJobId ? arg.parentJobId : null;

                    listeners.push(runtime.bus().on('clock-tick', function() {
                        updateRunStats(ui, viewModel, jobState);
                    }));

                    listenForJobStatus();
                    runtime.bus().emit('request-job-status', {
                        jobId: jobId,
                        parentJobId: parentJobId
                    });
                    listeningForJob = true;

                    ui.updateFromViewModel(viewModel);

                });
            });
        }

        function stop() {
            return Promise.try(function() {
                stopListeningForJobStatus();
            });
        }

        function detach() {
            return stop()
                .then(() => {
                    container.innerHTML = '';
                });
        }

        return {
            start: start,
            stop: stop,
            detach: detach
        };
    }

    return {
        make: function(config) {
            return factory(config);
        }
    };

});
<commit_msg>add comments to functions in the job state viewer widget<commit_after>/**
 * This widget shows a simple job state viewer for the app cell.
 * The viewer updates based on changes to the job state and view model.
 * It reports the current job state, runtime, and how long it has been / was queued for.
 */
define([
    'bluebird',
    'common/runtime',
    'common/ui',
    'common/format',
    'kb_common/html'
], function(
    Promise,
    Runtime,
    UI,
    format,
    html
) {
    'use strict';

    let t = html.tag,
        div = t('div'),
        p = t('p'),
        span = t('span');

    /**
     * Translate from EE2's job status (or other job state strings interpreted by the app cell)
     * to a presentable string. This returns a span with the text colored and bolded, and the
     * "nice" readable state string.
     *
     * Translated strings = completed, error, terminated, and does_not_exist. Those all get
     * different colors. Other strings are rendered black.
     * @param {string} jobState
     */
    function niceState(jobState) {
        let label,
            color;
        switch (jobState) {
            case 'completed':
                label = 'success';
                color = 'green';
                break;
            case 'error':
                label = 'error';
                color = 'red';
                break;
            case 'terminated':
                label = 'cancellation';
                color = 'orange';
                break;
            case 'does_not_exist':
                label = 'does_not_exist';
                color = 'orange';
                break;
            default:
                label = jobState;
                color = 'black';
        }

        return span({
            style: {
                color: color,
                fontWeight: 'bold'
            }
        }, label);
    }

    /**
     * Updates the view stats.
     * If there's no jobState, this just posts a message saying we're waiting for info, and stops.
     * Otherwise, if the jobState has an updated time (which all EE2 job states should), we do
     * the calculations and re-render here.
     *
     * In essence, this does all the calculation of what fields to render and passes it on to
     * ui.renderFromViewModel
     * @param {object} ui - the current UI object
     * @param {object} viewModel - the app cell's view model
     * @param {object} jobState - the app cell's current job state
     */
    function updateRunStats(ui, viewModel, jobState) {
        if (!jobState) {
            viewModel.launch._attrib.hidden = false;
            viewModel.launch.label = 'Determining Job State...';
        } else {
            const now = new Date().getTime();

            viewModel.launch._attrib.hidden = true;

            if (jobState.updated) {
                const creationTime = jobState.created;
                // Queue status - at least in or has been in the queue
                viewModel.queue._attrib.hidden = false;
                if (jobState.running) {
                    // Queue Status - show it, and it has finished, so show static elapsed time and
                    //   don't show position in queue.
                    viewModel.queue._attrib.style = { fontWeight: 'normal' };
                    viewModel.queue.active = false;
                    viewModel.queue.label = 'Queued for';
                    const execStartTime = jobState.running;
                    viewModel.queue.elapsed = format.niceDuration(execStartTime - creationTime);
                    viewModel.queue.position.label = '';
                    viewModel.queue.position.number = '';

                    // Run Status -- by definition it is running or ran, so show it.
                    viewModel.run._attrib.hidden = false;

                    if (jobState.finished) {
                        viewModel.run._attrib.style = { fontWeight: 'normal' };
                        viewModel.run.active = false;
                        viewModel.run.label = 'Ran for';
                        const finishTime = jobState.finished;
                        viewModel.run.elapsed = format.niceDuration(finishTime - execStartTime);

                        viewModel.finish._attrib.hidden = false;
                        viewModel.finish._attrib.style = { fontWeight: 'bold' };
                        viewModel.finish.active = true;
                        viewModel.finish.state = niceState(jobState.status);
                        viewModel.finish.time = format.niceTime(finishTime);
                        viewModel.finish.elapsed = format.niceDuration(now - finishTime);

                    } else {
                        viewModel.run._attrib.style = { fontWeight: 'bold' };
                        viewModel.run.active = true;
                        viewModel.run.label = 'Running ' + ui.loading({ size: null, color: 'green' });
                        viewModel.run.elapsed = format.niceDuration(now - execStartTime);

                        viewModel.finish._attrib.hidden = true;
                    }
                } else {
                    // Run Status - ensure not showing.
                    viewModel.run._attrib.hidden = true;
                    viewModel.run.active = false;

                    if (jobState.finished) {
                        // This can only happen when a job has been cancelled or errored out during queueing.

                        // Queue Status - it is out of the queue
                        viewModel.queue._attrib.style = { fontWeight: 'normal' };
                        viewModel.queue.active = false;
                        viewModel.queue.label = 'Queued for';
                        const finishTime = jobState.finished;
                        viewModel.queue.elapsed = format.niceDuration(finishTime - creationTime);
                        viewModel.queue.position.label = '';
                        viewModel.queue.position.number = '';

                        // Finished Status
                        viewModel.finish._attrib.hidden = false;
                        viewModel.finish._attrib.style = { fontWeight: 'bold' };
                        viewModel.finish.active = true;
                        viewModel.finish.state = niceState(jobState.status);
                        viewModel.finish.time = format.niceTime(finishTime);
                        viewModel.finish.elapsed = format.niceDuration(now - finishTime);
                    } else {
                        // Queue Status - in the queue
                        viewModel.queue._attrib.style = { fontWeight: 'bold' };
                        viewModel.queue.active = true;
                        viewModel.queue.label = 'Queued ' + ui.loading({ size: null, color: 'orange' });
                        if (jobState.position) {
                            viewModel.queue.position.label = ', currently at position ';
                            viewModel.queue.position.number = jobState.position;
                        } else {
                            viewModel.queue.position.label = '';
                            viewModel.queue.position.number = '';
                        }
                        viewModel.queue.elapsed = format.niceDuration(now - creationTime);

                        // Finished status -- ensure not showing
                        viewModel.finish._attrib.hidden = true;
                        viewModel.finish.active = false;
                    }
                }
            } else {
                viewModel.job_not_found._attrib.hidden = false;
            }
        }

        try {
            ui.updateFromViewModel(viewModel);
        } catch (err) {
            console.error('ERROR updating from view model', err);
        }
    }

    /**
     * Does the initial rendering set up for the run stats area. Creates rows
     * for each of the various run state possibilities.
     */
    function renderRunStats() {
        return div({ dataElement: 'run-stats', style: { paddingTop: '6px' } }, [
            div({
                class: 'row',
                dataElement: 'launch'
            }, [
                span({
                    dataElement: 'label'
                }),
                ' ',
                span({
                    dataElement: 'elapsed',
                    class: 'kb-elapsed-time'
                })
            ]),
            div({
                class: 'row',
                dataElement: 'job_not_found'
            }, [
                span({
                    dataElement: 'message'
                })
            ]),
            div({
                class: 'row',
                dataElement: 'queue'
            }, [
                span({
                    dataElement: 'label'
                }, 'Queue'),
                ' ',
                span({
                    dataElement: 'elapsed',
                    class: 'kb-elapsed-time'
                }),
                span({
                    dataElement: 'position'
                }, [
                    span({
                        dataElement: 'label'
                    }),
                    span({
                        dataElement: 'number'
                    })
                ])
            ]),
            div({
                class: 'row',
                dataElement: 'run'
            }, [
                span({
                    dataElement: 'label'
                }, 'Run'),
                ' ',
                span({
                    dataElement: 'elapsed',
                    class: 'kb-elapsed-time'
                })
            ]),
            div({
                class: 'row',
                dataElement: 'finish'
            }, [
                span('Finished with '),
                span({
                    dataElement: 'state'
                }),
                ' on ',
                span({
                    dataElement: 'time'
                }),
                ' (',
                span({
                    dataElement: 'elapsed'
                }),
                ' ago)'
            ])
        ]);
    }

    /**
     * Initializes this state viewer widget.
     * Inits the internal viewModel as well.
     * @param {object} config - the config passed to this widget (not used, but in the format)
     */
    function factory(config) {
        let container, ui, listeners = [],
            jobState = null,
            runtime = Runtime.make(),
            listeningForJob = false,
            jobId,
            parentJobId;

        let viewModel = {
            lastUpdated: {
                elapsed: null,
                time: null
            },
            launch: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                label: null,
                elapsed: null
            },
            queue: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                active: null,
                label: null,
                elapsed: null,
                position: {
                    label: null,
                    number: null
                }
            },
            run: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                active: null,
                label: null,
                elapsed: null
            },
            finish: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                active: null,
                state: null,
                time: null,
                elapsed: null
            },
            job_not_found: {
                _attrib: {
                    hidden: true,
                    style: {}
                },
                message: div([
                    p([
                        'This job was not found, or may not have been registered with this Narrative.'
                    ]),
                    p([
                        'You will not be able to inspect the job status or view the job log'
                    ])
                ])
            }
        };

        /**
         * Starts listening for job updates and requests the most recent job state for this app cell.
         */
        function startJobUpdates() {
            if (listeningForJob) {
                return;
            }
            runtime.bus().emit('request-job-update', {
                jobId: jobId,
                parentJobId: parentJobId
            });
            listeningForJob = true;
        }

        /**
         * Stops listening for job updates
         */
        function stopJobUpdates() {
            listeningForJob = false;
        }

        /**
         * If the job doesn't exist, then we need to set a different job state.
         * We don't care what the message is, but it's passed here anyway.
         * @param {object} message
         */
        function handleJobDoesNotExistUpdate(message) {
            jobState = {
                job_state: 'does_not_exist'
            };
        }

        /**
         * Called when the job-status message is received.
         * This parses the job status message. Takes into account both NJS and EE2 style messages.
         * @param {object} message
         */
        function handleJobStatusUpdate(message) {
            jobState = message.jobState;
            switch (jobState.status) {
                case 'queued':
                case 'created':
                case 'estimating':
                case 'running':
                    startJobUpdates();
                    break;
                case 'completed':
                case 'error':
                case 'terminated':
                    stopJobUpdates();
                    break;
                default:
                    stopJobUpdates();
                    console.error('Unknown job status', jobState.status, message);
                    throw new Error('Unknown job status ' + jobState.status);
            }
        }

        /**
         * Sets up handlers and listeners for job status updates.
         * Listens on the jobId channel for these messages:
         *  - job-status -> respond to job update
         *  - job-canceled -> no-op
         *  - job-does-not-exist -> respond to the not found exception
         */
        function listenForJobStatus() {
            let ev = runtime.bus().listen({
                channel: {
                    jobId: jobId
                },
                key: {
                    type: 'job-status'
                },
                handle: handleJobStatusUpdate
            });
            listeners.push(ev);

            ev = runtime.bus().listen({
                channel: {
                    jobId: jobId
                },
                key: {
                    type: 'job-canceled'
                },
                handle: () => {}
            });
            listeners.push(ev);

            ev = runtime.bus().listen({
                channel: {
                    jobId: jobId
                },
                key: {
                    type: 'job-does-not-exist'
                },
                handle: handleJobDoesNotExistUpdate
            });
            listeners.push(ev);
        }

        /**
         * Removes job status update listeners.
         */
        function stopListeningForJobStatus() {
            runtime.bus().removeListeners(listeners);
        }

        function start(arg) {
            return Promise.try(() => {
                if (container) {
                    return detach();
                }
            })
            .then(() => {
                return Promise.try(() => {
                    container = arg.node;
                    ui = UI.make({ node: container });

                    container.innerHTML = renderRunStats();

                    jobId = arg.jobId;
                    parentJobId = arg.parentJobId ? arg.parentJobId : null;

                    listeners.push(runtime.bus().on('clock-tick', () => {
                        updateRunStats(ui, viewModel, jobState);
                    }));

                    listenForJobStatus();

                    // request a new job status update from the kernel on start
                    runtime.bus().emit('request-job-status', {
                        jobId: jobId,
                        parentJobId: parentJobId
                    });
                    listeningForJob = true;

                    ui.updateFromViewModel(viewModel);
                });
            });
        }

        /**
         * Lifecycle method.
         * On widget stop, this removes all listeners for job status to clean itself up.
         */
        function stop() {
            return Promise.try(function() {
                stopListeningForJobStatus();
            });
        }

        /**
         * Lifecycle method.
         * When told to detach, this widget cleans itself up with a call to stop(),
         * then removes itself from the DOM.
         */
        function detach() {
            return stop()
                .then(() => {
                    container.innerHTML = '';
                });
        }

        return {
            start: start,
            stop: stop,
            detach: detach
        };
    }

    return {
        make: function(config) {
            return factory(config);
        }
    };

});
<|endoftext|>


<commit_before>import styled from 'styled-components'
import SlideMenu from 'react-burger-menu/lib/menus/slide'
import {theme} from 'tocco-ui'

const calcViewportHeight = () => {
  const vh = document.documentElement.clientHeight * 0.01
  document.documentElement.style.setProperty('--vh', `${vh}px`)
}

calcViewportHeight()

window.addEventListener('resize', () => calcViewportHeight())
window.addEventListener('orientationchange', () => calcViewportHeight())

export const StyledWrapper = styled.div`
  display: grid;
  grid-template:
    'header header' auto
    'menu content' 1fr / auto 1fr;
  background-color: ${theme.color('paper')};
  height: calc(var(--vh, 1vh) * 100);

  .bm-burger-button button:focus {
    outline: 0;
  }
`

export const StyledContent = styled.div`
  grid-area: content;
  background-color: ${theme.color('backgroundBody')};
  overflow: hidden;
  display: flex;
`
export const StyledMenu = styled(SlideMenu)`
  grid-area: menu;
`

export const burgerMenuStyles = {
  bmBurgerButton: {
    position: 'absolute',
    outline: '0',
    width: '24px',
    height: '24px',
    left: '17px',
    top: '10px'
  },
  bmBurgerBarsHover: {
    background: '#a90000'
  },
  bmMenuWrap: {
    position: 'fixed',
    height: '100%',
    width: '16%',
    minWidth: '350px'
  },
  bmMenu: {
    background: '#fff',
    padding: '0 0 3rem 0',
    fontSize: '1.15em',
    overflow: 'hidden'
  },
  bmMorphShape: {
    fill: '#373a47'
  },
  bmItemList: {
    color: '#b8b7ad',
    height: '100%'
  },
  bmItem: {
    display: 'inline-block'
  },
  bmOverlay: {
    background: 'rgba(0, 0, 0, 0.3)'
  }
}
<commit_msg>fix(admin): Hide burger menu button behind legacy modal window mask<commit_after>import styled from 'styled-components'
import SlideMenu from 'react-burger-menu/lib/menus/slide'
import {theme} from 'tocco-ui'

const calcViewportHeight = () => {
  const vh = document.documentElement.clientHeight * 0.01
  document.documentElement.style.setProperty('--vh', `${vh}px`)
}

calcViewportHeight()

window.addEventListener('resize', () => calcViewportHeight())
window.addEventListener('orientationchange', () => calcViewportHeight())

export const StyledWrapper = styled.div`
  display: grid;
  grid-template:
    'header header' auto
    'menu content' 1fr / auto 1fr;
  background-color: ${theme.color('paper')};
  height: calc(var(--vh, 1vh) * 100);

  .bm-burger-button button:focus {
    outline: 0;
  }
`

export const StyledContent = styled.div`
  grid-area: content;
  background-color: ${theme.color('backgroundBody')};
  overflow: hidden;
  display: flex;
`
export const StyledMenu = styled(SlideMenu)`
  grid-area: menu;
`

export const burgerMenuStyles = {
  bmBurgerButton: {
    position: 'absolute',
    outline: '0',
    width: '24px',
    height: '24px',
    left: '17px',
    top: '10px',
    // Default z-index of button is 1000 which is higher than the mask of the legacy action modal windows (not good).
    // It seems like it doesn't need to be higher than 1, therefore we set it to 1.
    zIndex: 1
  },
  bmBurgerBarsHover: {
    background: '#a90000'
  },
  bmMenuWrap: {
    position: 'fixed',
    height: '100%',
    width: '16%',
    minWidth: '350px'
  },
  bmMenu: {
    background: '#fff',
    padding: '0 0 3rem 0',
    fontSize: '1.15em',
    overflow: 'hidden'
  },
  bmMorphShape: {
    fill: '#373a47'
  },
  bmItemList: {
    color: '#b8b7ad',
    height: '100%'
  },
  bmItem: {
    display: 'inline-block'
  },
  bmOverlay: {
    background: 'rgba(0, 0, 0, 0.3)'
  }
}
<|endoftext|>