<commit_before>// @flow
import {createConnect} from 'react-redux/src/connect/connect'
import defaultSelectorFactory from 'react-redux/src/connect/selectorFactory'

import typeof {connect} from 'react-redux'

function selectorFactory(dispatch, factoryOptions) {
  const selector = defaultSelectorFactory(dispatch, factoryOptions)
  let cachedResult
  const pausableSelector = function(state, ownProps) {
    if (ownProps.isActive || cachedResult === undefined) {
      cachedResult = selector(state, ownProps)
    }
    return cachedResult
  }
  return pausableSelector
}

const pausableConnect: connect = createConnect({selectorFactory})

export default pausableConnect
<commit_msg>Always pass isActive prop through pausableConnect<commit_after>// @flow
import {createConnect} from 'react-redux/src/connect/connect'
import defaultSelectorFactory from 'react-redux/src/connect/selectorFactory'

import typeof {connect} from 'react-redux'

function selectorFactory(dispatch, factoryOptions) {
  const selector = defaultSelectorFactory(dispatch, factoryOptions)
  let cachedResult
  const pausableSelector = function(state, ownProps) {
    if (ownProps.isActive || cachedResult === undefined) {
      cachedResult = selector(state, ownProps)
      cachedResult.isActive = ownProps.isActive
    } else if (cachedResult.isActive !== ownProps.isActive) {
      cachedResult = {...cachedResult, isActive: ownProps.isActive}
    }
    return cachedResult
  }
  return pausableSelector
}

const pausableConnect: connect = createConnect({selectorFactory})

export default pausableConnect
<|endoftext|>


<commit_before>'use strict';

var shufflifyApp = angular.module('shufflifyApp');

shufflifyApp.controller('MainCtrl', ["$scope", "$http", "$location", "SpotifySources", "SpotifyLibrary", "SpotifyPlaylist", "AccessToken", "$modal", "$q", function ($scope, $http, $location, SpotifySources, SpotifyLibrary, SpotifyPlaylist, AccessToken, $modal, $q) {
	$scope.host = $location.host();
	$scope.localhost = $scope.host == '127.0.0.1' || $scope.host == 'localhost';

	$scope.spotifyData = {
		sources: [],
		library: [],
		playlists: []
	};

	$scope.selectionData = {
		sources: [],
		totalSongsInSources: 0,
		destination: null,
		totalSongsInDestination: 0,
		maxSongsDestination: null,
		ensureUnique: false
	};

	$scope.newPlaylistName = "";
	$scope.profile = null;
	$scope.spotifyDataStartedLoading = false;

	$scope.$on('oauth:logout', forgetProfile);
	$scope.$on('oauth:expired', forgetProfile);
	$scope.$on('oauth:denied', forgetProfile);
	$scope.$on('oauth:profile', loadProfile);

	function loadProfile(event, profile) {
		$http.defaults.headers.common.Authorization = 'Bearer ' + AccessToken.get().access_token;
		$scope.profile = profile;
		$scope.loadSpotifyData();
	}

	function forgetProfile() {
		$scope.profile = null;
		$scope.spotifyDataStartedLoading = false;
		$http.defaults.headers.common.Authorization = null;
		AccessToken.destroy();
	}

	$scope.loadSpotifyData = function () {
		if ($scope.spotifyDataStartedLoading) {
			return;
		}
		$scope.spotifyDataStartedLoading = true;
		SpotifySources.get().then(function (sources) {
			$scope.spotifyData.sources = sources.merged;
			$scope.spotifyData.library = sources.library;
			$scope.spotifyData.playlists = sources.playlists;
		});
	};

	$scope.$watchCollection('selectionData.sources', function () {
		var total = 0;

		$scope.selectionData.sources.forEach(function (source) {
			total += source.total;
		});

		$scope.selectionData.totalSongsInSources = total;
	});

	$scope.$watchCollection("[selectionData.totalSongsInSources, selectionData.maxSongsDestination]", function () {
		var maxSongsDestination = $scope.selectionData.maxSongsDestination;
		var totalSongsInSources = $scope.selectionData.totalSongsInSources;

		if ((!maxSongsDestination && maxSongsDestination !== 0) || maxSongsDestination < 0) {
			$scope.selectionData.totalSongsInDestination = totalSongsInSources;
		} else {
			$scope.selectionData.totalSongsInDestination = Math.min(maxSongsDestination, totalSongsInSources);
		}
	});

	$scope.selectAllSources = function () {
		$scope.selectionData.sources = angular.extend($scope.spotifyData.sources);
	};

	$scope.clearSourcesSelection = function () {
		$scope.selectionData.sources = undefined;
	};

	var addPlaylistDialogCtrl = ["$scope", "$modalInstance", "selectionData", function ($scope, $modalInstance, selectionData) {
		$scope.selectionData = selectionData;
		$scope.playlistInfo = {
			name: null
		};

		$scope.ok = function () {
			SpotifyPlaylist.addPlaylist($scope.playlistInfo.name).then(function (playlist) {
				$modalInstance.close(playlist);
			});
		};

		$scope.cancel = function () {
			$modalInstance.dismiss('cancel');
		};
	}];

	$scope.addPlaylist = function (name) {
		var addPlaylistDialog = $modal.open({
			templateUrl: "views/add_playlist.html",
			controller: addPlaylistDialogCtrl,
			windowClass: "add-playlist-dialog",
			resolve: {
				selectionData: function () {
					return $scope.selectionData;
				}
			}
		});

		addPlaylistDialog.result.then(function (playlist) {
			$scope.selectionData.destination = playlist;
		});
	};

	var confirmDialogCtrl = ["$scope", "$modalInstance", "selectionData", function ($scope, $modalInstance, selectionData) {
		$scope.selectionData = selectionData;

		$scope.ok = function () {
			$modalInstance.close();
		};

		$scope.cancel = function () {
			$modalInstance.dismiss('cancel');
		};
	}];

	var progressDialogCtrl = ["$scope", "$modalInstance", "selectionData", function ($scope, $modalInstance, selectionData) {
		$scope.selectionData = selectionData;

		$scope.progressData = {
			total_songs_to_read: $scope.selectionData.ensureUnique ? $scope.selectionData.totalSongsInSources : $scope.selectionData.totalSongsInDestination,
			total_songs_to_write: $scope.selectionData.totalSongsInDestination,
			num_tracks_read: 0,
			num_tracks_written: 0,
			percent_tracks_read: 0,
			percent_tracks_written: 0,
			finished_read: false,
			finished_write: false,
			write_error: null,
			read_error: null
		};

		$scope.$watchCollection("[progressData.num_tracks_read, progressData.total_songs_to_read]", function () {
			$scope.progressData.percent_tracks_read =
					$scope.progressData.num_tracks_read / $scope.progressData.total_songs_to_read * 100;
		});

		$scope.$watchCollection("[progressData.num_tracks_written, progressData.total_songs_to_write]", function () {
			$scope.progressData.percent_tracks_written =
					$scope.progressData.num_tracks_written / $scope.progressData.total_songs_to_write * 100;
		});

		$scope.$on("progress:tracks_read", function (event, delta) {
			$scope.progressData.num_tracks_read += delta;
		});

		$scope.$on("progress:finished_read", function () {
			$scope.progressData.finished_read = true;
		});

		$scope.$on("progress:tracks_written", function (event, delta) {
			$scope.progressData.num_tracks_written += delta;
		});

		$scope.$on("progress:finished_write", function () {
			$scope.progressData.finished_write = true;
		});

		$scope.$on("progress:read_error", function (event, error) {
			$scope.progressData.read_error = error;
		});

		$scope.$on("progress:write_error", function (event, error) {
			$scope.progressData.write_error = error;
		});

		// The total songs to write can be updated by duplicate removal.
		$scope.$on("progress:new_total_write_count", function (event, total_songs_to_write) {
			$scope.progressData.total_songs_to_write = total_songs_to_write;
		});

		$scope.ok = function () {
			$modalInstance.close();
		};
	}];

	function getSomeTracksFromSources() {
		var songs = [];
		var songNum = 0;

		$scope.selectionData.sources.forEach(function (source) {
			for (var i = 0; i < source.total; ++i) {
				songs.push(songNum++);
			}
		});

		var chosen_songs = window.chance.pick(songs, $scope.selectionData.totalSongsInDestination);
		// Sort by integers
		chosen_songs.sort(function (a, b) { return a - b; });

		var total_chosen_songs = chosen_songs.length;
		var current_offset = 0;
		var i = 0;

		var promises = [];

		$scope.selectionData.sources.forEach(function (source) {
			var source_total = source.total;
			var source_songs = [];

			while (i < total_chosen_songs) {
				var current_song_pos_in_source = chosen_songs[i] - current_offset;

				// If the song we're currently looking at belongs to a different source, move on
				if (current_song_pos_in_source >= source_total) {
					break;
				}
				// Else, add it to the source_songs collection
				else {
					source_songs.push(current_song_pos_in_source);
					++i;
				}
			}

			current_offset += source_total;

			promises.push(SpotifySources.getSourceTracks(source, source_songs).then(
					null,
					null,
					function (progress) {
						$scope.$broadcast("progress:tracks_read", progress.delta);
					}));
		});

		return promises;
	}

	function getAllTracksFromSources() {
		var promises = [];

		$scope.selectionData.sources.forEach(function (source) {
			promises.push(SpotifySources.getSourceTracks(source).then(
					null,
					null,
					function (progress) {
						$scope.$broadcast("progress:tracks_read", progress.delta);
					}));
		});

		return promises;
	}

	$scope.showConfirmDialog = function() {
		var confirmDialog = $modal.open({
			templateUrl: "views/confirm.html",
			controller: confirmDialogCtrl,
			windowClass: "confirm-dialog",
			resolve: {
				selectionData: function () {
					return $scope.selectionData;
				}
			}
		});

		confirmDialog.result.then(function () {
			var progressDialog = $modal.open({
				templateUrl: "views/progress.html",
				controller: progressDialogCtrl,
				windowClass: "progress-dialog",
				backdrop: "static",
				keyboard: false,
				scope: $scope,
				resolve: {
					selectionData: function () {
						return $scope.selectionData;
					}
				}
			});

			var destination_contains_all =
					$scope.selectionData.totalSongsInSources == $scope.selectionData.totalSongsInDestination;
			var ensure_unique = $scope.selectionData.ensureUnique;
			var promises = null;
			if (destination_contains_all || ensure_unique) {
				promises = getAllTracksFromSources();
			} else {
				promises = getSomeTracksFromSources();
			}

			$q.all(promises).then(
					// Success
					function (result) {
						$scope.$broadcast("progress:finished_read");

						var track_uris = [];

						result.forEach(function (tracks) {
							track_uris = track_uris.concat(tracks);
						});

						// Filter strange results out
						track_uris = track_uris.filter(function (track) {
							return track != "spotify:track:null";
						});

						if (ensure_unique) {
							// Remove duplicates by sorting and deleting those elements
							// that are preceded by an element with the same value.
							// From: http://stackoverflow.com/a/9229821/441265
							track_uris = track_uris.sort().filter(function(item, pos) {
								return !pos || item != track_uris[pos - 1];
							});

							$scope.$broadcast("progress:new_total_write_count", track_uris.length);
						}

						// (Re)shuffle to introduce randomness
						if (track_uris.length > $scope.selectionData.totalSongsInDestination) {
							track_uris = window.chance.pick(track_uris, $scope.selectionData.totalSongsInDestination);
						} else {
							track_uris = window.chance.shuffle(track_uris);
						}

						SpotifyPlaylist.setPlaylistTracks($scope.selectionData.destination.id, track_uris).then(
								// Success
								function (result) {
									$scope.$broadcast("progress:finished_write");
								},
								function (error) {
									console.log("Write error: " + error);
									$scope.$broadcast("progress:write_error", error);
								},
								function (progress) {
									$scope.$broadcast("progress:tracks_written", progress.delta);
								}
						);

						console.log("Success");
					},
					// Error
					function (error) {
						console.log("Read error: " + error);
						$scope.$broadcast("progress:read_error", error);
					},
					function (progress) {
						// Apparently never called??
					}
			);
		});
	};
}]);
<commit_msg>Fix write percentage calculation error.<commit_after>'use strict';

var shufflifyApp = angular.module('shufflifyApp');

shufflifyApp.controller('MainCtrl', ["$scope", "$http", "$location", "SpotifySources", "SpotifyLibrary", "SpotifyPlaylist", "AccessToken", "$modal", "$q", function ($scope, $http, $location, SpotifySources, SpotifyLibrary, SpotifyPlaylist, AccessToken, $modal, $q) {
	$scope.host = $location.host();
	$scope.localhost = $scope.host == '127.0.0.1' || $scope.host == 'localhost';

	$scope.spotifyData = {
		sources: [],
		library: [],
		playlists: []
	};

	$scope.selectionData = {
		sources: [],
		totalSongsInSources: 0,
		destination: null,
		totalSongsInDestination: 0,
		maxSongsDestination: null,
		ensureUnique: false
	};

	$scope.newPlaylistName = "";
	$scope.profile = null;
	$scope.spotifyDataStartedLoading = false;

	$scope.$on('oauth:logout', forgetProfile);
	$scope.$on('oauth:expired', forgetProfile);
	$scope.$on('oauth:denied', forgetProfile);
	$scope.$on('oauth:profile', loadProfile);

	function loadProfile(event, profile) {
		$http.defaults.headers.common.Authorization = 'Bearer ' + AccessToken.get().access_token;
		$scope.profile = profile;
		$scope.loadSpotifyData();
	}

	function forgetProfile() {
		$scope.profile = null;
		$scope.spotifyDataStartedLoading = false;
		$http.defaults.headers.common.Authorization = null;
		AccessToken.destroy();
	}

	$scope.loadSpotifyData = function () {
		if ($scope.spotifyDataStartedLoading) {
			return;
		}
		$scope.spotifyDataStartedLoading = true;
		SpotifySources.get().then(function (sources) {
			$scope.spotifyData.sources = sources.merged;
			$scope.spotifyData.library = sources.library;
			$scope.spotifyData.playlists = sources.playlists;
		});
	};

	$scope.$watchCollection('selectionData.sources', function () {
		var total = 0;

		$scope.selectionData.sources.forEach(function (source) {
			total += source.total;
		});

		$scope.selectionData.totalSongsInSources = total;
	});

	$scope.$watchCollection("[selectionData.totalSongsInSources, selectionData.maxSongsDestination]", function () {
		var maxSongsDestination = $scope.selectionData.maxSongsDestination;
		var totalSongsInSources = $scope.selectionData.totalSongsInSources;

		if ((!maxSongsDestination && maxSongsDestination !== 0) || maxSongsDestination < 0) {
			$scope.selectionData.totalSongsInDestination = totalSongsInSources;
		} else {
			$scope.selectionData.totalSongsInDestination = Math.min(maxSongsDestination, totalSongsInSources);
		}
	});

	$scope.selectAllSources = function () {
		$scope.selectionData.sources = angular.extend($scope.spotifyData.sources);
	};

	$scope.clearSourcesSelection = function () {
		$scope.selectionData.sources = undefined;
	};

	var addPlaylistDialogCtrl = ["$scope", "$modalInstance", "selectionData", function ($scope, $modalInstance, selectionData) {
		$scope.selectionData = selectionData;
		$scope.playlistInfo = {
			name: null
		};

		$scope.ok = function () {
			SpotifyPlaylist.addPlaylist($scope.playlistInfo.name).then(function (playlist) {
				$modalInstance.close(playlist);
			});
		};

		$scope.cancel = function () {
			$modalInstance.dismiss('cancel');
		};
	}];

	$scope.addPlaylist = function (name) {
		var addPlaylistDialog = $modal.open({
			templateUrl: "views/add_playlist.html",
			controller: addPlaylistDialogCtrl,
			windowClass: "add-playlist-dialog",
			resolve: {
				selectionData: function () {
					return $scope.selectionData;
				}
			}
		});

		addPlaylistDialog.result.then(function (playlist) {
			$scope.selectionData.destination = playlist;
		});
	};

	var confirmDialogCtrl = ["$scope", "$modalInstance", "selectionData", function ($scope, $modalInstance, selectionData) {
		$scope.selectionData = selectionData;

		$scope.ok = function () {
			$modalInstance.close();
		};

		$scope.cancel = function () {
			$modalInstance.dismiss('cancel');
		};
	}];

	var progressDialogCtrl = ["$scope", "$modalInstance", "selectionData", function ($scope, $modalInstance, selectionData) {
		$scope.selectionData = selectionData;

		$scope.progressData = {
			total_songs_to_read: $scope.selectionData.ensureUnique ? $scope.selectionData.totalSongsInSources : $scope.selectionData.totalSongsInDestination,
			total_songs_to_write: $scope.selectionData.totalSongsInDestination,
			num_tracks_read: 0,
			num_tracks_written: 0,
			percent_tracks_read: 0,
			percent_tracks_written: 0,
			finished_read: false,
			finished_write: false,
			write_error: null,
			read_error: null
		};

		$scope.$watchCollection("[progressData.num_tracks_read, progressData.total_songs_to_read]", function () {
			$scope.progressData.percent_tracks_read =
					$scope.progressData.num_tracks_read / $scope.progressData.total_songs_to_read * 100;
		});

		$scope.$watchCollection("[progressData.num_tracks_written, progressData.total_songs_to_write]", function () {
			$scope.progressData.percent_tracks_written =
					$scope.progressData.num_tracks_written / $scope.progressData.total_songs_to_write * 100;
		});

		$scope.$on("progress:tracks_read", function (event, delta) {
			$scope.progressData.num_tracks_read += delta;
		});

		$scope.$on("progress:finished_read", function () {
			$scope.progressData.finished_read = true;
		});

		$scope.$on("progress:tracks_written", function (event, delta) {
			$scope.progressData.num_tracks_written += delta;
		});

		$scope.$on("progress:finished_write", function () {
			$scope.progressData.finished_write = true;
		});

		$scope.$on("progress:read_error", function (event, error) {
			$scope.progressData.read_error = error;
		});

		$scope.$on("progress:write_error", function (event, error) {
			$scope.progressData.write_error = error;
		});

		// The total songs to write can be updated by duplicate removal.
		$scope.$on("progress:new_total_write_count", function (event, total_songs_to_write) {
			console.log("Old total write count: " + $scope.progressData.total_songs_to_write);
			console.log("New total write count: " + total_songs_to_write);
			$scope.progressData.total_songs_to_write = total_songs_to_write;
		});

		$scope.ok = function () {
			$modalInstance.close();
		};
	}];

	function getSomeTracksFromSources() {
		var songs = [];
		var songNum = 0;

		$scope.selectionData.sources.forEach(function (source) {
			for (var i = 0; i < source.total; ++i) {
				songs.push(songNum++);
			}
		});

		var chosen_songs = window.chance.pick(songs, $scope.selectionData.totalSongsInDestination);
		// Sort by integers
		chosen_songs.sort(function (a, b) { return a - b; });

		var total_chosen_songs = chosen_songs.length;
		var current_offset = 0;
		var i = 0;

		var promises = [];

		$scope.selectionData.sources.forEach(function (source) {
			var source_total = source.total;
			var source_songs = [];

			while (i < total_chosen_songs) {
				var current_song_pos_in_source = chosen_songs[i] - current_offset;

				// If the song we're currently looking at belongs to a different source, move on
				if (current_song_pos_in_source >= source_total) {
					break;
				}
				// Else, add it to the source_songs collection
				else {
					source_songs.push(current_song_pos_in_source);
					++i;
				}
			}

			current_offset += source_total;

			promises.push(SpotifySources.getSourceTracks(source, source_songs).then(
					null,
					null,
					function (progress) {
						$scope.$broadcast("progress:tracks_read", progress.delta);
					}));
		});

		return promises;
	}

	function getAllTracksFromSources() {
		var promises = [];

		$scope.selectionData.sources.forEach(function (source) {
			promises.push(SpotifySources.getSourceTracks(source).then(
					null,
					null,
					function (progress) {
						$scope.$broadcast("progress:tracks_read", progress.delta);
					}));
		});

		return promises;
	}

	$scope.showConfirmDialog = function() {
		var confirmDialog = $modal.open({
			templateUrl: "views/confirm.html",
			controller: confirmDialogCtrl,
			windowClass: "confirm-dialog",
			resolve: {
				selectionData: function () {
					return $scope.selectionData;
				}
			}
		});

		confirmDialog.result.then(function () {
			var progressDialog = $modal.open({
				templateUrl: "views/progress.html",
				controller: progressDialogCtrl,
				windowClass: "progress-dialog",
				backdrop: "static",
				keyboard: false,
				scope: $scope,
				resolve: {
					selectionData: function () {
						return $scope.selectionData;
					}
				}
			});

			var destination_contains_all =
					$scope.selectionData.totalSongsInSources == $scope.selectionData.totalSongsInDestination;
			var ensure_unique = $scope.selectionData.ensureUnique;
			var promises = null;
			if (destination_contains_all || ensure_unique) {
				promises = getAllTracksFromSources();
			} else {
				promises = getSomeTracksFromSources();
			}

			$q.all(promises).then(
					// Success
					function (result) {
						$scope.$broadcast("progress:finished_read");

						var track_uris = [];

						result.forEach(function (tracks) {
							track_uris = track_uris.concat(tracks);
						});

						// Filter strange results out
						track_uris = track_uris.filter(function (track) {
							return track != "spotify:track:null";
						});

						if (ensure_unique) {
							// Remove duplicates by sorting and deleting those elements
							// that are preceded by an element with the same value.
							// From: http://stackoverflow.com/a/9229821/441265
							track_uris = track_uris.sort().filter(function(item, pos) {
								return !pos || item != track_uris[pos - 1];
							});

							$scope.$broadcast("progress:new_total_write_count", track_uris.length);
						}

						// (Re)shuffle to introduce randomness
						if (track_uris.length > $scope.selectionData.totalSongsInDestination) {
							track_uris = window.chance.pick(track_uris, $scope.selectionData.totalSongsInDestination);
							$scope.$broadcast("progress:new_total_write_count", track_uris.length);
						} else {
							track_uris = window.chance.shuffle(track_uris);
						}

						SpotifyPlaylist.setPlaylistTracks($scope.selectionData.destination.id, track_uris).then(
								// Success
								function (result) {
									$scope.$broadcast("progress:finished_write");
								},
								function (error) {
									console.log("Write error: " + error);
									$scope.$broadcast("progress:write_error", error);
								},
								function (progress) {
									$scope.$broadcast("progress:tracks_written", progress.delta);
								}
						);

						console.log("Success");
					},
					// Error
					function (error) {
						console.log("Read error: " + error);
						$scope.$broadcast("progress:read_error", error);
					},
					function (progress) {
						// Apparently never called??
					}
			);
		});
	};
}]);
<|endoftext|>


<commit_before>function scatterChart() {
  /*
   * Based on Mike Bostock's system for creating reusable charts, described
   * here: http://bost.ocks.org/mike/chart/
   *
   * My particular instance is based on the example used in that page
   * located here: http://bost.ocks.org/mike/chart/time-series-chart.js
   *
   * I added a Y axis, multiple lines.
   *
   * Tooltip found here: http://stackoverflow.com/questions/34886070/multiseries-line-chart-with-mouseover-tooltip
   */
  var margin = {
    top : 20,
    right : 20,
    bottom : 30,
    left : 50
  };
  var width = 760;
  var height = 120;
  var xValue = function(d) {
    return +d[0];
  };
  var yValue = function(d) {
    return +d[1];
  };
  //var xScale = d3.scale.linear();
  var xScale = d3.time.scale();
  //var yScale = d3.scale.linear();
  var yScale = d3.scale.log();
  var xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(6, 0);
  var yAxis = d3.svg.axis().scale(yScale).orient("left").tickSize(6, 0).ticks(5).tickFormat(function (d) {
    return yScale.tickFormat(10, d3.format(",d"))(d);
  });

  //var color = d3.scaleOrdinal(d3.schemeCategory10);
  var color = d3.scale.category10();

  //NEW
  //var area = d3.svg.area().x(X).y1(Y);
  var line = d3.svg.line()
      .interpolate("step-before")
      .x(X)
      .y(Y)
      .defined(function (d) { return d[1] !== null; });//This allows the line to break at null values.

  var xDomain = [];
  //leave empty. First time data are loaded, it will calculate the full x domain.
  var fullxDomain = [];
  var fullyDomain = [];

  function chart(selection) {
    selection.each(function(dataArray) {

      if (!xDomain.length) {//if xDomain hasn't been set yet, set it to the full domain.
        //xDomain = d3.extent(dataArray[0], function(d) { return d[0]; });
        xDomain = setFullxDomain();
      }

      // Update the full x & y domain.
      fullxDomain = setFullxDomain();
      fullyDomain = setFullyDomain();

      // Update the x-scale.
      xScale.domain(xDomain)//don't change the xDomain. Why not fullxDomain????
      .range([0, width - margin.left - margin.right]);

      // Update the y-scale.
      yScale
      //.domain([0, d3.max(dataArray[0], function(d) { return d[1]; })])//need to set extent in a new way, since some lines will have a different max.
      .domain(fullyDomain).range([height - margin.top - margin.bottom, 0]);

      // Select the svg element, if it exists.
      var svg = d3.select(this).selectAll("svg").data([dataArray]);
      //bind data to svg if it exists. IF it doesn't, then a new one will be made in the next line with the .enter(); WE only want one graph, so we should only have one element in the array.
      // Otherwise, create the skeletal chart.
      var gEnter = svg.enter().append("svg").append("g");
      //took out when I took out .data() above.

      var lineGroup = gEnter.append("g").attr("class", "lineGroup");
      var lineEnter = lineGroup.selectAll("path").data(dataArray).enter().append("path").attr("class", "line");
      //.attr("d", line);//don't draw the line yet. update the size of the svg first.

      gEnter.append("g").attr("class", "x axis");//is this name okay? It has a space!
      gEnter.append("g").attr("class", "y axis");

      var titleGroup = gEnter.append("g").attr("class", "titleGroup");
      titleGroup.append("svg:text").attr("class", "Title").text("Stream Hydrograph");
      titleGroup.append("svg:text").attr("class", "subtitle").attr("dy", "1em");
      titleGroup.append("svg:text").attr("class", "subtitle2").attr("dy", "2.2em");

      // Update the outer dimensions.
      svg.attr("width", width).attr("height", height);

      // Update the inner dimensions.
      var g = svg.select("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      // Update the lines.
      g.selectAll(".line").attr("d", line).attr("stroke", function(d, i) {return color(i);});

      // Update the x-axis.
      g.select(".x.axis").attr("transform", "translate(0," + yScale.range()[0] + ")").call(xAxis).on("click", myClickFunction);

      // Update the y-axis.
      g.select(".y.axis").attr("transform", "translate(0," + xScale.range()[0] + ")").call(yAxis).on("click", myRClickFunction);

      //title block
      // Update the title.
      g.select(".titleGroup").attr("transform", "translate(10,0)");
      g.select(".subtitle").text(dataArray.length + " lines");
      g.select(".subtitle2").text(dataArray[0].length + " measurements per line")
      //title.on("click", myRClickFunction);

      //***************Tooltip Code ***************

// append a g for all the mouse over nonsense
      //TODO: mouse has trouble when a site has no data and part of the dataArray is null.
      var mouseG = g.append("g")
          .attr("class", "mouse-over-effects");

// this is the vertical line
      mouseG.append("path")
          .attr("class", "mouse-line")
          .style("stroke", "black")
          .style("stroke-width", "1px")
          .style("opacity", "0");

// keep a reference to all our lines
      var lines = document.getElementsByClassName('line');

// here's a g for each circle and text on the line
      var mousePerLine = mouseG.selectAll('.mouse-per-line')
          .data(dataArray)
          .enter()
          .append("g")
          .attr("class", "mouse-per-line");

// the circle
      mousePerLine.append("circle")
          .attr("r", 7)
          .style("stroke", function(d) {
            return color(d.name);
          })
          .style("fill", "none")
          .style("stroke-width", "1px")
          .style("opacity", "0");

// the text
      mousePerLine.append("text")
          .attr("transform", "translate(10,3)");

// rect to capture mouse movements
      mouseG.append('svg:rect')
          .attr('width', width)
          .attr('height', height)
          .attr('fill', 'none')
          .attr('pointer-events', 'all')
          .on('mouseout', function() { // on mouse out hide line, circles and text
            d3.select(".mouse-line")
                .style("opacity", "0");
            d3.selectAll(".mouse-per-line circle")
                .style("opacity", "0");
            d3.selectAll(".mouse-per-line text")
                .style("opacity", "0");
          })
          .on('mouseover', function() { // on mouse in show line, circles and text
            d3.select(".mouse-line")
                .style("opacity", "1");
            d3.selectAll(".mouse-per-line circle")
                .style("opacity", "1");
            d3.selectAll(".mouse-per-line text")
                .style("opacity", "1");
          })
          .on('mousemove', function() { // mouse moving over canvas
            var mouse = d3.mouse(this);

            // move the vertical line
            d3.select(".mouse-line")
                .attr("d", function() {
                  var d = "M" + mouse[0] + "," + height;
                  d += " " + mouse[0] + "," + 0;
                  return d;
                });

            // position the circle and text
            d3.selectAll(".mouse-per-line")
                .attr("transform", function(d, i) {
                  //console.log(width/mouse[0]);
                  //console.log(d);
                  if(d.length < 1) { return; } //return if empty set.
                  var xDate = xScale.invert(mouse[0]),
                      bisect = d3.bisector(function(d) { return d[0]; }).right;
                  idx = bisect(d[1], xDate); //Sometimes an empty set is in viewModel.dataArray and an error occurs.

                  // since we are use curve fitting we can't relay on finding the points like I had done in my last answer
                  // this conducts a search using some SVG path functions
                  // to find the correct position on the line
                  // from http://bl.ocks.org/duopixel/3824661
                  var beginning = 0,
                      end = lines[i].getTotalLength(),
                      target = null;

                  while (true){
                    target = Math.floor((beginning + end) / 2);
                    pos = lines[i].getPointAtLength(target);
                    if ((target === end || target === beginning) && pos.x !== mouse[0]) {
                      break;
                    }
                    if (pos.x > mouse[0])      end = target;
                    else if (pos.x < mouse[0]) beginning = target;
                    else break; //position found
                  }

                  // update the text with y value
                  d3.select(this).select('text')
                      .text(yScale.invert(pos.y).toFixed(2));

                  // return position
                  return "translate(" + mouse[0] + "," + pos.y +")";
                });
          });

      //***************Tooltip Code ***************

      //NEW click function for handling mouseclicks on an object.
      function myClickFunction() {//just a silly function taken from http://bl.ocks.org/mbostock/1166403
        console.log("click");

        var n = dataArray[0].length - 1, i = Math.floor(Math.random() * n / 2), j = i + Math.floor(Math.random() * n / 2) + 1;
        xDomain = [xValue(dataArray[0][i]), xValue(dataArray[0][j])];
        xScale.domain(xDomain);
        var t = g.transition().duration(750);
        t.select(".x.axis").call(xAxis);
        t.selectAll(".line").attr("d", line);
      }

      function myRClickFunction() {
        xDomain = setFullxDomain();
        console.log("myRClickFunction");
        xScale.domain(xDomain);
        var t = g.transition().duration(750);
        t.select(".x.axis").call(xAxis);
        t.selectAll(".line").attr("d", line);
      }

      function setFullxDomain() {
        //console.log("setFullxDomain");
        //set xmax and xmin to x value in first element in first array.
        var xmax = dataArray[0][0][0];
        var xmin = xmax;
        //console.log("xmax: " + xmax + " xmin: " + xmin);
        //d3.min(dataArray[0], xValue);
        var localxMax = xmax;
        var localxMin = xmax;
        var domain = [];

        //console.log("domain: " + domain);
        //console.log(domain);
        //domain = d3.extent(dataArray[0].map(function(d) {return d.date;}));
        //console.log("new domain: " );
        //console.log(domain);
        //x.domain(d3.extent(data.map(function(d) {return d.date;})));
        for ( i = 0; i < dataArray.length; i++) {
          //loop through each of the arrays.
          //console.log("localxMax before search: " + localxMax);
          localxMax = d3.max(dataArray[i], function(d) {
            return d[0];
          });
          //console.log("localxMax of dataArray[" + i + "] is: " + localxMax);
          if (localxMax > xmax) {
            xmax = localxMax;
          }
          localxMin = d3.min(dataArray[i], function(d) {
            return d[0];
          });
          if (localxMin < xmin) {
            xmin = localxMin;
          }//it will never be smaller than zero.

        }

        domain = [xmin, xmax];
        //console.log(domain);
        return domain;
      }

      function setFullyDomain() {
        //console.log("setFullyDomain");
        //set xmax and xmin to x value in first element in first array.
        var max = dataArray[0][0][1];
        var min = max;
        //console.log("max: " + max + " min: " + min);
        //d3.min(dataArray[0], xValue);
        var localMax = max;
        var localMin = max;
        var domain = [];

        //console.log("y domain: " + domain);
        //console.log(domain);
        //domain = d3.extent(dataArray[0].map(function(d) {return d.date;}));
        //console.log("new domain: " );
        //console.log(domain);
        //x.domain(d3.extent(data.map(function(d) {return d.date;})));
        for ( i = 0; i < dataArray.length; i++) {
          //loop through each of the arrays.
          //console.log("localyMax before search: " + localMax);
          localMax = d3.max(dataArray[i], function(d) {
            return d[1];
          });
          //console.log("localyMax of dataArray[" + i + "] is: " + localMax);
          if (localMax > max) {
            max = localMax;
          }
          localMin = d3.min(dataArray[i], function(d) {
            return d[1];
          });
          if (localMin < min) {
            min = localMin;
          }//it will never be smaller than zero unless it finds a negative value..

        }

        domain = [min, max];
        //console.log("domain min: " + domain[0] + ",  max: " + domain[1]);
        return domain;
      }

    });
  }

  // The x-accessor for the path generator; xScale âˆ˜ xValue.
  function X(d) {
    return xScale(d[0]);
  }

  // The x-accessor for the path generator; yScale âˆ˜ yValue.
  function Y(d) {
    return yScale(d[1]);
  }

  chart.margin = function(a) {
    if (!arguments.length)
      return margin;
    margin = a;
    return chart;
  };

  chart.width = function(a) {
    if (!arguments.length)
      return width;
    width = a;
    return chart;
  };

  chart.height = function(a) {
    if (!arguments.length)
      return height;
    height = a;
    return chart;
  };

  chart.x = function(a) {//Do we need this?
    if (!arguments.length)
      return xValue;
    xValue = a;
    return chart;
  };

  chart.y = function(a) {
    if (!arguments.length)
      return yValue;
    yValue = a;
    return chart;
  };

  chart.xscale = function(a) {
    if (!arguments.length){
      console.log(xScale);
      return xScale;
    }
    if (a ==="linear"){
      console.log("x:linear");
      xScale = d3.scale.linear();
    } else if (a === "log") {
      console.log("x:log");
      xScale = d3.scale.log();
    } else if (a === "time") {
      xScale = d3.time.scale();
      console.log("x:time");
    }
    return chart;
  };

  chart.yscale = function(a) {
    if (!arguments.length){
      console.log(yScale);
      return yScale;
    }
    if (a ==="linear"){
      console.log("y:linear");
      yScale = d3.scale.linear();
      yAxis.scale(yScale).orient("left").tickSize(6, 0).ticks(5);
    } else if (a === "log") {
      console.log("y:log");
      yScale = d3.scale.log();
      yAxis.tickFormat(function (d) {return yScale.tickFormat(10, d3.format(",d"))(d);});
    } else if (a === "time") {
      yScale = d3.time.scale();
      console.log("y:time");
    }
    return chart;
  };
  return chart;
}
<commit_msg>clean up scatterChart<commit_after>function scatterChart() {
  /*
   * Based on Mike Bostock's system for creating reusable charts, described
   * here: http://bost.ocks.org/mike/chart/
   *
   * My particular instance is based on the example used in that page
   * located here: http://bost.ocks.org/mike/chart/time-series-chart.js
   *
   * I added a Y axis, multiple lines.
   *
   * Tooltip found here: http://stackoverflow.com/questions/34886070/multiseries-line-chart-with-mouseover-tooltip
   */
  var margin = {
    top : 20,
    right : 20,
    bottom : 30,
    left : 50
  };
  var width = 760;
  var height = 120;
  var xValue = function(d) {
    return +d[0];
  };
  var yValue = function(d) {
    return +d[1];
  };
  //var xScale = d3.scale.linear();
  var xScale = d3.time.scale();
  //var yScale = d3.scale.linear();
  var yScale = d3.scale.log();
  var xAxis = d3.svg.axis().scale(xScale).orient("bottom").tickSize(6, 0);
  var yAxis = d3.svg.axis().scale(yScale).orient("left").tickSize(6, 0).ticks(5).tickFormat(function (d) {
    return yScale.tickFormat(10, d3.format(",d"))(d);
  });

  //var color = d3.scaleOrdinal(d3.schemeCategory10);
  var color = d3.scale.category10();

  //NEW
  //var area = d3.svg.area().x(X).y1(Y);
  var line = d3.svg.line()
      .interpolate("step-before")
      .x(X)
      .y(Y)
      .defined(function (d) { return d[1] !== null; });//This allows the line to break at null values.

  var xDomain = [];
  //leave empty. First time data are loaded, it will calculate the full x domain.
  var fullxDomain = [];
  var fullyDomain = [];

  function chart(selection) {
    selection.each(function(dataArray) {

      if (!xDomain.length) {//if xDomain hasn't been set yet, set it to the full domain.
        //xDomain = d3.extent(dataArray[0], function(d) { return d[0]; });
        xDomain = setFullxDomain();
      }

      // Update the full x & y domain.
      fullxDomain = setFullxDomain();
      fullyDomain = setFullyDomain();

      // Update the x-scale.
      xScale.domain(xDomain)//don't change the xDomain. Why not fullxDomain????
      .range([0, width - margin.left - margin.right]);

      // Update the y-scale.
      yScale
      //.domain([0, d3.max(dataArray[0], function(d) { return d[1]; })])//need to set extent in a new way, since some lines will have a different max.
      .domain(fullyDomain).range([height - margin.top - margin.bottom, 0]);

      // Select the svg element, if it exists.
      var svg = d3.select(this).selectAll("svg").data([dataArray]);
      //bind data to svg if it exists. IF it doesn't, then a new one will be made in the next line with the .enter(); WE only want one graph, so we should only have one element in the array.
      // Otherwise, create the skeletal chart.
      var gEnter = svg.enter().append("svg").append("g");
      //took out when I took out .data() above.

      var lineGroup = gEnter.append("g").attr("class", "lineGroup");
      var lineEnter = lineGroup.selectAll("path").data(dataArray).enter().append("path").attr("class", "line");
      //.attr("d", line);//don't draw the line yet. update the size of the svg first.

      gEnter.append("g").attr("class", "x axis");//is this name okay? It has a space!
      gEnter.append("g").attr("class", "y axis");

      var titleGroup = gEnter.append("g").attr("class", "titleGroup");
      titleGroup.append("svg:text").attr("class", "Title").text("Stream Hydrograph");
      titleGroup.append("svg:text").attr("class", "subtitle").attr("dy", "1em");
      titleGroup.append("svg:text").attr("class", "subtitle2").attr("dy", "2.2em");

      // Update the outer dimensions.
      svg.attr("width", width).attr("height", height);

      // Update the inner dimensions.
      var g = svg.select("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

      // Update the lines.
      g.selectAll(".line").attr("d", line).attr("stroke", function(d, i) {return color(i);});

      // Update the x-axis.
      g.select(".x.axis").attr("transform", "translate(0," + yScale.range()[0] + ")").call(xAxis).on("click", myClickFunction);

      // Update the y-axis.
      g.select(".y.axis").attr("transform", "translate(0," + xScale.range()[0] + ")").call(yAxis).on("click", myRClickFunction);

      //title block
      // Update the title.
      g.select(".titleGroup").attr("transform", "translate(10,0)");
      g.select(".subtitle").text(dataArray.length + " lines");
      g.select(".subtitle2").text(dataArray[0].length + " measurements per line");
      //title.on("click", myRClickFunction);

      //***************Tooltip Code ***************

// append a g for all the mouse over nonsense
      var mouseG = g.append("g")
          .attr("class", "mouse-over-effects");

// this is the vertical line
      mouseG.append("path")
          .attr("class", "mouse-line")
          .style("stroke", "black")
          .style("stroke-width", "1px")
          .style("opacity", "0");

// keep a reference to all our lines
      var lines = document.getElementsByClassName('line');

// here's a g for each circle and text on the line
      var mousePerLine = mouseG.selectAll('.mouse-per-line')
          .data(dataArray)
          .enter()
          .append("g")
          .attr("class", "mouse-per-line");

// the circle
      mousePerLine.append("circle")
          .attr("r", 7)
          .style("stroke", function(d) {
            return color(d.name);
          })
          .style("fill", "none")
          .style("stroke-width", "1px")
          .style("opacity", "0");

// the text
      mousePerLine.append("text")
          .attr("transform", "translate(10,3)");

// rect to capture mouse movements
      mouseG.append('svg:rect')
          .attr('width', width)
          .attr('height', height)
          .attr('fill', 'none')
          .attr('pointer-events', 'all')
          .on('mouseout', function() { // on mouse out hide line, circles and text
            d3.select(".mouse-line")
                .style("opacity", "0");
            d3.selectAll(".mouse-per-line circle")
                .style("opacity", "0");
            d3.selectAll(".mouse-per-line text")
                .style("opacity", "0");
          })
          .on('mouseover', function() { // on mouse in show line, circles and text
            d3.select(".mouse-line")
                .style("opacity", "1");
            d3.selectAll(".mouse-per-line circle")
                .style("opacity", "1");
            d3.selectAll(".mouse-per-line text")
                .style("opacity", "1");
          })
          .on('mousemove', function() { // mouse moving over canvas
            var mouse = d3.mouse(this);

            // move the vertical line
            d3.select(".mouse-line")
                .attr("d", function() {
                  var d = "M" + mouse[0] + "," + height;
                  d += " " + mouse[0] + "," + 0;
                  return d;
                });

            // position the circle and text
            d3.selectAll(".mouse-per-line")
                .attr("transform", function(d, i) {
                  //console.log(width/mouse[0]);
                  //console.log(d);
                  if(d.length < 1) { return; } //return if empty set.
                  var xDate = xScale.invert(mouse[0]),
                      bisect = d3.bisector(function(d) { return d[0]; }).right;
                  var idx = bisect(d[1], xDate); //Sometimes an empty set is in viewModel.dataArray and an error occurs.

                  // since we are use curve fitting we can't relay on finding the points like I had done in my last answer
                  // this conducts a search using some SVG path functions
                  // to find the correct position on the line
                  // from http://bl.ocks.org/duopixel/3824661
                  var beginning = 0;
                  var end = lines[i].getTotalLength(); //getTotalLength() is defined elsewhere...?

                  while (true){
                    var target = Math.floor((beginning + end) / 2);
                    var pos = lines[i].getPointAtLength(target);
                    if ((target === end || target === beginning) && pos.x !== mouse[0]) {
                      break;
                    }
                    if (pos.x > mouse[0])      end = target;
                    else if (pos.x < mouse[0]) beginning = target;
                    else break; //position found
                  }

                  // update the text with y value
                  d3.select(this).select('text')
                      .text(yScale.invert(pos.y).toFixed(2));

                  // return position
                  return "translate(" + mouse[0] + "," + pos.y +")";
                });
          });
      //***************Tooltip Code ***************

      //NEW click function for handling mouseclicks on an object.
      function myClickFunction() {//just a silly function taken from http://bl.ocks.org/mbostock/1166403
        console.log("click");

        var n = dataArray[0].length - 1, i = Math.floor(Math.random() * n / 2), j = i + Math.floor(Math.random() * n / 2) + 1;
        xDomain = [xValue(dataArray[0][i]), xValue(dataArray[0][j])];
        xScale.domain(xDomain);
        var t = g.transition().duration(750);
        t.select(".x.axis").call(xAxis);
        t.selectAll(".line").attr("d", line);
      }

      function myRClickFunction() {
        xDomain = setFullxDomain();
        console.log("myRClickFunction");
        xScale.domain(xDomain);
        var t = g.transition().duration(750);
        t.select(".x.axis").call(xAxis);
        t.selectAll(".line").attr("d", line);
      }

      function setFullxDomain() {
        //console.log("setFullxDomain");
        //set xmax and xmin to x value in first element in first array.
        var xmax = dataArray[0][0][0];
        var xmin = xmax;
        //console.log("xmax: " + xmax + " xmin: " + xmin);
        //d3.min(dataArray[0], xValue);
        var localxMax = xmax;
        var localxMin = xmax;

        //console.log("domain: " + domain);
        //console.log(domain);
        //domain = d3.extent(dataArray[0].map(function(d) {return d.date;}));
        //console.log("new domain: " );
        //console.log(domain);
        //x.domain(d3.extent(data.map(function(d) {return d.date;})));
        for ( var i = 0; i < dataArray.length; i++) {
          //loop through each of the arrays.
          //console.log("localxMax before search: " + localxMax);
          localxMax = d3.max(dataArray[i], function(d) {
            return d[0];
          });
          //console.log("localxMax of dataArray[" + i + "] is: " + localxMax);
          if (localxMax > xmax) {
            xmax = localxMax;
          }
          localxMin = d3.min(dataArray[i], function(d) {
            return d[0];
          });
          if (localxMin < xmin) {
            xmin = localxMin;
          }//it will never be smaller than zero.

        }

        return [xmin, xmax];
      }

      function setFullyDomain() {
        //console.log("setFullyDomain");
        //set xmax and xmin to x value in first element in first array.
        var max = dataArray[0][0][1];
        var min = max;
        //console.log("max: " + max + " min: " + min);
        //d3.min(dataArray[0], xValue);
        var localMax = max;
        var localMin = max;

        //console.log("y domain: " + domain);
        //console.log(domain);
        //domain = d3.extent(dataArray[0].map(function(d) {return d.date;}));
        //console.log("new domain: " );
        //console.log(domain);
        //x.domain(d3.extent(data.map(function(d) {return d.date;})));
        for ( var i = 0; i < dataArray.length; i++) {
          //loop through each of the arrays.
          //console.log("localyMax before search: " + localMax);
          localMax = d3.max(dataArray[i], function(d) {
            return d[1];
          });
          //console.log("localyMax of dataArray[" + i + "] is: " + localMax);
          if (localMax > max) {
            max = localMax;
          }
          localMin = d3.min(dataArray[i], function(d) {
            return d[1];
          });
          if (localMin < min) {
            min = localMin;
          }//it will never be smaller than zero unless it finds a negative value..

        }
        return [min, max];
      }

    });
  }

  // The x-accessor for the path generator; xScale âˆ˜ xValue.
  function X(d) {
    return xScale(d[0]);
  }

  // The x-accessor for the path generator; yScale âˆ˜ yValue.
  function Y(d) {
    return yScale(d[1]);
  }

  chart.margin = function(a) {
    if (!arguments.length)
      return margin;
    margin = a;
    return chart;
  };

  chart.width = function(a) {
    if (!arguments.length)
      return width;
    width = a;
    return chart;
  };

  chart.height = function(a) {
    if (!arguments.length)
      return height;
    height = a;
    return chart;
  };

  chart.x = function(a) {//Do we need this?
    if (!arguments.length)
      return xValue;
    xValue = a;
    return chart;
  };

  chart.y = function(a) {
    if (!arguments.length)
      return yValue;
    yValue = a;
    return chart;
  };

  chart.xscale = function(a) {
    if (!arguments.length){
      console.log(xScale);
      return xScale;
    }
    if (a ==="linear"){
      console.log("x:linear");
      xScale = d3.scale.linear();
    } else if (a === "log") {
      console.log("x:log");
      xScale = d3.scale.log();
    } else if (a === "time") {
      xScale = d3.time.scale();
      console.log("x:time");
    }
    return chart;
  };

  chart.yscale = function(a) {
    if (!arguments.length){
      console.log(yScale);
      return yScale;
    }
    if (a ==="linear"){
      console.log("y:linear");
      yScale = d3.scale.linear();
      yAxis.scale(yScale).orient("left").tickSize(6, 0).ticks(5);
    } else if (a === "log") {
      console.log("y:log");
      yScale = d3.scale.log();
      yAxis.tickFormat(function (d) {return yScale.tickFormat(10, d3.format(",d"))(d);});
    } else if (a === "time") {
      yScale = d3.time.scale();
      console.log("y:time");
    }
    return chart;
  };
  return chart;
}
<|endoftext|>


<commit_before>'use strict';

angular.module('confRegistrationWebApp')
  .directive('showAnswer', function (AnswerCache, $modal, $timeout) {
    return {
      templateUrl: 'views/adminAnswerDisplay.html',
      restrict: 'E',
      scope: {
        answers: '=',
        block: '='
      },
      controller: function ($scope) {
        $scope.editMode = false;

        $scope.setEditMode = function (newValue, blockId) {
          $scope.editMode = newValue;
          if (!blockId){
        	  blockId = $scope.block.id;
          }
          if (newValue === true) {
            $timeout(function () {
              var inputId = 'edit-' + blockId;
              var inputElem = document.getElementById(inputId);
              inputElem.focus();
            }, 20);

            AnswerCache.syncBlock($scope, 'answer');
          }

        };

        var editAnswerDialogOptions = {
          templateUrl: 'views/editAnswer.html',
          controller: 'EditAnswerDialogCtrl',
          scope: $scope
        };

        $scope.createEditDialog = function () {
          $modal.open(editAnswerDialogOptions).result.then(function () {
            AnswerCache.syncBlock($scope, 'answer');
          });
        };

        $scope.$watch('answers', function () {
          if ($scope.answers) {
            var answerObject = _.find($scope.answers, { blockId: $scope.block.id });
            if (answerObject) {
              $scope.answer = answerObject;
            }
          }
        });

        $scope.getSelectedCheckboxes = function (choices) {
          return _.keys(_.pick(choices, function (val) {
            return val === true;
          }));
        };

      }
    };
  });
<commit_msg>editMode and related things no longer needed.<commit_after>'use strict';

angular.module('confRegistrationWebApp')
  .directive('showAnswer', function (AnswerCache, $modal, $timeout) {
    return {
      templateUrl: 'views/adminAnswerDisplay.html',
      restrict: 'E',
      scope: {
        answers: '=',
        block: '='
      },
      controller: function ($scope) {

        var editAnswerDialogOptions = {
          templateUrl: 'views/editAnswer.html',
          controller: 'EditAnswerDialogCtrl',
          scope: $scope
        };

        $scope.createEditDialog = function () {
          $modal.open(editAnswerDialogOptions).result.then(function () {
            AnswerCache.syncBlock($scope, 'answer');
          });
        };

        $scope.$watch('answers', function () {
          if ($scope.answers) {
            var answerObject = _.find($scope.answers, { blockId: $scope.block.id });
            if (answerObject) {
              $scope.answer = answerObject;
            }
          }
        });

        $scope.getSelectedCheckboxes = function (choices) {
          return _.keys(_.pick(choices, function (val) {
            return val === true;
          }));
        };

      }
    };
  });
<|endoftext|>


<commit_before>define([
		"dojo/_base/lang",
		"dojo/_base/declare",
		"../../CircularGauge",
		"../../LinearScaler",
		"../../CircularScale",
		"../../CircularValueIndicator",
		"../../CircularRangeIndicator"
	],
	function(lang, declare, CircularGauge, LinearScaler, CircularScale, CircularValueIndicator, CircularRangeIndicator){

	/*=====
     var _CircularGauge = dojox.gauge.CircularGauge;
     =====*/

		return declare("dojox.gauge.components.black.SemiCircularLinearGauge", CircularGauge, {

			constructor: function(args, node){
				var scaler = new LinearScaler();
				this.addElement("background", lang.hitch(this, this.drawBackground));
				var scale = new CircularScale();
				scale.set("scaler", scaler);
				scale.set("originX", 186.46999);
				scale.set("originY", 184.71315);
				scale.set("radius", 149.82183);
				scale.set("startAngle", -180);
				scale.set("endAngle", 0);
				scale.set("orientation", "clockwise");
				scale.set("labelGap", 8);
				scale.set("font", {
					family: "Helvetica",
					weight: 'bold',
					size: "14pt",
					color: "#CECECE"
				});
				scale.set("tickShapeFunc", function(group, scale, tick){
					return group.createCircle({
						r: tick.isMinor ? 2 : 4
					}).setFill("#CECECE");
				});
				this.addElement("scale", scale);
				var indicator = new CircularValueIndicator();
				indicator.set("interactionArea", "gauge");
				indicator.set("value", scaler.minimum);
				indicator.set("indicatorShapeFunc", function(group, indicator){

					return group.createPolyline([0, -12, indicator.scale.radius - 2, 0, 0, 12, 0, -12]).setStroke({
						color: [69,69,69],
						width: 1
					}).setFill([164,164,164]);

				});
				scale.addIndicator("indicator", indicator);
				this.addElement("foreground", lang.hitch(this, this.drawForeground));
			},

			drawBackground: function(g){
				g.createPath({
					path: "M372.8838 205.5338 C372.9125 204.4188 372.93 194.1 372.94 185.5712 C372.4475 82.9713 289.1138 -0.035 186.4063 0 C83.7 0.0363 0.4225 83.0975 0 185.6975 C0.01 194.1825 0.0275 204.4288 0.0563 205.5413 C0.235 212.3138 5.7763 217.7112 12.5525 217.7112 L360.3888 217.7112 C367.1663 217.7112 372.71 212.3088 372.8838 205.5338"
				}).setFill("black");
				g.createPath({
					path: "M358.6738 203.9615 C358.7188 202.3277 358.7463 188.189 358.745 186.544 L358.745 186.4277 C358.7138 91.2815 281.5575 14.1777 186.4113 14.209 C91.2675 14.2427 14.1625 91.3977 14.1938 186.544 C14.1938 186.5827 14.2213 202.3665 14.2663 203.9615 L358.6738 203.9615 Z"
				}).setFill({
					type: "linear",
					x1: 14.19376,
					y1: 260.88725,
					x2: 14.19376,
					y2: 156.52337,
					colors: [
						{offset: 0, color: [100,100,100]},
						{offset: 1, color: "black"}
					]
				});
				g.createPath({
					path: "M358.7038 182.8677 C356.775 89.3677 280.3713 14.1777 186.4163 14.209 C92.5013 14.2415 16.1713 89.4177 14.2438 182.8652 C66.8388 197.029 127.36 168.779 188.7525 168.779 C250.1638 168.779 306.2575 197.0353 358.7038 182.8677"
				}).setFill({
					type: "linear",
					x1: 14.24378,
					y1: 186.84286,
					x2: 14.24378,
					y2: 14.20898,
					colors: [
						{offset: 0, color: "black"},
						{offset: 1, color: [200,200,200]}
					]
				});
				g.createPath({
					path: "M358.953 183.1203 C357.0243 89.6203 280.6205 14.4303 186.6655 14.4616 C92.7505 14.4941 16.4205 89.6703 14.493 183.1178 C67.088 197.2816 127.6093 169.0316 189.0018 169.0316 C250.413 169.0316 306.5068 197.2878 358.953 183.1203"
				}).setFill([255,255,255,0.12157]);
			},

			drawForeground: function(g){
				g.createPath({
					path: "M214.9406 185.2945 C214.9456 201.0183 202.2044 213.7683 186.4806 213.7745 C170.7544 213.7795 158.0044 201.037 157.9994 185.3145 L157.9994 185.2945 C157.9931 169.5707 170.7369 156.8207 186.4619 156.8145 C202.1844 156.8095 214.9356 169.552 214.9406 185.2758 L214.9406 185.2945 Z"
				}).setFill("black");
				g.createPath({
					path: "M211.3563 185.294 C211.36 199.039 200.2238 210.1827 186.4787 210.1878 C172.735 210.1927 161.59 199.0552 161.585 185.3115 L161.585 185.294 C161.58 171.5502 172.7175 160.4052 186.4613 160.4002 C200.2063 160.3953 211.3513 171.534 211.3563 185.2778 L211.3563 185.294 Z"
				}).setFill({
					type: "linear",
					x1: 161.58503,
					y1: 210.18773,
					x2: 161.58503,
					y2: 185.29399,
					colors: [
						{offset: 0, color: [100,100,100]},
						{offset: 1, color: "black"}
					]
				});
				g.createPath({
					path: "M211.35 184.764 C211.0713 171.2578 200.035 160.3953 186.4625 160.4002 C172.8963 160.4052 161.87 171.264 161.5925 184.764 C169.1888 186.809 177.93 182.729 186.8013 182.729 C195.6712 182.729 203.7738 186.8102 211.35 184.764"
				}).setFill({
					type: "linear",
					x1: 161.59251,
					y1: 185.33811,
					x2: 161.59251,
					y2: 160.40024,
					colors: [
						{offset: 0, color: "black"},
						{offset: 1, color: [150,150,150]}
					]
				});
				g.createPath({
					path: "M211.3494 184.7635 C211.0706 171.2573 200.0344 160.396 186.4632 160.401 C172.8956 160.406 161.8707 171.2647 161.5919 184.7635 C169.1881 186.8085 177.9306 182.7285 186.8006 182.7285 C195.6706 182.7285 203.7731 186.811 211.3494 184.7635"
				}).setFill([255,255,255,0.12157]);
			}

		});
	}
);

<commit_msg>predefined gauge simplification<commit_after>define(["dojo/_base/lang", "dojo/_base/declare", "../../CircularGauge", "../../LinearScaler", "../../CircularScale", "../../CircularValueIndicator", "../../CircularRangeIndicator"], function(lang, declare, CircularGauge, LinearScaler, CircularScale, CircularValueIndicator, CircularRangeIndicator){

    /*=====
     var _CircularGauge = ibm_ilog.gauge.CircularGauge;
     =====*/
    return declare("dojox.gauge.components.black.SemiCircularLinearGauge", CircularGauge, {

        constructor: function(args, node){
            var scaler = new LinearScaler();
            this.addElement("background", lang.hitch(this, this.drawBackground));
            var scale = new CircularScale();
            scale.set("scaler", scaler);
            scale.set("originX", 186.46999);
            scale.set("originY", 184.71315);
            scale.set("radius", 149.82183);
            scale.set("startAngle", -180);
            scale.set("endAngle", 0);
            scale.set("orientation", "clockwise");
            scale.set("labelGap", 8);
            scale.set("font", {
                family: "Helvetica",
                weight: 'bold',
                size: "14pt",
                color: "#CECECE"
            });
            scale.set("tickShapeFunc", function(group, scale, tick){
                return group.createCircle({
                    r: tick.isMinor ? 2 : 4
                }).setFill("#CECECE");
            });
            this.addElement("scale", scale);
            var indicator = new CircularValueIndicator();
            indicator.set("interactionArea", "gauge");
            indicator.set("value", scaler.minimum);
            indicator.set("indicatorShapeFunc", function(group, indicator){

                return group.createPolyline([0, -12, indicator.scale.radius - 2, 0, 0, 12, 0, -12]).setStroke({
                    color: [69, 69, 69],
                    width: 1
                }).setFill([164, 164, 164]);

            });
            scale.addIndicator("indicator", indicator);
            this.addElement("foreground", lang.hitch(this, this.drawForeground));
        },

        drawBackground: function(g){
            g.createPath({
                path: "M372.8838 205.5688 C372.9125 204.4538 372.93 194.135 372.94 185.6062 C372.4475 83.0063 289.1138 -0 186.4063 0.035 C83.7 0.0713 0.4225 83.1325 0 185.7325 C0.01 194.2175 0.0275 204.4638 0.0563 205.5763 C0.235 212.3488 5.7763 217.7462 12.5525 217.7462 L360.3888 217.7462 C367.1663 217.7462 372.71 212.3438 372.8838 205.5688"
            }).setFill([0, 0, 0, 1]);
            g.createPath({
                path: "M358.6738 203.9965 C358.7188 202.3627 358.7463 188.224 358.745 186.579 L358.745 186.4627 C358.7138 91.3165 281.5575 14.2127 186.4113 14.244 C91.2675 14.2777 14.1625 91.4327 14.1938 186.579 C14.1938 186.6177 14.2213 202.4015 14.2663 203.9965 L358.6738 203.9965 Z"
            }).setFill({
                type: "linear",
                x1: 14.19376,
                y1: 260.92225,
                x2: 14.19376,
                y2: 156.55837,
                colors: [{
                    offset: 0,
                    color: [100, 100, 100]
                }, {
                    offset: 1,
                    color: "black"
                }]
            });
            g.createPath({
                path: "M358.7038 182.9027 C356.775 89.4027 280.3713 14.2127 186.4163 14.244 C92.5013 14.2765 16.1713 89.4527 14.2438 182.9002 C66.8388 197.064 127.36 168.814 188.7525 168.814 C250.1638 168.814 306.2575 197.0703 358.7038 182.9027"
            }).setFill({
                type: "linear",
                x1: 14.24378,
                y1: 186.87786,
                x2: 14.24378,
                y2: 14.24398,
                colors: [{
                    offset: 0,
                    color: "black"
                }, {
                    offset: 1,
                    color: [200, 200, 200]
                }]
            });
            g.createPath({
                path: "M358.953 183.1553 C357.0243 89.6553 280.6205 14.4653 186.6655 14.4966 C92.7505 14.5291 16.4205 89.7053 14.493 183.1528 C67.088 197.3166 127.6093 169.0666 189.0018 169.0666 C250.413 169.0666 306.5068 197.3228 358.953 183.1553"
            }).setFill([255, 255, 255, 0.12157]);
        },

        drawForeground: function(g){
            var g1 = g.createGroup();
            g1.createPath({
                path: "M214.9406 185.3295 C214.9456 201.0533 202.2044 213.8033 186.4806 213.8095 C170.7544 213.8145 158.0044 201.072 157.9994 185.3495 L157.9994 185.3295 C157.9931 169.6057 170.7369 156.8557 186.4619 156.8495 C202.1844 156.8445 214.9356 169.587 214.9406 185.3108 L214.9406 185.3295 Z"
            }).setFill("black");
            g1.createPath({
                path: "M211.3563 185.329 C211.36 199.074 200.2238 210.2177 186.4787 210.2228 C172.735 210.2277 161.59 199.0902 161.585 185.3465 L161.585 185.329 C161.58 171.5852 172.7175 160.4402 186.4613 160.4352 C200.2063 160.4303 211.3513 171.569 211.3563 185.3128 L211.3563 185.329 Z"
            }).setFill({
                type: "linear",
                x1: 161.58503,
                y1: 210.22273,
                x2: 161.58503,
                y2: 185.32899,
                colors: [{
                    offset: 0,
                    color: [100, 100, 100]
                }, {
                    offset: 1,
                    color: "black"
                }]
            });
            g1.createPath({
                path: "M211.35 184.799 C211.0713 171.2928 200.035 160.4303 186.4625 160.4352 C172.8963 160.4402 161.87 171.299 161.5925 184.799 C169.1888 186.844 177.93 182.764 186.8013 182.764 C195.6712 182.764 203.7738 186.8452 211.35 184.799"
            }).setFill({
                type: "linear",
                x1: 161.59251,
                y1: 185.37311,
                x2: 161.59251,
                y2: 160.43524,
                colors: [{
                    offset: 0,
                    color: "black"
                }, {
                    offset: 1,
                    color: [150, 150, 150]
                }]
            });
            g1.createPath({
                path: "M211.3494 184.7985 C211.0706 171.2923 200.0344 160.431 186.4632 160.436 C172.8956 160.441 161.8707 171.2997 161.5919 184.7985 C169.1881 186.8435 177.9306 182.7635 186.8006 182.7635 C195.6706 182.7635 203.7731 186.846 211.3494 184.7985"
            }).setFill([255, 255, 255, 0.12157]);
        }
    });
});

<|endoftext|>


<commit_before>import _ from 'underscore';

import WidgetCollection from '../collections/WidgetCollection';
import ControlsPanel from './ControlsPanel';
import View from 'girder/views/View';
import FolderModel from 'girder/models/FolderModel';
import ItemModel from 'girder/models/ItemModel';
import router  from 'girder/router';
import { restRequest } from 'girder/rest';
import { renderMarkdown } from 'girder/misc';

import template from '../templates/taskRun.pug';
import '../stylesheets/taskRun.styl';

const TaskRunView = View.extend({
    events: {
        'click .g-run-task': 'execute'
    },

    initialize: function (settings) {
        this._taskSpec = this.model.get('meta').itemTaskSpec || {};
        this._inputs = this._taskSpec.inputs || [];
        this._outputs = this._taskSpec.outputs || [];
        this._inputWidgets = new WidgetCollection();
        this._outputWidgets = new WidgetCollection();
        this._initialValues = settings.initialValues || null;

        const inputs = this._initialValues && this._initialValues.inputs || {};
        const outputs = this._initialValues && this._initialValues.outputs || {};

        // Build all the widget models from the task IO spec
        this._inputWidgets.add(
            this._inputs.map((input) => this._setJobInfo(input, inputs))
        );

        this._outputWidgets.add(
            this._outputs.map((output) => this._setJobInfo(output, outputs))
        );

        this._inputsPanel = new ControlsPanel({
            title: 'Configure inputs',
            collection: this._inputWidgets,
            parentView: this
        });

        this._outputsPanel = new ControlsPanel({
            title: 'Configure outputs',
            collection: this._outputWidgets,
            parentView: this
        });
    },

    /**
     * Fill in values according to an existing job.
     *
     * @param {object} spec The task parameter spec
     * @param {object} bindings The job parameter bindings
     */
    _setJobInfo: function (spec, bindings) {
        const match = bindings[spec.id || spec.name] || {};
        if (match.mode === 'girder' && match.resource_type === 'item') {
            spec.value = new ItemModel({
                _id: match.id,
                _modelType: 'item',
                name: match.fileName || match.id
            });
            spec.fileName = match.fileName || match.id;
        } else if (match.mode === 'girder' && match.parent_type === 'folder') {
            spec.value = new FolderModel({
                _id: match.parent_id,
                _modelType: 'folder'
            });
            spec.fileName = match.name || match.id;
        } else if (_.has(match, 'data')) {
            spec.value = match.data;
        }
        return spec;
    },

    render: function () {
        var hasInputs = !!this._inputs.length,
            hasOutputs = !!this._outputs.length;

        this.$el.html(template({
            item: this.model,
            hasInputs: hasInputs,
            hasOutputs: hasOutputs,
            renderMarkdown: renderMarkdown
        }));

        if (hasInputs) {
            this._inputsPanel.setElement(this.$('.g-inputs-container')).render();
        }

        if (hasOutputs) {
            this._outputsPanel.setElement(this.$('.g-outputs-container')).render();
        }
    },

    /**
     * Validates that all of the widgets are in a valid state. Displays any
     * invalid states and
     */
    validate: function () {
        let ok = true;
        const test = (model) => {
            if (!model.isValid()) {
                ok = false;
            }
        };

        // Don't short-circuit; we want to highlight *all* invalid inputs
        this._inputWidgets.each(test);
        this._outputWidgets.each(test);

        return ok;
    },

    /**
     * Translates the WidgetCollection state for the input and output widgets into the
     * appropriate girder_worker input and output bindings, then invokes the endpoint
     * to execute the task. TODO We probably want to move that translation layer down into
     * the WidgetCollection itself in the future.
     */
    execute: function (e) {
        if (!this.validate()) {
            this.$('.g-validation-failed-message').text(
                'One or more of your inputs or outputs is invalid, they are highlighted in red.');
            return;
        }
        this.$('.g-validation-failed-message').empty();
        $(e.currentTarget).attr('disabled', 'true').addClass('disabled');

        const inputs = {}, outputs = {};

        const translate = (model) => {
            const val = model.value();

            switch (model.get('type')) {
                case 'image': // This is an input
                    return {
                        mode: 'girder',
                        resource_type: 'file',
                        id: val.id,
                        fileName: model.get('fileName') || null
                    };
                case 'file': // This is an input
                    return {
                        mode: 'girder',
                        resource_type: 'item',
                        id: val.id,
                        fileName: model.get('fileName') || null
                    };
                case 'new-file': // This is an output
                    return {
                        mode: 'girder',
                        parent_id: val.id,
                        parent_type: 'folder',
                        name: model.get('fileName')
                    };
                default:
                    return {
                        mode: 'inline',
                        data: model.value()
                    };
            }
        };

        this._inputWidgets.each((model) => {
            inputs[model.id] = translate(model);
        });
        this._outputWidgets.each((model) => {
            outputs[model.id] = translate(model);
        });

        restRequest({
            path: `item_task/${this.model.id}/execution`,
            type: 'POST',
            data: {
                inputs: JSON.stringify(inputs),
                outputs: JSON.stringify(outputs)
            },
            error: null
        }).done((resp) => {
            router.navigate(`job/${resp._id}`, {trigger: true});
        }).error((resp) => {
            $(e.currentTarget).attr('disabled', null).removeClass('disabled');
            this.$('.g-validation-failed-message').text('Error: ' + resp.responseJSON.message);
        });
    }
});

export default TaskRunView;
<commit_msg>Set multivalue arguments as comma separated lists<commit_after>import _ from 'underscore';

import WidgetCollection from '../collections/WidgetCollection';
import ControlsPanel from './ControlsPanel';
import View from 'girder/views/View';
import FolderModel from 'girder/models/FolderModel';
import ItemModel from 'girder/models/ItemModel';
import router  from 'girder/router';
import { restRequest } from 'girder/rest';
import { renderMarkdown } from 'girder/misc';

import template from '../templates/taskRun.pug';
import '../stylesheets/taskRun.styl';

const TaskRunView = View.extend({
    events: {
        'click .g-run-task': 'execute'
    },

    initialize: function (settings) {
        this._taskSpec = this.model.get('meta').itemTaskSpec || {};
        this._inputs = this._taskSpec.inputs || [];
        this._outputs = this._taskSpec.outputs || [];
        this._inputWidgets = new WidgetCollection();
        this._outputWidgets = new WidgetCollection();
        this._initialValues = settings.initialValues || null;

        const inputs = this._initialValues && this._initialValues.inputs || {};
        const outputs = this._initialValues && this._initialValues.outputs || {};

        // Build all the widget models from the task IO spec
        this._inputWidgets.add(
            this._inputs.map((input) => this._setJobInfo(input, inputs))
        );

        this._outputWidgets.add(
            this._outputs.map((output) => this._setJobInfo(output, outputs))
        );

        this._inputsPanel = new ControlsPanel({
            title: 'Configure inputs',
            collection: this._inputWidgets,
            parentView: this
        });

        this._outputsPanel = new ControlsPanel({
            title: 'Configure outputs',
            collection: this._outputWidgets,
            parentView: this
        });
    },

    /**
     * Fill in values according to an existing job.
     *
     * @param {object} spec The task parameter spec
     * @param {object} bindings The job parameter bindings
     */
    _setJobInfo: function (spec, bindings) {
        const match = bindings[spec.id || spec.name] || {};
        if (match.mode === 'girder' && match.resource_type === 'item') {
            spec.value = new ItemModel({
                _id: match.id,
                _modelType: 'item',
                name: match.fileName || match.id
            });
            spec.fileName = match.fileName || match.id;
        } else if (match.mode === 'girder' && match.parent_type === 'folder') {
            spec.value = new FolderModel({
                _id: match.parent_id,
                _modelType: 'folder'
            });
            spec.fileName = match.name || match.id;
        } else if (_.has(match, 'data')) {
            spec.value = match.data;
        }
        return spec;
    },

    render: function () {
        var hasInputs = !!this._inputs.length,
            hasOutputs = !!this._outputs.length;

        this.$el.html(template({
            item: this.model,
            hasInputs: hasInputs,
            hasOutputs: hasOutputs,
            renderMarkdown: renderMarkdown
        }));

        if (hasInputs) {
            this._inputsPanel.setElement(this.$('.g-inputs-container')).render();
        }

        if (hasOutputs) {
            this._outputsPanel.setElement(this.$('.g-outputs-container')).render();
        }
    },

    /**
     * Validates that all of the widgets are in a valid state. Displays any
     * invalid states and
     */
    validate: function () {
        let ok = true;
        const test = (model) => {
            if (!model.isValid()) {
                ok = false;
            }
        };

        // Don't short-circuit; we want to highlight *all* invalid inputs
        this._inputWidgets.each(test);
        this._outputWidgets.each(test);

        return ok;
    },

    /**
     * Translates the WidgetCollection state for the input and output widgets into the
     * appropriate girder_worker input and output bindings, then invokes the endpoint
     * to execute the task. TODO We probably want to move that translation layer down into
     * the WidgetCollection itself in the future.
     */
    execute: function (e) {
        if (!this.validate()) {
            this.$('.g-validation-failed-message').text(
                'One or more of your inputs or outputs is invalid, they are highlighted in red.');
            return;
        }
        this.$('.g-validation-failed-message').empty();
        $(e.currentTarget).attr('disabled', 'true').addClass('disabled');

        const inputs = {}, outputs = {};

        const translate = (model) => {
            let val = model.value();

            switch (model.get('type')) {
                case 'image': // This is an input
                    return {
                        mode: 'girder',
                        resource_type: 'file',
                        id: val.id,
                        fileName: model.get('fileName') || null
                    };
                case 'file': // This is an input
                    return {
                        mode: 'girder',
                        resource_type: 'item',
                        id: val.id,
                        fileName: model.get('fileName') || null
                    };
                case 'new-file': // This is an output
                    return {
                        mode: 'girder',
                        parent_id: val.id,
                        parent_type: 'folder',
                        name: model.get('fileName')
                    };
                default:
                    if (model.isVector()) {
                        val = val.join(',');
                    }
                    return {
                        mode: 'inline',
                        data: val
                    };
            }
        };

        this._inputWidgets.each((model) => {
            inputs[model.id] = translate(model);
        });
        this._outputWidgets.each((model) => {
            outputs[model.id] = translate(model);
        });

        restRequest({
            path: `item_task/${this.model.id}/execution`,
            type: 'POST',
            data: {
                inputs: JSON.stringify(inputs),
                outputs: JSON.stringify(outputs)
            },
            error: null
        }).done((resp) => {
            router.navigate(`job/${resp._id}`, {trigger: true});
        }).error((resp) => {
            $(e.currentTarget).attr('disabled', null).removeClass('disabled');
            this.$('.g-validation-failed-message').text('Error: ' + resp.responseJSON.message);
        });
    }
});

export default TaskRunView;
<|endoftext|>


<commit_before>import React, { useEffect, useState } from 'react'
// import PropTypes from 'prop-types'

import Grid from '@material-ui/core/Grid'
import {
  makeStyles,
  Paper,
  Table,
  TableCell,
  TableRow,
  TableHead,
  TableBody,
  TableFooter,
  TableContainer,
  TableSortLabel,
} from '@material-ui/core'

import { Alert, AlertTitle } from '@material-ui/lab'

import api from '@/_api'

import BasePageContainer from '@/_common/BasePageContainer'
import BasePageToolbar from '@/_common/BasePageToolbar'
import { BaseTablePagination } from '@/_common/BaseTable'

import UsersListAction from './UsersListActions'
import UsersListTableItems from './UsersListTableItems'

const UsersList = ({ match }) => {
  const classes = useStyles()

  const [status, setStatus] = React.useState('idle')
  const [statusMessage, setStatusMessage] = React.useState('')
  const [page, setPage] = React.useState(0)
  const [usersData, setUsersData] = useState({ users: [], count: 0 })
  const [rowsPerPage, setRowsPerPage] = React.useState(10)
  const [order, setOrder] = React.useState({
    order: 'desc',
    orderBy: 'createdAt',
  })

  const { users, count } = usersData
  // const rowsExpected = count ? Math.max(count - rowsPerPage * page, 0) : rowsPerPage

  const tableColumns = [
    {
      id: 'avatarUrl',
      label: '',
      isSortable: false,
    },
    {
      id: 'firstName',
      label: 'First Name',
      isSortable: true,
    },
    {
      id: 'lastName',
      label: 'Last Name',
      isSortable: true,
    },
    {
      id: 'username',
      label: 'Username',
      isSortable: true,
    },
    {
      id: 'email',
      label: 'Email',
      isSortable: true,
    },
    {
      id: 'createdAt',
      label: 'Created',
      isSortable: true,
    },
  ]

  // Request users
  useEffect(() => {
    async function fetchUsers() {
      setStatus('loading')

      try {
        const userDataRes = await api.users.getList({
          limit: rowsPerPage,
          offset: page * rowsPerPage,
          order,
        })

        // Make some artificial delay
        await new Promise(resolve => {
          setTimeout(() => resolve(true), 300)
        })

        setStatus('idle')
        setUsersData(userDataRes)
      } catch (err) {
        console.log('error', err.message)

        setStatus('error')
        setStatusMessage(err.message)
      }
    }

    fetchUsers()
  }, [order, page, rowsPerPage, usersData.count])

  const handleChangePage = (event, newPage) => {
    setPage(newPage)
  }

  const handleChangeRowsPerPage = event => {
    setRowsPerPage(parseInt(event.target.value, 10))
    setPage(0)
  }

  const handelChangeOrder = (event, columnId) => {
    setOrder({
      // If the sorting column has changed
      order: columnId !== order.orderBy || order.order === 'desc' ? 'asc' : 'desc',
      orderBy: columnId,
    })
  }

  return (
    <BasePageContainer>
      <BasePageToolbar
        title={'Users Adminstration'}
        actionsComponent={UsersListAction}
      ></BasePageToolbar>
      <Grid container spacing={3}>
        <Grid item xs={12}>
          {status === 'error' && (
            <Alert severity="error">
              <AlertTitle>Error</AlertTitle>
              {statusMessage}
            </Alert>
          )}

          {status !== 'error' && (
            <TableContainer component={Paper}>

                <TableHead>
                  <TableRow>
                    {tableColumns.map(column => (
                      <TableCell key={column.id}>
                        {/* Sortable */}
                        {column.isSortable && (
                          <TableSortLabel
                            active={order.orderBy === column.id}
                            direction={order.orderBy === column.id ? order.order : 'asc'}
                            onClick={event => handelChangeOrder(event, column.id)}
                          >
                            {column.label}
                          </TableSortLabel>
                        )}
                        {/* Non-sortable */}
                        {!column.isSortable && column.label}
                      </TableCell>
                    ))}
                    <TableCell>Actions</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  <UsersListTableItems
                    users={status === 'loading' ? [] : users}
                    rowsPerPage={rowsPerPage}
                  />
                </TableBody>
                <TableFooter>
                  <TableRow>
                    <BaseTablePagination
                      page={page}
                      rowsPerPage={rowsPerPage}
                      count={count}
                      order={order}
                      onChangePage={handleChangePage}
                      onChangeRowsPerPage={handleChangeRowsPerPage}
                    />
                  </TableRow>
                </TableFooter>

            </TableContainer>
          )}
        </Grid>
      </Grid>
    </BasePageContainer>
  )
}

const useStyles = makeStyles(theme => ({
  root: {
    flexShrink: 0,
    marginLeft: theme.spacing(2.5),
  },
}))

UsersList.propTypes = {}

export default UsersList
<commit_msg>Fix double fetching<commit_after>import React, { useEffect, useState } from 'react'
// import PropTypes from 'prop-types'

import Grid from '@material-ui/core/Grid'
import {
  makeStyles,
  Paper,
  Table,
  TableCell,
  TableRow,
  TableHead,
  TableBody,
  TableFooter,
  TableContainer,
  TableSortLabel,
} from '@material-ui/core'

import { Alert, AlertTitle } from '@material-ui/lab'

import api from '@/_api'

import BasePageContainer from '@/_common/BasePageContainer'
import BasePageToolbar from '@/_common/BasePageToolbar'
import { BaseTablePagination } from '@/_common/BaseTable'

import UsersListAction from './UsersListActions'
import UsersListTableItems from './UsersListTableItems'

const UsersList = ({ match }) => {
  const classes = useStyles()

  const [status, setStatus] = React.useState('idle')
  const [statusMessage, setStatusMessage] = React.useState('')
  const [page, setPage] = React.useState(0)
  const [usersData, setUsersData] = useState({ users: [], count: 0 })
  const [rowsPerPage, setRowsPerPage] = React.useState(10)
  const [order, setOrder] = React.useState({
    order: 'desc',
    orderBy: 'createdAt',
  })

  const { users, count } = usersData
  // const rowsExpected = count ? Math.max(count - rowsPerPage * page, 0) : rowsPerPage

  const tableColumns = [
    {
      id: 'avatarUrl',
      label: '',
      isSortable: false,
    },
    {
      id: 'firstName',
      label: 'First Name',
      isSortable: true,
    },
    {
      id: 'lastName',
      label: 'Last Name',
      isSortable: true,
    },
    {
      id: 'username',
      label: 'Username',
      isSortable: true,
    },
    {
      id: 'email',
      label: 'Email',
      isSortable: true,
    },
    {
      id: 'createdAt',
      label: 'Created',
      isSortable: true,
    },
  ]

  // Request users
  useEffect(() => {
    async function fetchUsers() {
      setStatus('loading')

      try {
        const userDataRes = await api.users.getList({
          limit: rowsPerPage,
          offset: page * rowsPerPage,
          order,
        })

        // Make some artificial delay
        await new Promise(resolve => {
          setTimeout(() => resolve(true), 300)
        })

        setStatus('idle')
        setUsersData(userDataRes)
      } catch (err) {
        console.log('error', err.message)

        setStatus('error')
        setStatusMessage(err.message)
      }
    }

    fetchUsers()
  }, [order, page, rowsPerPage])

  const handleChangePage = (event, newPage) => {
    setPage(newPage)
  }

  const handleChangeRowsPerPage = event => {
    setRowsPerPage(parseInt(event.target.value, 10))
    setPage(0)
  }

  const handelChangeOrder = (event, columnId) => {
    setOrder({
      // If the sorting column has changed
      order: columnId !== order.orderBy || order.order === 'desc' ? 'asc' : 'desc',
      orderBy: columnId,
    })
  }

  return (
    <BasePageContainer>
      <BasePageToolbar
        title={'Users Adminstration'}
        actionsComponent={UsersListAction}
      ></BasePageToolbar>
      <Grid container spacing={3}>
        <Grid item xs={12}>
          {status === 'error' && (
            <Alert severity="error">
              <AlertTitle>Error</AlertTitle>
              {statusMessage}
            </Alert>
          )}

          {status !== 'error' && (
            <TableContainer component={Paper}>

                <TableHead>
                  <TableRow>
                    {tableColumns.map(column => (
                      <TableCell key={column.id}>
                        {/* Sortable */}
                        {column.isSortable && (
                          <TableSortLabel
                            active={order.orderBy === column.id}
                            direction={order.orderBy === column.id ? order.order : 'asc'}
                            onClick={event => handelChangeOrder(event, column.id)}
                          >
                            {column.label}
                          </TableSortLabel>
                        )}
                        {/* Non-sortable */}
                        {!column.isSortable && column.label}
                      </TableCell>
                    ))}
                    <TableCell>Actions</TableCell>
                  </TableRow>
                </TableHead>
                <TableBody>
                  <UsersListTableItems
                    users={status === 'loading' ? [] : users}
                    rowsPerPage={rowsPerPage}
                  />
                </TableBody>
                <TableFooter>
                  <TableRow>
                    <BaseTablePagination
                      page={page}
                      rowsPerPage={rowsPerPage}
                      count={count}
                      order={order}
                      onChangePage={handleChangePage}
                      onChangeRowsPerPage={handleChangeRowsPerPage}
                    />
                  </TableRow>
                </TableFooter>

            </TableContainer>
          )}
        </Grid>
      </Grid>
    </BasePageContainer>
  )
}

const useStyles = makeStyles(theme => ({
  root: {
    flexShrink: 0,
    marginLeft: theme.spacing(2.5),
  },
}))

UsersList.propTypes = {}

export default UsersList
<|endoftext|>


<commit_before>export default class WebpackNotifier {
	constructor({ onStart = ()=>{}, onFinish = (report)=>{} } = {}) {
		this.onStart = onStart
		this.onFinish = onFinish
	}

	apply(compiler) {
		let throttle = this.createThrottle()

		compiler.plugin('invalid', () => {throttle.onStart()})
		compiler.plugin('compile', () => {throttle.onStart()})
		compiler.plugin('done', stats => {
			const { errors, warnings } = stats.compilation
			throttle.onStop({ warnings, errors })
			throttle = this.createThrottle()
		})
		compiler.plugin('failed', err => {console.error('error', err)})
	}

	createThrottle() {
		let startResolver = this.createResolver()
		startResolver.promise.then(() => this.onStart())
		let stopResolver = this.createResolver(startResolver.promise)
		stopResolver.promise.then(report => this.onFinish(report))
		return {
			onStart: startResolver.resolve,
			onStop: stopResolver.resolve,
		}
	}

	createResolver(inboundPromise = Promise.resolve()) {
		let resolve
		let outboundPromise = new Promise(r => {resolve = r})
		let promise = inboundPromise.then(() => outboundPromise)
		return { resolve, promise }
	}
}
<commit_msg>Errors in the onStart or onFinish scripts are now logged to the console<commit_after>export default class WebpackNotifier {
	constructor({ onStart = ()=>{}, onFinish = (report)=>{} } = {}) {
		this.onStart = onStart
		this.onFinish = onFinish
	}

	apply(compiler) {
		let throttle = this.createThrottle()

		compiler.plugin('invalid', () => {throttle.onStart()})
		compiler.plugin('compile', () => {throttle.onStart()})
		compiler.plugin('done', stats => {
			const { errors, warnings } = stats.compilation
			throttle.onStop({ warnings, errors })
			throttle = this.createThrottle()
		})
		compiler.plugin('failed', err => {console.error('error', err)})
	}

	createThrottle() {
		let startResolver = this.createResolver()
		startResolver.promise
			.then(() => this.onStart())
			.catch(error => {
				console.error('onStart failed: ' + (error.stack || error.message || error))
				// This promise never resolves. This is on purpose
				return new Promise(() => {})
			})
		let stopResolver = this.createResolver(startResolver.promise)
		stopResolver.promise
			.then(report => this.onFinish(report))
			.catch(error => console.error('onFinish failed: ' + (error.stack || error.message || error)))
		return {
			onStart: startResolver.resolve,
			onStop: stopResolver.resolve,
		}
	}

	createResolver(inboundPromise = Promise.resolve()) {
		let resolve
		let outboundPromise = new Promise(r => {resolve = r})
		let promise = inboundPromise.then(() => outboundPromise)
		return { resolve, promise }
	}
}
<|endoftext|>


<commit_before>Elm.Native.JavaScript = {};
Elm.Native.JavaScript.make = function(elm) {
  elm.Native = elm.Native || {};
  elm.Native.JavaScript = elm.Native.JavaScript || {};
  if (elm.Native.JavaScript.values) return elm.Native.JavaScript.values;

  var List = Elm.Native.List.make(elm);
  var Render = ElmRuntime.use(ElmRuntime.Render.Element);

  function fromJS(v) {
      var type = typeof v;
      if (type === 'number' ) return v;
      if (type === 'boolean') return v;
      if (type === 'string' ) return List.fromArray(v);
      if (v instanceof Array) {
          var arr = [];
          var len = v.length;
          for (var i = 0; i < len; ++i) {
              var x = fromJS(v[i]);
              if (x !== null) arr.push(x);
          }
          return List.fromArray(arr);
      }
      if (type === 'object') {
          var rec = { _:{} };
          for (var f in v) {
              var x = fromJS(v[f]);
              if (x !== null) rec[f] = x;
          }
          return rec;
      }
      return null;
  }

  function toJS(v) {
      var type = typeof v;
      if (type === 'number' || type === 'boolean') return v;
      if (type === 'object' && '_' in v) {
          var obj = {};
          for (var k in v) {
              var x = toJS(v[k]);
              if (x !== null) obj[k] = x;
          }
          return obj;
      }
      if (type === 'object' && (v.ctor === '::' || v.ctor === '[]')) {
          var array = List.toArray(v);
          if (typeof array[0] === 'string') {
              array = array.join('');
          } else {
              for (var i = array.length; i--; ) {
                  array[i] = toJS(array[i]);
              }
          }
          return array;
      }
      return null;
  }

  function fromRecord(r) {
      if (typeof r === 'object' && '_' in r) {
          return toJS(r);
      }
      throw new Error("'fromRecord' must be called on a record.");
  }

  function id(n) { return n; }

  function toElement(w,h,domNode) {
      return A3( newElement, w, h, {
              ctor: 'Custom',
              type: 'DomNode',
              render: function(node) { return node; },
              update: function(node,oldNode,newNode) {
                  if (node === newNode) return;
                  node.parentNode.replaceChild(newNode, node);
              },
              model: domNode
          });
  }

  function fromElement(element) {
      return Render.render(element);
  }

  return elm.Native.JavaScript.values = {
      toInt      : function(n) { return n|0; },
      toFloat    : function(n) { return +n; },
      toBool     : id,
      toString   : id,
      toList     : List.fromArray,
      fromString : id,
      fromList   : List.toArray,
      fromInt    : id,
      fromFloat  : id,
      fromBool   : id,

      toElement   : toElement,
      fromElement : fromElement,
      toRecord    : fromJS,
      fromRecord  : fromRecord
  };

};
<commit_msg>Fix string conversion problems<commit_after>Elm.Native.JavaScript = {};
Elm.Native.JavaScript.make = function(elm) {
  elm.Native = elm.Native || {};
  elm.Native.JavaScript = elm.Native.JavaScript || {};
  if (elm.Native.JavaScript.values) return elm.Native.JavaScript.values;

  var List = Elm.Native.List.make(elm);
  var Render = ElmRuntime.use(ElmRuntime.Render.Element);

  function fromJS(v) {
      var type = typeof v;
      if (type === 'number' ) return v;
      if (type === 'boolean') return v;
      if (type === 'string' ) return v;
      if (v instanceof Array) {
          var arr = [];
          var len = v.length;
          for (var i = 0; i < len; ++i) {
              var x = fromJS(v[i]);
              if (x !== null) arr.push(x);
          }
          return List.fromArray(arr);
      }
      if (type === 'object') {
          var rec = { _:{} };
          for (var f in v) {
              var x = fromJS(v[f]);
              if (x !== null) rec[f] = x;
          }
          return rec;
      }
      return null;
  }

  function toJS(v) {
      var type = typeof v;
      if (type === 'number' || type === 'boolean') return v;
      if (type === 'object' && '_' in v) {
          var obj = {};
          for (var k in v) {
              var x = toJS(v[k]);
              if (x !== null) obj[k] = x;
          }
          return obj;
      }
      if (type === 'object' && (v.ctor === '::' || v.ctor === '[]')) {
          var array = List.toArray(v);
          for (var i = array.length; i--; ) {
              array[i] = toJS(array[i]);
          }
          return array;
      }
      return null;
  }

  function fromRecord(r) {
      if (typeof r === 'object' && '_' in r) {
          return toJS(r);
      }
      throw new Error("'fromRecord' must be called on a record.");
  }

  function id(n) { return n; }

  function toElement(w,h,domNode) {
      return A3( newElement, w, h, {
              ctor: 'Custom',
              type: 'DomNode',
              render: function(node) { return node; },
              update: function(node,oldNode,newNode) {
                  if (node === newNode) return;
                  node.parentNode.replaceChild(newNode, node);
              },
              model: domNode
          });
  }

  function fromElement(element) {
      return Render.render(element);
  }

  return elm.Native.JavaScript.values = {
      toInt      : function(n) { return n|0; },
      toFloat    : function(n) { return +n; },
      toBool     : id,
      toString   : id,
      toList     : List.fromArray,
      fromString : id,
      fromList   : List.toArray,
      fromInt    : id,
      fromFloat  : id,
      fromBool   : id,

      toElement   : toElement,
      fromElement : fromElement,
      toRecord    : fromJS,
      fromRecord  : fromRecord
  };

};
<|endoftext|>