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

/**
 * @fileoverview The initial database core.
 *
 * It's designed to be read into the server (see CodeCity/server) at
 * initial startup time.  It should not be needed after that (the
 * server should save all internal state to a checkpoint file, and
 * subsequent server starts will restore that state), but it is
 * intended to written in such a way that it should be possible to
 * re-run it to update an existing core with newer versions of the
 * core objects / functions.  Doing so is at your own risk however!
 *
 * @author cpcallen@google.com (Christopher Allen)
 */
'use strict'; // For testing with node.js

// Global scope declarations:
var $ = Object.create(Object.prototype);

(function() {
  // Core build options:
  var forceAll = false; // Overwrite existing?

  /**
   * Log a message to the console.
   * @param {string} msg Message to show.
   */
  function log(msg) {
    // FIXME: this implementation for testing in Node only.
    console.log(msg);
  }

  /**
   * Verify that obj has a property obj[key], with specified
   * enumerability, whose value is an object with prototype proto.
   * Existing values / prototype will not be overwritten unless force
   * or forceAll is true, but an error will be reported instead.
   * @param {Object} obj The object to which to add a property.
   * @param {string} key The property key to add.
   * @param {Object} proto The desired prototype for obj[key].
   * @param {bool=} enumerable The property should be enumerable.
   * @param {bool=} force Overwrite existing?
   */
  function make(obj, key, proto, enumerable, force) {
    var desc = Object.getOwnPropertyDescriptor(obj, key);
    if (desc === undefined) {
      desc = { value: Object.create(proto), enumerable,
               writable: true, configurable: true };
    } else if (force === undefined ? forceAll : force) {
      if (typeof obj !== 'object' && typeof obj !== 'function' ||
          obj === null) {
        desc.value = Object.create(proto);
      }
      Object.setPrototypeOf(desc.value, proto);
      desc.enumerable = enumerable;
    } else {
      if (typeof obj !== 'object' && typeof obj !== 'function' ||
          obj === null) {
        log(obj + '.' + key + ' is not an object.');
        return; // Abort: can't (usefully) set prototype of primitive
      }
      if (Object.getPrototypeOf(desc.value) !== proto) {
        log(obj + '.' + key + ' has prototype ' +
            Object.getPrototypeOf(desc.value));
      }
      if (desc.enumerable !== enumerable) {
        log(obj + '.' + key + ' enumerability is ' + desc.enumerable);
      }
    }
    Object.defineProperty(obj, key, desc);
  }

  /**
   * Set a obj[key] to value (and make it enumerable iff enumerble is
   * true) but don't modify an existing property unless force or
   * global forceAll is true (force will override forceAll if given).
   * @param {Object} obj The object to which to add a property.
   * @param {string} key The property key to add.
   * @param {*} value The value to add as obj[key].
   * @param {bool=} enumerable The property should be enumerable.
   * @param {bool=} force Overwrite existing.
   */
  function set(obj, key, value, enumerable, force) {
    var desc = Object.getOwnPropertyDescriptor(obj, key);
    if (desc === undefined || (force === undefined ? forceAll : force)) {
      desc = { value, enumerable, writable: true, configurable: true };
    } else {
      if (desc.value !== value) {
        log(obj + '.' + key + ' === ' + value + ' (expected: ' + value + ').');
      }
      if (desc.enumerable !== enumerable) {
        log(obj + '.' + key + ' enumerability is ' + desc.enumerable);
      }
    }
    Object.defineProperty(obj, key, desc);
  }

  /*******************************************************************
   * $.object
   */
  set($, 'object', Object.prototype, true);

  /*******************************************************************
   * $.physical
   */
  make($, 'physical', $.object, true);

  set($.physical, 'name', 'Physical object prototype', true);
  set($.physical, 'location', null, true);
  set($.physical, 'contents_', undefined, true); // later vetted into existance

  set($.physical, 'contents', function() {
    // Return the contents of this object.  This is for VR purposes,
    // and descendents of $.physical can override it to 'hide' certain
    // objects from their contents.
    $.physical_vet(this);
    return this.contents_;
  });

  set($.physical, 'moveto', function(dest) {
    // Move this physical object to dest
    //
    // This is based loosely on moo.ca's #3:moveto and #102:bf_move
    // Vet obj, src and dest (also does type check):
    $.physical_vet(this);
    var src = this.location;
    src === null || $.physical_vet(src);
    dest === null || $.physical_vet(dest);

    // FIXME: permission checks go here.
    // Do moveable and accept checks:
    if (!this.moveable(dest)) {
      var destName = (dest === null ? 'null' : dest.name);
      throw Error(this.name + ' declined to move to ' + destName);
    }
    if (dest !== null && !dest.accept(this)) {
      throw Error(dest.name + ' refused ' + this.name);
    }

    // Check proposed containment hierarchy for cycles.  We do this
    // after the movable and accept checks because we don't want those
    // checks in case they muck arounnd with the containment
    // hierarchy.
    for(var loc = dest; loc !== null; loc = loc.location) {
      if (loc === this) {
        throw Error("Can't put " + this.name + " inside itself.");
      }
    }

    // Do move:
    var i;
    if ($.physical.isPrototypeOf(src)) {
      while ((i = src.contents_.indexOf(this)) >= 0) {
        src.contents_.splice(i, 1);
      }
    }
    this.location = dest;
    dest === null || dest.contents_.push(this);

    // FIXME: call exitfunc
    // FIXME: call enterfunc
  });

  set($.physical, 'moveable', function(obj) {
    // Returns true iff this is willing to accept obj into its contents.
    return false;
  });

  set($.physical, 'accept', function(obj) {
    // Returns true iff this is willing to accept obj into its
    // contents.  This function should only be called by
    // $.physical.moveto(), and might cause some action to occur as a
    // result.  Other callers interested in testing to see if a move will
    // be accepted should call .acceptable() instead.
    return this.acceptable(obj);
  });

  set($.physical, 'acceptable', function(obj) {
    // Returns true iff this is willing to accept obj into its
    // contents.  By default .accept() delegates this decision to this
    // function.  This function (and any overrides) MUST NOT cause any
    // observable action to occur.
    return false;
  });

  set($.physical, 'contains', function(obj) {
    // Returns true iff obj is located in this.
    $.physical_vet(this);
    $.physical_vet(obj);
    for (var loc = obj.location; loc !== null; loc = loc.location) {
      if (loc === this) {
        return true;
      }
      $.physical_vet(loc);
    }
    return false;
  });

  // FIXME: should be a non-overridable method on $.physical:
  set($, 'physical_vet', function(obj) {
    // Verify the integrity of a $.physical object.
    if (!$.physical.isPrototypeOf(obj)) {
      throw TypeError('Not a $.physical object');
    }
    // They can only be located in another $.physical object - and
    // that object must obj in its contents:
    if (!$.physical.isPrototypeOf(obj.location) ||
        obj.contents_.indexOf(obj) === -1) {
      obj.location === null;
    }
    // obj.contents_ must be an array unique to obj (not inherited):
    if (!obj.hasOwnProperty('contents_') || !Array.isArray(obj.contents_) ||
        !obj.contents_.for === obj) {
      // FIXME: attributes?
      obj.contents_ = [];
      Object.defineProperty(obj.contents_, 'for', { value: obj });
    }
    // obj.contents_ must not contain any duplicates, non-$.physical
    // objects, objects not located in obj:
    for (var i = 0; i < obj.contents_.length; i++) {
      if (obj.contents_.lastIndexOf(obj.contents_[i]) !== i ||
          !$.physical.isPrototypeOf(obj.contents_[i]) ||
          obj.contents_[i].location !== obj) {
        obj.contents_.splice(i, 1);
      }
    }
    // FIXME: check for circular containment?
  });

})();

if (typeof module !== 'undefined') { // Node.js
  module.exports = $;
}
<commit_msg>Add check for actually-an-object to $.physical_vet<commit_after>/**
 * @license
 * Code City: Database Core
 *
 * Copyright 2017 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview The initial database core.
 *
 * It's designed to be read into the server (see CodeCity/server) at
 * initial startup time.  It should not be needed after that (the
 * server should save all internal state to a checkpoint file, and
 * subsequent server starts will restore that state), but it is
 * intended to written in such a way that it should be possible to
 * re-run it to update an existing core with newer versions of the
 * core objects / functions.  Doing so is at your own risk however!
 *
 * @author cpcallen@google.com (Christopher Allen)
 */
'use strict'; // For testing with node.js

// Global scope declarations:
var $ = Object.create(Object.prototype);

(function() {
  // Core build options:
  var forceAll = false; // Overwrite existing?

  /**
   * Log a message to the console.
   * @param {string} msg Message to show.
   */
  function log(msg) {
    // FIXME: this implementation for testing in Node only.
    console.log(msg);
  }

  /**
   * Verify that obj has a property obj[key], with specified
   * enumerability, whose value is an object with prototype proto.
   * Existing values / prototype will not be overwritten unless force
   * or forceAll is true, but an error will be reported instead.
   * @param {Object} obj The object to which to add a property.
   * @param {string} key The property key to add.
   * @param {Object} proto The desired prototype for obj[key].
   * @param {bool=} enumerable The property should be enumerable.
   * @param {bool=} force Overwrite existing?
   */
  function make(obj, key, proto, enumerable, force) {
    var desc = Object.getOwnPropertyDescriptor(obj, key);
    if (desc === undefined) {
      desc = { value: Object.create(proto), enumerable,
               writable: true, configurable: true };
    } else if (force === undefined ? forceAll : force) {
      if (typeof obj !== 'object' && typeof obj !== 'function' ||
          obj === null) {
        desc.value = Object.create(proto);
      }
      Object.setPrototypeOf(desc.value, proto);
      desc.enumerable = enumerable;
    } else {
      if (typeof obj !== 'object' && typeof obj !== 'function' ||
          obj === null) {
        log(obj + '.' + key + ' is not an object.');
        return; // Abort: can't (usefully) set prototype of primitive
      }
      if (Object.getPrototypeOf(desc.value) !== proto) {
        log(obj + '.' + key + ' has prototype ' +
            Object.getPrototypeOf(desc.value));
      }
      if (desc.enumerable !== enumerable) {
        log(obj + '.' + key + ' enumerability is ' + desc.enumerable);
      }
    }
    Object.defineProperty(obj, key, desc);
  }

  /**
   * Set a obj[key] to value (and make it enumerable iff enumerble is
   * true) but don't modify an existing property unless force or
   * global forceAll is true (force will override forceAll if given).
   * @param {Object} obj The object to which to add a property.
   * @param {string} key The property key to add.
   * @param {*} value The value to add as obj[key].
   * @param {bool=} enumerable The property should be enumerable.
   * @param {bool=} force Overwrite existing.
   */
  function set(obj, key, value, enumerable, force) {
    var desc = Object.getOwnPropertyDescriptor(obj, key);
    if (desc === undefined || (force === undefined ? forceAll : force)) {
      desc = { value, enumerable, writable: true, configurable: true };
    } else {
      if (desc.value !== value) {
        log(obj + '.' + key + ' === ' + value + ' (expected: ' + value + ').');
      }
      if (desc.enumerable !== enumerable) {
        log(obj + '.' + key + ' enumerability is ' + desc.enumerable);
      }
    }
    Object.defineProperty(obj, key, desc);
  }

  /*******************************************************************
   * $.object
   */
  set($, 'object', Object.prototype, true);

  /*******************************************************************
   * $.physical
   */
  make($, 'physical', $.object, true);

  set($.physical, 'name', 'Physical object prototype', true);
  set($.physical, 'location', null, true);
  set($.physical, 'contents_', undefined, true); // later vetted into existance

  set($.physical, 'contents', function() {
    // Return the contents of this object.  This is for VR purposes,
    // and descendents of $.physical can override it to 'hide' certain
    // objects from their contents.
    $.physical_vet(this);
    return this.contents_;
  });

  set($.physical, 'moveto', function(dest) {
    // Move this physical object to dest
    //
    // This is based loosely on moo.ca's #3:moveto and #102:bf_move
    // Vet obj, src and dest (also does type check):
    $.physical_vet(this);
    var src = this.location;
    src === null || $.physical_vet(src);
    dest === null || $.physical_vet(dest);

    // FIXME: permission checks go here.
    // Do moveable and accept checks:
    if (!this.moveable(dest)) {
      var destName = (dest === null ? 'null' : dest.name);
      throw Error(this.name + ' declined to move to ' + destName);
    }
    if (dest !== null && !dest.accept(this)) {
      throw Error(dest.name + ' refused ' + this.name);
    }

    // Check proposed containment hierarchy for cycles.  We do this
    // after the movable and accept checks because we don't want those
    // checks in case they muck arounnd with the containment
    // hierarchy.
    for(var loc = dest; loc !== null; loc = loc.location) {
      if (loc === this) {
        throw Error("Can't put " + this.name + " inside itself.");
      }
    }

    // Do move:
    var i;
    if ($.physical.isPrototypeOf(src)) {
      while ((i = src.contents_.indexOf(this)) >= 0) {
        src.contents_.splice(i, 1);
      }
    }
    this.location = dest;
    dest === null || dest.contents_.push(this);

    // FIXME: call exitfunc
    // FIXME: call enterfunc
  });

  set($.physical, 'moveable', function(obj) {
    // Returns true iff this is willing to accept obj into its contents.
    return false;
  });

  set($.physical, 'accept', function(obj) {
    // Returns true iff this is willing to accept obj into its
    // contents.  This function should only be called by
    // $.physical.moveto(), and might cause some action to occur as a
    // result.  Other callers interested in testing to see if a move will
    // be accepted should call .acceptable() instead.
    return this.acceptable(obj);
  });

  set($.physical, 'acceptable', function(obj) {
    // Returns true iff this is willing to accept obj into its
    // contents.  By default .accept() delegates this decision to this
    // function.  This function (and any overrides) MUST NOT cause any
    // observable action to occur.
    return false;
  });

  set($.physical, 'contains', function(obj) {
    // Returns true iff obj is located in this.
    $.physical_vet(this);
    $.physical_vet(obj);
    for (var loc = obj.location; loc !== null; loc = loc.location) {
      if (loc === this) {
        return true;
      }
      $.physical_vet(loc);
    }
    return false;
  });

  // FIXME: should be a non-overridable method on $.physical:
  set($, 'physical_vet', function(obj) {
    // Verify the integrity of a $.physical object.
    if (typeof obj !== 'object' || !$.physical.isPrototypeOf(obj)) {
      throw TypeError('Not a $.physical object');
    }
    // They can only be located in another $.physical object - and
    // that object must obj in its contents:
    if (!$.physical.isPrototypeOf(obj.location) ||
        obj.contents_.indexOf(obj) === -1) {
      obj.location === null;
    }
    // obj.contents_ must be an array unique to obj (not inherited):
    if (!obj.hasOwnProperty('contents_') || !Array.isArray(obj.contents_) ||
        !obj.contents_.for === obj) {
      // FIXME: attributes?
      obj.contents_ = [];
      Object.defineProperty(obj.contents_, 'for', { value: obj });
    }
    // obj.contents_ must not contain any duplicates, non-$.physical
    // objects, objects not located in obj:
    for (var i = 0; i < obj.contents_.length; i++) {
      if (obj.contents_.lastIndexOf(obj.contents_[i]) !== i ||
          !$.physical.isPrototypeOf(obj.contents_[i]) ||
          obj.contents_[i].location !== obj) {
        obj.contents_.splice(i, 1);
      }
    }
    // FIXME: check for circular containment?
  });

})();

if (typeof module !== 'undefined') { // Node.js
  module.exports = $;
}
<|endoftext|>


<commit_before>function carleonMpdController ($scope, $q, $translatePartialLoader, $translate, $interval, angharadConfig, carleonFactory, carleonMpdFactory, sagremorService, sagremorParams, toaster) {
    var ctrl = this;

    this.mpd = {};
    this.playlists = [];
    this.selectedPlaylist = false;
    this._interval = null;

    function init() {
		var promises = [
			carleonMpdFactory.getMpdStatus(ctrl.element.name),
			carleonMpdFactory.getMpdPlaylists(ctrl.element.name)
		];

		$q.all(promises).then(function (responses) {
			ctrl.mpd = responses[0];
			ctrl.playlists = responses[1];
			if (ctrl.mpd.state === "play" || ctrl.mpd.state === "pause") {
				startRefreshStatusInterval();
			}
		}, function () {
			toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_load_error", {name: ctrl.element.name}));
		});
    }

    function startRefreshStatusInterval() {
		ctrl._interval = $interval(function () {
			ctrl.refreshStatus();
		}, 1000 * 10);
	}

    this.refreshStatus = function() {
		carleonMpdFactory.getMpdStatus(ctrl.element.name).then(function (response) {
			ctrl.mpd = response;
			if ((ctrl.mpd.state === "play" || ctrl.mpd.state === "pause") && ctrl._interval == null) {
				startRefreshStatusInterval();
			} else if (ctrl._interval != null && ctrl.mpd.state !== "play" && ctrl.mpd.state !== "pause") {
				$interval.cancel(ctrl._interval);
				ctrl._interval = null;
			}
		}, function () {
			toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_load_error", {name: ctrl.element.name}));
		});
	};

    this.displayVolume = function () {
		return ctrl.mpd.volume + "%";
	};

	this.setVolume = function (delta) {
		var newVolume = ctrl.mpd.volume + delta;
		if (newVolume >= 0 && newVolume <= 100) {
			carleonMpdFactory.setMpdVolume(ctrl.element.name, newVolume).then(function () {
				ctrl.mpd.volume = newVolume;
			}, function () {
				toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_volume_error"));
			});
		}
	};

	this.displayVolumeCommand = function (delta) {
		return (delta>0?"+":"") + delta + " %"
	};

	this.setAction = function (action) {
		carleonMpdFactory.setMpdAction(ctrl.element.name, action).then(function () {
			ctrl.refreshStatus();
		}, function () {
			toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_volume_error"));
		});
	};

	this.loadPlaylist = function () {
		if (!!ctrl.selectedPlaylist) {
			carleonMpdFactory.loadMpdPlaylist(ctrl.element.name, ctrl.selectedPlaylist).then(function () {
				ctrl.refreshStatus();
			}, function () {
				toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_load_playlist_error"));
			});
		}
	};

	$scope.$on("refreshCarleonServices", function () {
		ctrl.refreshStatus();
	});

	$scope.$on("$destroy", function() {
		$interval.cancel(ctrl._interval);
	});

    init();
}

angular.module("sagremorApp").component("serviceMpd", {
    templateUrl: "components/carleonMpd/carleonMpd.template.html",
    controller: carleonMpdController,
    bindings: {
        element: "="
    }
})
.factory("carleonMpdFactory", function($http, $uibModal, $translate, toaster, angharadConfig, angharadBackendService, sagremorConfirm, sharedData) {
    var urlBase = angharadConfig.baseUrl + angharadConfig.prefixCarleon;
    var carleonMpdFactory = {};

	/* Mpd service */
    carleonMpdFactory.getMpdServiceList = function () {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/");
    };

    carleonMpdFactory.getMpdService = function (name) {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/" + name);
    };

    carleonMpdFactory.addMpdService = function (mpdService) {
        return angharadBackendService.httpRequest("POST", urlBase + "service-mpd/", mpdService);
    };

    carleonMpdFactory.setMpdService = function (name, mpdService) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name, mpdService);
    };

    carleonMpdFactory.removeMpdService = function (name) {
        return angharadBackendService.httpRequest("DELETE", urlBase + "service-mpd/" + name);
    };

    carleonMpdFactory.getMpdStatus = function (name) {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/" + name + "/status");
    };

    carleonMpdFactory.getMpdPlaylists = function (name) {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/" + name + "/playlists");
    };

    carleonMpdFactory.setMpdAction = function (name, action) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name + "/action/" + action);
    };

    carleonMpdFactory.loadMpdPlaylist = function (name, playlist) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name + "/playlist/" + playlist);
    };

    carleonMpdFactory.setMpdVolume = function (name, volume) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name + "/volume/" + volume);
    };

	carleonMpdFactory.addService = function () {
		var modalInstance = $uibModal.open({
			animation: true,
			templateUrl: "components/carleonMpd/carleonMpd.modal.html",
			controller: "carleonMpdModalCtrl",
			controllerAs: "carleonMpdModalCtrl",
			size: "md",
			resolve: {
				mpd: function () {
					return null;
				}
			}
		});
	};

	carleonMpdFactory.editService = function (service) {
		var modalInstance = $uibModal.open({
			animation: true,
			templateUrl: "components/carleonMpd/carleonMpd.modal.html",
			controller: "carleonMpdModalCtrl",
			controllerAs: "carleonMpdModalCtrl",
			size: "md",
			resolve: {
				mpd: function () {
					return service;
				}
			}
		});
	};

	carleonMpdFactory.removeService = function (service) {
		return sagremorConfirm.open($translate.instant("carleon_mpd_remove"), $translate.instant("carleon_mpd_confirm")).then(function () {
			return carleonMpdFactory.removeMpdService(service.name).then(function () {
				var injector = sharedData.get("carleonServices", "service-mpd");
				_.remove(injector.element, function (element) {
					return element.name === service.name;
				});
				sharedData.add("carleonServices", "service-mpd", injector);
				toaster.pop("success", $translate.instant("carleon_mpd_remove"), $translate.instant("carleon_mpd_remove_success"));
			}, function () {
				toaster.pop("error", $translate.instant("carleon_mpd_remove"), $translate.instant("carleon_mpd_remove_error"));
			});
		});
	};

    return carleonMpdFactory;

})
.config(function run($translatePartialLoaderProvider) {
	$translatePartialLoaderProvider.addPart("carleonMpd");
})
.run(function(sagGenericInjectorManager, carleonMpdFactory) {
    sagGenericInjectorManager.add({
        type: "service-mpd",
        directive: "service-mpd",
        groupTitle: "carleon_mpd_title",
        service: carleonMpdFactory,
        carleonService: true,
        size: 2,
        leftMenu: {
			title: "carleon_music_title",
			target: "carleonMpd"
		},
		widgetHeight: 4,
		widgetWidth: 3,
		icon: "music",
		commands: {
			action: {
				title: "carleon_mpd_command_action_title",
				parameters: {
					action: "carleon_mpd_command_action_parameter_action_title",
				},
				result: {
					type: "boolean",
					title: "carleon_mpd_command_action_result_title"
				}
			},
			load_playlist: {
				title: "carleon_mpd_command_playlist_title",
				parameters: {
					playlist: "carleon_mpd_command_playlist_parameter_plsylist_title",
				},
				result: {
					type: "boolean",
					title: "carleon_mpd_command_playlist_result_title"
				}
			},
			set_volume: {
				title: "carleon_mpd_command_volume_title",
				parameters: {
					volume: "carleon_mpd_command_volume_parameter_volume_title",
				},
				result: {
					type: "boolean",
					title: "carleon_mpd_command_volume_result_title"
				}
			}
		}
    });
});
<commit_msg>Refresh status only when window has focus<commit_after>function carleonMpdController ($scope, $q, $window, $translatePartialLoader, $translate, $interval, angharadConfig, carleonFactory, carleonMpdFactory, sagremorService, sagremorParams, toaster) {
    var ctrl = this;

    this.mpd = {};
    this.playlists = [];
    this.selectedPlaylist = false;
    this._interval = null;
    this.hasFocus = true;

    function init() {
		var promises = [
			carleonMpdFactory.getMpdStatus(ctrl.element.name),
			carleonMpdFactory.getMpdPlaylists(ctrl.element.name)
		];

		$q.all(promises).then(function (responses) {
			ctrl.mpd = responses[0];
			ctrl.playlists = responses[1];
			if (ctrl.mpd.state === "play" || ctrl.mpd.state === "pause") {
				startRefreshStatusInterval();
			}
		}, function () {
			toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_load_error", {name: ctrl.element.name}));
		});
    }

    function startRefreshStatusInterval() {
		ctrl._interval = $interval(function () {
			if (ctrl.hasFocus) {
				ctrl.refreshStatus();
			}
		}, 1000 * 10);
	}

    this.refreshStatus = function() {
		carleonMpdFactory.getMpdStatus(ctrl.element.name).then(function (response) {
			ctrl.mpd = response;
			if ((ctrl.mpd.state === "play" || ctrl.mpd.state === "pause") && ctrl._interval == null) {
				startRefreshStatusInterval();
			} else if (ctrl._interval != null && ctrl.mpd.state !== "play" && ctrl.mpd.state !== "pause") {
				$interval.cancel(ctrl._interval);
				ctrl._interval = null;
			}
		}, function () {
			toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_load_error", {name: ctrl.element.name}));
		});
	};

    this.displayVolume = function () {
		return ctrl.mpd.volume + "%";
	};

	this.setVolume = function (delta) {
		var newVolume = ctrl.mpd.volume + delta;
		if (newVolume >= 0 && newVolume <= 100) {
			carleonMpdFactory.setMpdVolume(ctrl.element.name, newVolume).then(function () {
				ctrl.mpd.volume = newVolume;
			}, function () {
				toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_volume_error"));
			});
		}
	};

	this.displayVolumeCommand = function (delta) {
		return (delta>0?"+":"") + delta + " %"
	};

	this.setAction = function (action) {
		carleonMpdFactory.setMpdAction(ctrl.element.name, action).then(function () {
			ctrl.refreshStatus();
		}, function () {
			toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_volume_error"));
		});
	};

	this.loadPlaylist = function () {
		if (!!ctrl.selectedPlaylist) {
			carleonMpdFactory.loadMpdPlaylist(ctrl.element.name, ctrl.selectedPlaylist).then(function () {
				ctrl.refreshStatus();
			}, function () {
				toaster.pop("error", $translate.instant("carleon_mpd"), $translate.instant("carleon_mpd_load_playlist_error"));
			});
		}
	};

	$scope.$on("refreshCarleonServices", function () {
		ctrl.refreshStatus();
	});

	$scope.$on("$destroy", function() {
		$interval.cancel(ctrl._interval);
	});

	$window.onblur = function() {
		ctrl.hasFocus = false;
	};

	$window.onfocus = function() {
		ctrl.hasFocus = true;
	};

    init();
}

angular.module("sagremorApp").component("serviceMpd", {
    templateUrl: "components/carleonMpd/carleonMpd.template.html",
    controller: carleonMpdController,
    bindings: {
        element: "="
    }
})
.factory("carleonMpdFactory", function($http, $uibModal, $translate, toaster, angharadConfig, angharadBackendService, sagremorConfirm, sharedData) {
    var urlBase = angharadConfig.baseUrl + angharadConfig.prefixCarleon;
    var carleonMpdFactory = {};

	/* Mpd service */
    carleonMpdFactory.getMpdServiceList = function () {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/");
    };

    carleonMpdFactory.getMpdService = function (name) {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/" + name);
    };

    carleonMpdFactory.addMpdService = function (mpdService) {
        return angharadBackendService.httpRequest("POST", urlBase + "service-mpd/", mpdService);
    };

    carleonMpdFactory.setMpdService = function (name, mpdService) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name, mpdService);
    };

    carleonMpdFactory.removeMpdService = function (name) {
        return angharadBackendService.httpRequest("DELETE", urlBase + "service-mpd/" + name);
    };

    carleonMpdFactory.getMpdStatus = function (name) {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/" + name + "/status");
    };

    carleonMpdFactory.getMpdPlaylists = function (name) {
        return angharadBackendService.httpRequest("GET", urlBase + "service-mpd/" + name + "/playlists");
    };

    carleonMpdFactory.setMpdAction = function (name, action) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name + "/action/" + action);
    };

    carleonMpdFactory.loadMpdPlaylist = function (name, playlist) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name + "/playlist/" + playlist);
    };

    carleonMpdFactory.setMpdVolume = function (name, volume) {
        return angharadBackendService.httpRequest("PUT", urlBase + "service-mpd/" + name + "/volume/" + volume);
    };

	carleonMpdFactory.addService = function () {
		var modalInstance = $uibModal.open({
			animation: true,
			templateUrl: "components/carleonMpd/carleonMpd.modal.html",
			controller: "carleonMpdModalCtrl",
			controllerAs: "carleonMpdModalCtrl",
			size: "md",
			resolve: {
				mpd: function () {
					return null;
				}
			}
		});
	};

	carleonMpdFactory.editService = function (service) {
		var modalInstance = $uibModal.open({
			animation: true,
			templateUrl: "components/carleonMpd/carleonMpd.modal.html",
			controller: "carleonMpdModalCtrl",
			controllerAs: "carleonMpdModalCtrl",
			size: "md",
			resolve: {
				mpd: function () {
					return service;
				}
			}
		});
	};

	carleonMpdFactory.removeService = function (service) {
		return sagremorConfirm.open($translate.instant("carleon_mpd_remove"), $translate.instant("carleon_mpd_confirm")).then(function () {
			return carleonMpdFactory.removeMpdService(service.name).then(function () {
				var injector = sharedData.get("carleonServices", "service-mpd");
				_.remove(injector.element, function (element) {
					return element.name === service.name;
				});
				sharedData.add("carleonServices", "service-mpd", injector);
				toaster.pop("success", $translate.instant("carleon_mpd_remove"), $translate.instant("carleon_mpd_remove_success"));
			}, function () {
				toaster.pop("error", $translate.instant("carleon_mpd_remove"), $translate.instant("carleon_mpd_remove_error"));
			});
		});
	};

    return carleonMpdFactory;

})
.config(function run($translatePartialLoaderProvider) {
	$translatePartialLoaderProvider.addPart("carleonMpd");
})
.run(function(sagGenericInjectorManager, carleonMpdFactory) {
    sagGenericInjectorManager.add({
        type: "service-mpd",
        directive: "service-mpd",
        groupTitle: "carleon_mpd_title",
        service: carleonMpdFactory,
        carleonService: true,
        size: 2,
        leftMenu: {
			title: "carleon_music_title",
			target: "carleonMpd"
		},
		widgetHeight: 4,
		widgetWidth: 3,
		icon: "music",
		commands: {
			action: {
				title: "carleon_mpd_command_action_title",
				parameters: {
					action: "carleon_mpd_command_action_parameter_action_title",
				},
				result: {
					type: "boolean",
					title: "carleon_mpd_command_action_result_title"
				}
			},
			load_playlist: {
				title: "carleon_mpd_command_playlist_title",
				parameters: {
					playlist: "carleon_mpd_command_playlist_parameter_plsylist_title",
				},
				result: {
					type: "boolean",
					title: "carleon_mpd_command_playlist_result_title"
				}
			},
			set_volume: {
				title: "carleon_mpd_command_volume_title",
				parameters: {
					volume: "carleon_mpd_command_volume_parameter_volume_title",
				},
				result: {
					type: "boolean",
					title: "carleon_mpd_command_volume_result_title"
				}
			}
		}
    });
});
<|endoftext|>


<commit_before>import React from 'react'
import Modal, {
	closeStyle
}
from 'simple-react-modal'

/**
 * Start button
 */
class StartButton extends React.Component {
	render() {
		let disabled = this.props.disabled ? 'disabled' : '';

		return (

                <button disabled={ disabled } onClick={ this.props.spinHandler }>GO</button>

		);
	}
}

StartButton.displayName = 'StartButton';
StartButton.propTypes = {
	disabled: React.PropTypes.bool.isRequired,
	spinHandler: React.PropTypes.func
};

/**
 * Show rules.
 */
class RuleBox extends React.Component {
	constructor() {
		super()
		this.state = {}
	}
	show() {
		this.setState({
			show: true
		})
	}
	close() {
		this.setState({
			show: false
		})
	}
	render() {
		return (

      <button onClick={this.show.bind(this)}>活动规则</button>
      <Modal
      closeOnOuterClick={true}
      show={this.state.show}
      onClose={this.close.bind(this)}>

      X

{this.props.rule}

      </Modal>

		);
	}
}

RuleBox.displayName = 'RuleBox';
RuleBox.propTypes = {
	rule: React.PropTypes.string.isRequired
};
RuleBox.defaultProps = {
	rule: 'No rule set'
}

/**
 * Slot item -array- Slot items - spin
 */
class SlotItem extends React.Component {
	render() {
		return (

		);
	}
}

SlotItem.defaultProps = {
	item: {
		name: null,
		imgurl: null
	}
};

SlotItem.displayName = 'SlotItem';
SlotItem.propTypes = {
	item: React.PropTypes.object.isRequired
};

/**
 * A slot machine contains several slots.
 */
class SlotMachine extends React.Component {

	constructor(props) {
		super(props);
		let items = this.requestItems();
		this.state = {
			params: props.params === 'undefined' ? null : this.props.params,
			initUrl: this.props.initUrl,
			delay: this.props.delay,
			mess: this.props.mess,
			stop: true,
			slotNumber: props.slotNumber,
			slotIndexes: new Array(props.slotNumber).fill(items[0]),
			items: items,
			rule: 'rule ...',
			showRule: false
		}
	}
	requestItems() {
		/**
		 * I used to write
		 * this.setState({...})
		 * It doesn't work
		 * @type {Array}
		 */
		return ([{
			name: 'just',
			imgurl: null
		}, {
			name: 'do',
			imgurl: null
		}, {
			name: 'it',
			imgurl: null
		}])
	}
	spinHandler() {
		this.setState({
			stop: false
		})
		this.timer = setInterval(function() {
			let tempSlots = Array.from(this.state.slotIndexes);
			tempSlots.forEach((v, i, a) => {
				tempSlots[i] = this.state.items[this.randNext()]
			});
			this.setState({
				slotIndexes: tempSlots
			})

		}.bind(this), 50);
	}
	randNext() {
		return Math.floor(Math.random() * this.state.items.length)
	}
	generateResult(lottery, index) {
		if ((typeof lottery) === 'boolean') {
			if (lottery) {
				return new Array(this.state.slotNumber).fill(this.state.items[index])
			} else {
				let result = new Array(this.state.slotNumber).fill(this.state.items[index]);
				let diff = false;
				let last;
				// result cannot be (N)[size]
				result.forEach((v, i, a) => {
					let temp = this.randNext()
					if (i != 0) {
						if (last != temp) {
							diff = true
						} else {
							if (i == a.length - 1) {
								while (!diff) {
									temp = this.randNext()
								}
							}
						}
					}
					last = temp
					a[i] = this.state.items[temp]
				})
				console.log(result)
				return result
			}
		} else {
			alet('wooooooops')
		}
	}
	getAwardHandler() {
		clearInterval(this.timer);
		this.setState({
			slotIndexes: this.generateResult(false, 0)
		})
		this.setState({
			stop: true
		})
	}
	showRule() {
		this.setState({
			showRule: true
		})
	}
	hideRule() {
		this.setState({
			showRule: false
		})
	}
	render() {
		let hah = this.state.slotIndexes.map(function(v, i, a) {
			return (<SlotItem item={this.state.slotIndexes[i]}></SlotItem>);
		}.bind(this))
		return (

				{this.state.slotIndexes.map(function(v, i, a){
					return (<SlotItem item={this.state.slotIndexes[i]}></SlotItem>);
				}.bind(this))}

				<Modal show={this.state.showRule} onClose={this.hideRule.bind(this)} transitionSpeed={1000}>

{this.state.rule}

				</Modal>
				<button onClick={this.showRule.bind(this)} disabled={this.state.showRule}>活动规则</button>
				<button onClick={this.spinHandler.bind(this)} disabled={!this.state.stop}>点击开始</button>
				<button onClick={this.getAwardHandler.bind(this)} disabled={this.state.stop}>模拟返回请求</button>

		);
	}
}

SlotMachine.displayName = 'SlotMachine';
/**
 * may need params to identify how to init slot machine.
 * Or params stored in backend session so that i should not care.
 * initUrl: url to get items, rules.
 * delay: each slot should delay animation xxx ms.
 * mess: every slot should have a different array. It's out of plan :)
 * slotNumber: number of slots
 * @type {Object}
 */
SlotMachine.defaultProps = {
	params: null,
	initUrl: null,
	delay: 500,
	mess: false,
	slotNumber: 3
}
SlotMachine.propTypes = {
	params: React.PropTypes.string,
	initUrl: React.PropTypes.string,
	delay: React.PropTypes.number,
	mess: React.PropTypes.bool,
	slotNumber: React.PropTypes.number
}

export default SlotMachine;<commit_msg>Change interval to 100 to fix web no response bug<commit_after>import React from 'react'
import Modal, {
	closeStyle
}
from 'simple-react-modal'

/**
 * Start button
 */
class StartButton extends React.Component {
	render() {
		let disabled = this.props.disabled ? 'disabled' : '';

		return (

                <button disabled={ disabled } onClick={ this.props.spinHandler }>GO</button>

		);
	}
}

StartButton.displayName = 'StartButton';
StartButton.propTypes = {
	disabled: React.PropTypes.bool.isRequired,
	spinHandler: React.PropTypes.func
};

/**
 * Show rules.
 */
class RuleBox extends React.Component {
	constructor() {
		super()
		this.state = {}
	}
	show() {
		this.setState({
			show: true
		})
	}
	close() {
		this.setState({
			show: false
		})
	}
	render() {
		return (

      <button onClick={this.show.bind(this)}>活动规则</button>
      <Modal
      closeOnOuterClick={true}
      show={this.state.show}
      onClose={this.close.bind(this)}>

      X

{this.props.rule}

      </Modal>

		);
	}
}

RuleBox.displayName = 'RuleBox';
RuleBox.propTypes = {
	rule: React.PropTypes.string.isRequired
};
RuleBox.defaultProps = {
	rule: 'No rule set'
}

/**
 * Slot item -array- Slot items - spin
 */
class SlotItem extends React.Component {
	render() {
		return (

		);
	}
}

SlotItem.defaultProps = {
	item: {
		name: null,
		imgurl: null
	}
};

SlotItem.displayName = 'SlotItem';
SlotItem.propTypes = {
	item: React.PropTypes.object.isRequired
};

/**
 * A slot machine contains several slots.
 */
class SlotMachine extends React.Component {

	constructor(props) {
		super(props);
		let items = this.requestItems();
		this.state = {
			params: props.params === 'undefined' ? null : this.props.params,
			initUrl: this.props.initUrl,
			delay: this.props.delay,
			mess: this.props.mess,
			stop: true,
			slotNumber: props.slotNumber,
			slotIndexes: new Array(props.slotNumber).fill(items[0]),
			items: items,
			rule: 'rule ...',
			showRule: false,
			played: 0,
			maxPlayTimes: this.props.maxPlayTimes
		}
	}
	requestItems() {
		/**
		 * I used to write
		 * this.setState({...})
		 * It doesn't work
		 * @type {Array}
		 */
		return ([{
			name: 'just',
			imgurl: null
		}, {
			name: 'do',
			imgurl: null
		}, {
			name: 'it',
			imgurl: null
		}])
	}

	spinHandler() {
		this.setState({
			stop: false
		})
		this.timer = setInterval(function() {
			if (!this.state.stop) {
				let tempSlots = Array.from(this.state.slotIndexes);
				tempSlots.forEach((v, i, a) => {
					tempSlots[i] = this.state.items[this.randNext()]
				});
				this.setState({
					slotIndexes: tempSlots
				})
			} else {
				clearInterval(this.timer)
			}
		}.bind(this), 100);
	}
	randNext() {
		return Math.floor(Math.random() * this.state.items.length)
	}
	generateResult(lottery, index) {
		if ((typeof lottery) === 'boolean') {
			if (lottery) {
				return new Array(this.state.slotNumber).fill(this.state.items[index])
			} else {
				let result = new Array(this.state.slotNumber).fill(this.state.items[index]);
				let diff = false;
				let last;
				// result cannot be (N)[size]
				result.forEach((v, i, a) => {
					let temp = this.randNext()
					if (i != 0) {
						if (last != temp) {
							diff = true
						} else {
							if (i == a.length - 1) {
								while (!diff) {
									temp = this.randNext()
								}
							}
						}
					}
					last = temp
					a[i] = this.state.items[temp]
				})

				return result
			}
		} else {
			alet('wooooooops')
		}
	}
	getAwardHandler() {
		this.setState({
			stop: true
		})
		this.setState({
			slotIndexes: this.generateResult(false, 0)
		})
	}
	showRule() {
		this.setState({
			showRule: true
		})
	}
	hideRule() {
		this.setState({
			showRule: false
		})
	}
	render() {
		let hah = this.state.slotIndexes.map(function(v, i, a) {
			return (<SlotItem item={this.state.slotIndexes[i]}></SlotItem>);
		}.bind(this))
		return (

				{this.state.slotIndexes.map(function(v, i, a){
					return (<SlotItem item={this.state.slotIndexes[i]}></SlotItem>);
				}.bind(this))}

				<Modal show={this.state.showRule} onClose={this.hideRule.bind(this)} transitionSpeed={1000}>

{this.state.rule}

				</Modal>
				<button onClick={this.showRule.bind(this)} disabled={this.state.showRule}>活动规则</button>
				<button onClick={this.spinHandler.bind(this)} disabled={!this.state.stop}>点击开始</button>
				<button onClick={this.getAwardHandler.bind(this)} disabled={this.state.stop}>模拟返回请求</button>

		);
	}
}

SlotMachine.displayName = 'SlotMachine';
/**
 * may need params to identify how to init slot machine.
 * Or params stored in backend session so that i should not care.
 * initUrl: url to get items, rules.
 * delay: each slot should delay animation xxx ms.
 * mess: every slot should have a different array. It's out of plan :)
 * slotNumber: number of slots
 * @type {Object}
 */
SlotMachine.defaultProps = {
	params: null,
	initUrl: null,
	delay: 500,
	mess: false,
	slotNumber: 3,
	maxPlayTimes: 1
}
SlotMachine.propTypes = {
	params: React.PropTypes.string,
	initUrl: React.PropTypes.string,
	delay: React.PropTypes.number,
	mess: React.PropTypes.bool,
	slotNumber: React.PropTypes.number,
	maxPlayTimes: React.PropTypes.number
}

export default SlotMachine;<|endoftext|>


<commit_before>(function (define) {
define(function (require) {
"use strict";

	var eventNames, colaIdAttr,
		beforePhase, propagatingPhase, afterPhase, canceledPhase,
		resolver, addPropertyTransforms, simpleStrategy,
		undef;

	// TODO: make these configurable/extensible
	eventNames = {
		// collection item events
		add: 1,
		remove: 1,
		update: 1,
		target: 1,
		// edit mode events
		edit: 1,
		cancel: 1,
		save: 1,
		// multi-item events
		select: 1,
		unselect: 1,
		// network-level events
		join: 1,
		sync: 1,
		leave: 1
	};

	colaIdAttr = 'data-cola-id';

	/**
	 * Signal that event has not yet been pushed onto the network.
	 * Return false to prevent the event from being pushed.
	 */
	beforePhase = {};

	/**
	 * Signal that event is currently being propagated to adapters.
	 */
	propagatingPhase = {};

	/**
	 * Signal that an event has already been pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	afterPhase = {};

	/**
	 * Signal that an event was canceled and not pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	canceledPhase = {};

	resolver = require('./AdapterResolver');
	addPropertyTransforms = require('./addPropertyTransforms');
	simpleStrategy = require('./network/strategy/default');

	/**
	 * @constructor
	 * @param primary {Object} primary data source
	 * @param options.strategy {strategyFunction} a strategy
	 * Strategies determine if an event gets onto the network and then how
	 * it's processed by the other adapters in the network.
	 * Only one strategy can be applied to a network. However, strategies
	 * can be composed/combined.
	 */
	function Hub (primary, options) {
		var adapters, eventQueue, strategy, publicApi, eventsApi,
			callPublicEvent;

		// all adapters in network
		adapters = [];

		// events to be processed (fifo)
		eventQueue = [];

		callPublicEvent = checkEventsApi;

		strategy = options.strategy;
		if (!strategy) strategy = simpleStrategy;

		// create public api
		publicApi = {
			addSource: addSource,
			destroy: destroy
		};

		// add standard events to publicApi
		addApiMethods(eventNames);

		// add events
		publicApi.eventsHub = eventsApi = options.eventsHub;
		if (eventsApi) {
			addApiEvents(eventNames);
		}
		else {
			eventsApi = {};
		}

		// create adapter for primary and add it
		if (primary) addSource(primary, options);

		return publicApi;

		/**
		 * @memberOf Hub
		 * @param source
		 * @param options {Object}
		 * @param [options.eventNames] {Function} function that returns a
		 *   list of method names that should be considered events
		 *   If omitted, all methods, the standard event names are used.
		 * @param options.provide {Boolean} if true, initiates a 'sync' event
		 *   from this source's adapter
		 */
		function addSource (source, options) {
			var Adapter, adapter, proxy, method, eventFinder;

			if (!options) options = {};

			// create an adapter for this source
			// if we can't find an Adapter constructor, it is assumed to be an
			// adapter already.
			// TODO: revisit this assumption?
			Adapter = resolver(source);
			adapter = Adapter ? new Adapter(source, options) : source;
			if (options.bindings) {
				adapter = addPropertyTransforms(adapter, collectPropertyTransforms(options.bindings));
			}

			proxy = beget(adapter);

			// sniff for event hooks
			eventFinder = configureEventFinder(options.eventNames);

			// override methods that require event hooks
			for (method in adapter) {
				if (typeof adapter[method] == 'function') {
					if (eventFinder(method)) {
						// store original method on proxy (to stop recursion)
						proxy[method] = adapter[method];
						// change public api of adapter to call back into hub
						observeAdapterMethod(adapter, method, adapter[method]);
						// ensure hub has a public method of the same name
						addApiMethod(method);
						addApiEvent(method);
					}
				}
			}

			// save the proxied adapter
			adapters.push(proxy);
		}

		function queueEvent (source, data, type) {
			var queueNeedsRestart;

			// if queue length is zero, we need to start processing it again
			queueNeedsRestart = eventQueue.length == 0;

			// enqueue event
			eventQueue.push({ source: source, data: data, type: type });

			// start processing, if necessary
			if (queueNeedsRestart) processNextEvent();
		}

		function processNextEvent () {
			var event;

			// get the next event, if any
			event = eventQueue.shift();

			// if there was an event, process it soon
			if (event) {
				setTimeout(function () {
					processEvent(event.source, event.data, event.type);
				}, 0);
			}
		}

		/*
			1. call eventsHub.beforeXXX(data)
			2. call strategy on each source/dest pair w/ event XXX and data
				- cancel iteration if any strategy returns false for any pair
			3. if not canceled, call eventsHub.XXX(data)
		 */
		function processEvent (source, data, type) {
			var context, strategyApi, i, adapter, canceled;

			// give public api a chance to see (and possibly cancel) event
			canceled = callPublicEvent(data, camelize('before', type));

			// if public api cancels, the network never sees the event at all
			if (!canceled) {

				context = { phase: beforePhase };
				strategyApi = createStrategyApi(context);

				canceled = false === strategy(source, undef, data, type, strategyApi);
				i = adapters.length;

				context.phase = propagatingPhase;
				while (!canceled && (adapter = adapters[--i])) {
					if (false === strategy(source, adapter, data, type, strategyApi)) {
						break;
					}
				}

				context.phase = canceled ? canceledPhase : afterPhase;
				canceled = strategy(source, undef, data, type, strategyApi);

			}

			callPublicEvent(data, type);

			processNextEvent();

			return canceled;
		}

		function createStrategyApi (context) {
			function isPhase (phase) { return context.phase == phase; }
			return {
				queueEvent: queueEvent,
				isBefore: function () { return isPhase(beforePhase); },
				isAfter: function () { return isPhase(afterPhase); },
				isCanceled: function () { return isPhase(canceledPhase); },
				isPropagating: function () { return isPhase(propagatingPhase); }
			};
		}

		function observeAdapterMethod (adapter, type, origMethod) {
			return adapter[type] = function (data) {
				processEvent(adapter, data, type);
				return origMethod.call(adapter, data);
			};
		}

		function addApiMethods (eventNames) {
			for (var name in eventNames) {
				addApiMethod(name);
			}
		}

		function addApiMethod (name) {
			if (!publicApi[name]) {
				publicApi[name] = function (itemOrDomEvent) {
					var data;
					// Note: next line assumes the primary knows about all items
					data = convertFromDomEvent(itemOrDomEvent, primary);
					return processEvent(null, data, name);
				};
			}
		}

		function addApiEvents (eventNames) {
			for (var name in eventNames) {
				addApiEvent(name);
			}
		}

		function addApiEvent (name) {
			// add function stub to api
			if (!eventsApi[name]) {
				eventsApi[name] = function (data) {};
			}
			// add beforeXXX stub, too
			name = camelize('before', name);
			if (!eventsApi[name]) {
				eventsApi[name] = function (data) {};
			}
		}

		function callEventsApi (data, name) {
			// yah, this is simple, but there's some redirection going on
			// see checkEventsApi
			try {
				return eventsApi[name](data);
			}
			catch (ex) {
				return false;
			}
		}

		function checkEventsApi (data, name) {
			var tempApi;

			// once we have an eventsHub, start using it and stop using
			// this function
			if (publicApi.eventsHub) {

				// switch eventsApi to public property
				eventsApi = publicApi.eventsHub;
				tempApi = eventsApi;

				// ensure all events are on new api
				for (var p in tempApi) addApiEvent(p);

				// switch callPublicEvent to normal function
				callPublicEvent = callEventsApi;

				// resume as usual
				callPublicEvent(data, name);
			}
		}

		function destroy () {
			var adapter;
			while ((adapter = adapters.pop())) {
				if (typeof adapter.destroy == 'function') {
					adapter.destroy();
				}
			}
		}

	}

	// TODO: get rid of this mess
	resolver.register(require('./ArrayAdapter'), 'collection');
	resolver.register(require('./dom/NodeListAdapter'), 'collection');
	resolver.register(require('./ResultSetAdapter'), 'collection');
	resolver.register(require('./QueryAdapter'), 'collection');
	resolver.register(require('./dom/NodeAdapter'), 'object');
	resolver.register(require('./ObjectAdapter'), 'object');
	resolver.register(require('./ResultAdapter'), 'object');

	return Hub;

	/**
	 * Signature for all network strategy functions.
	 * @param source {Object} the adapter that sourced the event
	 * @param dest {Object} the adapter receiving the event
	 * @param data {Object} any data associated with the event
	 * @param type {String} the type of event
	 * @param api {Object} helpful functions for strategies
	 * @returns {Boolean} whether event is allowed.
	 */
	function strategyFunction (source, dest, data, type, api) {};

	function configureEventFinder (option) {
		if (typeof option == 'function') return option;

		return function (name) { return eventNames.hasOwnProperty(name); };
	}

	function convertFromDomEvent (objOrEvent, adapter) {
		var obj, node, id;
		// using feature sniffing to detect if this is an event object
		if (objOrEvent.target && objOrEvent.stopPropagation && objOrEvent.preventDefault) {
			// walk dom, find id, return item with same identifier
			node = objOrEvent.target;
			while (node && !node.hasAttribute(colaIdAttr)) {
				node = node.parentNode;
				if (node.nodeType != 1) node = undef;
			}
			if (node) {
				id = node.getAttribute(colaIdAttr);
				adapter.forEach(function (item) {
					if (adapter.identifier(item) == id) obj = item;
				});
			}
			if (!obj) {
				var err = new Error('Hub: could not find data item for dom event.');
				err.event = objOrEvent; // TODO: is this helpful?
				throw err;
			}
			return obj;
		}
		else {
			return objOrEvent;
		}
	}

	function collectPropertyTransforms (bindings) {
		var name, propertyTransforms, transform;

		propertyTransforms = {};
		for (name in bindings) {
			transform = bindings[name].transform;
			if (transform) {
				propertyTransforms[name] = transform;
			}
		}

		return propertyTransforms;
	}

	function Begetter () {}
	function beget (proto) {
		var obj;
		Begetter.prototype = proto;
		obj = new Begetter();
		Begetter.prototype = undef;
		return obj;
	}

	function noop () {}

	function camelize (prefix, name) {
		return prefix + name.charAt(0).toUpperCase() + name.substr(1);
	}

});
}(
	typeof define == 'function' && define.amd
		? define
		: function (factory) { module.exports = factory(require); }
));
<commit_msg>fix minor bugs<commit_after>(function (define) {
define(function (require) {
"use strict";

	var eventNames, colaIdAttr,
		beforePhase, propagatingPhase, afterPhase, canceledPhase,
		resolver, addPropertyTransforms, simpleStrategy,
		undef;

	// TODO: make these configurable/extensible
	eventNames = {
		// collection item events
		add: 1,
		remove: 1,
		update: 1,
		target: 1,
		// edit mode events
		edit: 1,
		cancel: 1,
		save: 1,
		// multi-item events
		select: 1,
		unselect: 1,
		// network-level events
		join: 1,
		sync: 1,
		leave: 1
	};

	colaIdAttr = 'data-cola-id';

	/**
	 * Signal that event has not yet been pushed onto the network.
	 * Return false to prevent the event from being pushed.
	 */
	beforePhase = {};

	/**
	 * Signal that event is currently being propagated to adapters.
	 */
	propagatingPhase = {};

	/**
	 * Signal that an event has already been pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	afterPhase = {};

	/**
	 * Signal that an event was canceled and not pushed onto the network.
	 * Return value is ignored since the event has already propagated.
	 */
	canceledPhase = {};

	resolver = require('./AdapterResolver');
	addPropertyTransforms = require('./addPropertyTransforms');
	simpleStrategy = require('./network/strategy/default');

	/**
	 * @constructor
	 * @param primary {Object} primary data source
	 * @param options.strategy {strategyFunction} a strategy
	 *   Strategies determine if an event gets onto the network and then how
	 *   it's processed by the other adapters in the network.
	 *   Only one strategy can be applied to a network. However, strategies
	 *   can be composed/combined.
	 * @param [options.eventsHub] {Object} an object to receive events
	 *   from the hub's network
	 */
	function Hub (primary, options) {
		var adapters, eventQueue, strategy, publicApi, eventsApi,
			callPublicEvent;

		// all adapters in network
		adapters = [];

		// events to be processed (fifo)
		eventQueue = [];

		callPublicEvent = checkEventsApi;

		if (!options) options = {};

		strategy = options.strategy;
		if (!strategy) strategy = simpleStrategy;

		// create public api
		publicApi = {
			addSource: addSource,
			destroy: destroy
		};

		// add standard events to publicApi
		addApiMethods(eventNames);

		// add events
		publicApi.eventsHub = eventsApi = options.eventsHub;
		if (eventsApi) {
			addApiEvents(eventNames);
		}
		else {
			eventsApi = {};
		}

		// create adapter for primary and add it
		if (primary) addSource(primary, options);

		return publicApi;

		/**
		 * @memberOf Hub
		 * @param source
		 * @param options {Object}
		 * @param [options.eventNames] {Function} function that returns a
		 *   list of method names that should be considered events
		 *   If omitted, all methods, the standard event names are used.
		 * @param options.provide {Boolean} if true, initiates a 'sync' event
		 *   from this source's adapter
		 */
		function addSource (source, options) {
			var Adapter, adapter, proxy, method, eventFinder;

			if (!options) options = {};

			// create an adapter for this source
			// if we can't find an Adapter constructor, it is assumed to be an
			// adapter already.
			// TODO: revisit this assumption?
			Adapter = resolver(source);
			adapter = Adapter ? new Adapter(source, options) : source;
			if (options.bindings) {
				adapter = addPropertyTransforms(adapter, collectPropertyTransforms(options.bindings));
			}

			proxy = beget(adapter);

			// sniff for event hooks
			eventFinder = configureEventFinder(options.eventNames);

			// override methods that require event hooks
			for (method in adapter) {
				if (typeof adapter[method] == 'function') {
					if (eventFinder(method)) {
						// store original method on proxy (to stop recursion)
						proxy[method] = adapter[method];
						// change public api of adapter to call back into hub
						observeAdapterMethod(adapter, method, adapter[method]);
						// ensure hub has a public method of the same name
						addApiMethod(method);
						addApiEvent(method);
					}
				}
			}

			// save the proxied adapter
			adapters.push(proxy);

			return adapter;
		}

		function queueEvent (source, data, type) {
			var queueNeedsRestart;

			// if queue length is zero, we need to start processing it again
			queueNeedsRestart = eventQueue.length == 0;

			// enqueue event
			eventQueue.push({ source: source, data: data, type: type });

			// start processing, if necessary
			if (queueNeedsRestart) processNextEvent();
		}

		function processNextEvent () {
			var event;

			// get the next event, if any
			event = eventQueue.shift();

			// if there was an event, process it soon
			if (event) {
				setTimeout(function () {
					processEvent(event.source, event.data, event.type);
				}, 0);
			}
		}

		/*
			1. call eventsHub.beforeXXX(data)
			2. call strategy on each source/dest pair w/ event XXX and data
				- cancel iteration if any strategy returns false for any pair
			3. if not canceled, call eventsHub.XXX(data)
		 */
		function processEvent (source, data, type) {
			var context, strategyApi, i, adapter, canceled;

			// give public api a chance to see (and possibly cancel) event
			canceled = callPublicEvent(data, camelize('before', type));

			// if public api cancels, the network never sees the event at all
			if (!canceled) {

				context = { phase: beforePhase };
				strategyApi = createStrategyApi(context);

				canceled = false === strategy(source, undef, data, type, strategyApi);
				i = adapters.length;

				context.phase = propagatingPhase;
				while (!canceled && (adapter = adapters[--i])) {
					if (false === strategy(source, adapter, data, type, strategyApi)) {
						break;
					}
				}

				context.phase = canceled ? canceledPhase : afterPhase;
				canceled = strategy(source, undef, data, type, strategyApi);

			}

			callPublicEvent(data, type);

			processNextEvent();

			return canceled;
		}

		function createStrategyApi (context) {
			function isPhase (phase) { return context.phase == phase; }
			return {
				queueEvent: queueEvent,
				isBefore: function () { return isPhase(beforePhase); },
				isAfter: function () { return isPhase(afterPhase); },
				isCanceled: function () { return isPhase(canceledPhase); },
				isPropagating: function () { return isPhase(propagatingPhase); }
			};
		}

		function observeAdapterMethod (adapter, type, origMethod) {
			return adapter[type] = function (data) {
				processEvent(adapter, data, type);
				return origMethod.call(adapter, data);
			};
		}

		function addApiMethods (eventNames) {
			for (var name in eventNames) {
				addApiMethod(name);
			}
		}

		function addApiMethod (name) {
			if (!publicApi[name]) {
				publicApi[name] = function (itemOrDomEvent) {
					var data;
					// Note: next line assumes the primary knows about all items
					data = convertFromDomEvent(itemOrDomEvent, primary);
					return processEvent(null, data, name);
				};
			}
		}

		function addApiEvents (eventNames) {
			for (var name in eventNames) {
				addApiEvent(name);
			}
		}

		function addApiEvent (name) {
			// add function stub to api
			if (!eventsApi[name]) {
				eventsApi[name] = function (data) {};
			}
			// add beforeXXX stub, too
			name = camelize('before', name);
			if (!eventsApi[name]) {
				eventsApi[name] = function (data) {};
			}
		}

		function callEventsApi (data, name) {
			// yah, this is simple, but there's some redirection going on
			// see checkEventsApi
			try {
				return eventsApi[name](data);
			}
			catch (ex) {
				return false;
			}
		}

		function checkEventsApi (data, name) {
			var tempApi;

			// once we have an eventsHub, start using it and stop using
			// this function
			if (publicApi.eventsHub) {

				// switch eventsApi to public property
				eventsApi = publicApi.eventsHub;
				tempApi = eventsApi;

				// ensure all events are on new api
				for (var p in tempApi) addApiEvent(p);

				// switch callPublicEvent to normal function
				callPublicEvent = callEventsApi;

				// resume as usual
				callPublicEvent(data, name);
			}
		}

		function destroy () {
			var adapter;
			while ((adapter = adapters.pop())) {
				if (typeof adapter.destroy == 'function') {
					adapter.destroy();
				}
			}
		}

	}

	// TODO: get rid of this mess
	resolver.register(require('./ArrayAdapter'), 'collection');
	resolver.register(require('./dom/NodeListAdapter'), 'collection');
	resolver.register(require('./ResultSetAdapter'), 'collection');
	resolver.register(require('./QueryAdapter'), 'collection');
	resolver.register(require('./dom/NodeAdapter'), 'object');
	resolver.register(require('./ObjectAdapter'), 'object');
	resolver.register(require('./ResultAdapter'), 'object');

	return Hub;

	/**
	 * Signature for all network strategy functions.
	 * @param source {Object} the adapter that sourced the event
	 * @param dest {Object} the adapter receiving the event
	 * @param data {Object} any data associated with the event
	 * @param type {String} the type of event
	 * @param api {Object} helpful functions for strategies
	 * @returns {Boolean} whether event is allowed.
	 */
	function strategyFunction (source, dest, data, type, api) {};

	function configureEventFinder (option) {
		if (typeof option == 'function') return option;

		return function (name) { return eventNames.hasOwnProperty(name); };
	}

	function convertFromDomEvent (objOrEvent, adapter) {
		var obj, node, id;
		// using feature sniffing to detect if this is an event object
		if (objOrEvent.target && objOrEvent.stopPropagation && objOrEvent.preventDefault) {
			// walk dom, find id, return item with same identifier
			node = objOrEvent.target;
			while (node && !node.hasAttribute(colaIdAttr)) {
				node = node.parentNode;
				if (node.nodeType != 1) node = undef;
			}
			if (node) {
				id = node.getAttribute(colaIdAttr);
				adapter.forEach(function (item) {
					if (adapter.identifier(item) == id) obj = item;
				});
			}
			if (!obj) {
				var err = new Error('Hub: could not find data item for dom event.');
				err.event = objOrEvent; // TODO: is this helpful?
				throw err;
			}
			return obj;
		}
		else {
			return objOrEvent;
		}
	}

	function collectPropertyTransforms (bindings) {
		var name, propertyTransforms, transform;

		propertyTransforms = {};
		for (name in bindings) {
			transform = bindings[name].transform;
			if (transform) {
				propertyTransforms[name] = transform;
			}
		}

		return propertyTransforms;
	}

	function Begetter () {}
	function beget (proto) {
		var obj;
		Begetter.prototype = proto;
		obj = new Begetter();
		Begetter.prototype = undef;
		return obj;
	}

	function noop () {}

	function camelize (prefix, name) {
		return prefix + name.charAt(0).toUpperCase() + name.substr(1);
	}

});
}(
	typeof define == 'function' && define.amd
		? define
		: function (factory) { module.exports = factory(require); }
));
<|endoftext|>


<commit_before>/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import LogoutDialog from "../dialogs/LogoutDialog";
import Modal from "../../../Modal";
import SdkConfig from '../../../SdkConfig';
import { getHostingLink } from '../../../utils/HostingLink';
import MatrixClientPeg from '../../../MatrixClientPeg';
import {MenuItem} from "../../structures/ContextMenu";
import sdk from "../../../index";

export class TopLeftMenu extends React.Component {
    static propTypes = {
        displayName: PropTypes.string.isRequired,
        userId: PropTypes.string.isRequired,
        onFinished: PropTypes.func,

        // Optional function to collect a reference to the container
        // of this component directly.
        containerRef: PropTypes.func,
    };

    constructor() {
        super();
        this.viewHomePage = this.viewHomePage.bind(this);
        this.openSettings = this.openSettings.bind(this);
        this.signIn = this.signIn.bind(this);
        this.signOut = this.signOut.bind(this);
    }

    hasHomePage() {
        const config = SdkConfig.get();
        const pagesConfig = config.embeddedPages;
        if (pagesConfig && pagesConfig.homeUrl) {
            return true;
        }
        // This is a deprecated config option for the home page
        // (despite the name, given we also now have a welcome
        // page, which is not the same).
        return !!config.welcomePageUrl;
    }

    render() {
        const isGuest = MatrixClientPeg.get().isGuest();

        const hostingSignupLink = getHostingLink('user-context-menu');
        let hostingSignup = null;
        if (hostingSignupLink) {
            hostingSignup =

                {_t(
                    "Upgrade to your own domain", {},
                    {
                        a: sub => {sub},
                    },
                )}

;
        }

        let homePageItem = null;
        if (this.hasHomePage()) {
            homePageItem = (
                <MenuItem className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>
                    {_t("Home")}
                </MenuItem>
            );
        }

        let signInOutItem;
        if (isGuest) {
            signInOutItem = (
                <MenuItem className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>
                    {_t("Sign in")}
                </MenuItem>
            );
        } else {
            signInOutItem = (
                <MenuItem className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>
                    {_t("Sign out")}
                </MenuItem>
            );
        }

        const helpItem = (
            <MenuItem className="mx_TopLeftMenu_icon_help" onClick={this.openHelp}>
                {_t("Help")}
            </MenuItem>
        );

        const settingsItem = (
            <MenuItem className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
                {_t("Settings")}
            </MenuItem>
        );

        return

{this.props.displayName}

{this.props.userId}

                {hostingSignup}

                {homePageItem}
                {settingsItem}
                {helpItem}
                {signInOutItem}

;
    }

    openHelp() {
        this.closeMenu();
        const RedesignFeedbackDialog = sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
        Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
    }

    viewHomePage() {
        dis.dispatch({action: 'view_home_page'});
        this.closeMenu();
    }

    openSettings() {
        dis.dispatch({action: 'view_user_settings'});
        this.closeMenu();
    }

    signIn() {
        dis.dispatch({action: 'start_login'});
        this.closeMenu();
    }

    signOut() {
        Modal.createTrackedDialog('Logout E2E Export', '', LogoutDialog);
        this.closeMenu();
    }

    closeMenu() {
        if (this.props.onFinished) this.props.onFinished();
    }
}
<commit_msg>Fix wrong scope binding on openHelp for TopLeftMenu<commit_after>/*
Copyright 2018, 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import dis from '../../../dispatcher';
import { _t } from '../../../languageHandler';
import LogoutDialog from "../dialogs/LogoutDialog";
import Modal from "../../../Modal";
import SdkConfig from '../../../SdkConfig';
import { getHostingLink } from '../../../utils/HostingLink';
import MatrixClientPeg from '../../../MatrixClientPeg';
import {MenuItem} from "../../structures/ContextMenu";
import sdk from "../../../index";

export class TopLeftMenu extends React.Component {
    static propTypes = {
        displayName: PropTypes.string.isRequired,
        userId: PropTypes.string.isRequired,
        onFinished: PropTypes.func,

        // Optional function to collect a reference to the container
        // of this component directly.
        containerRef: PropTypes.func,
    };

    constructor() {
        super();
        this.viewHomePage = this.viewHomePage.bind(this);
        this.openSettings = this.openSettings.bind(this);
        this.signIn = this.signIn.bind(this);
        this.signOut = this.signOut.bind(this);
    }

    hasHomePage() {
        const config = SdkConfig.get();
        const pagesConfig = config.embeddedPages;
        if (pagesConfig && pagesConfig.homeUrl) {
            return true;
        }
        // This is a deprecated config option for the home page
        // (despite the name, given we also now have a welcome
        // page, which is not the same).
        return !!config.welcomePageUrl;
    }

    render() {
        const isGuest = MatrixClientPeg.get().isGuest();

        const hostingSignupLink = getHostingLink('user-context-menu');
        let hostingSignup = null;
        if (hostingSignupLink) {
            hostingSignup =

                {_t(
                    "Upgrade to your own domain", {},
                    {
                        a: sub => {sub},
                    },
                )}

;
        }

        let homePageItem = null;
        if (this.hasHomePage()) {
            homePageItem = (
                <MenuItem className="mx_TopLeftMenu_icon_home" onClick={this.viewHomePage}>
                    {_t("Home")}
                </MenuItem>
            );
        }

        let signInOutItem;
        if (isGuest) {
            signInOutItem = (
                <MenuItem className="mx_TopLeftMenu_icon_signin" onClick={this.signIn}>
                    {_t("Sign in")}
                </MenuItem>
            );
        } else {
            signInOutItem = (
                <MenuItem className="mx_TopLeftMenu_icon_signout" onClick={this.signOut}>
                    {_t("Sign out")}
                </MenuItem>
            );
        }

        const helpItem = (
            <MenuItem className="mx_TopLeftMenu_icon_help" onClick={this.openHelp}>
                {_t("Help")}
            </MenuItem>
        );

        const settingsItem = (
            <MenuItem className="mx_TopLeftMenu_icon_settings" onClick={this.openSettings}>
                {_t("Settings")}
            </MenuItem>
        );

        return

{this.props.displayName}

{this.props.userId}

                {hostingSignup}

                {homePageItem}
                {settingsItem}
                {helpItem}
                {signInOutItem}

;
    }

    openHelp = () => {
        this.closeMenu();
        const RedesignFeedbackDialog = sdk.getComponent("views.dialogs.RedesignFeedbackDialog");
        Modal.createTrackedDialog('Report bugs & give feedback', '', RedesignFeedbackDialog);
    };

    viewHomePage() {
        dis.dispatch({action: 'view_home_page'});
        this.closeMenu();
    }

    openSettings() {
        dis.dispatch({action: 'view_user_settings'});
        this.closeMenu();
    }

    signIn() {
        dis.dispatch({action: 'start_login'});
        this.closeMenu();
    }

    signOut() {
        Modal.createTrackedDialog('Logout E2E Export', '', LogoutDialog);
        this.closeMenu();
    }

    closeMenu() {
        if (this.props.onFinished) this.props.onFinished();
    }
}
<|endoftext|>


<commit_before>/*
Copyright 2017 New Vector Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import {_t} from '../../../languageHandler.js';

const EditableItem = React.createClass({
    displayName: 'EditableItem',

    propTypes: {
        initialValue: PropTypes.string,
        index: PropTypes.number,
        placeholder: PropTypes.string,

        onChange: PropTypes.func,
        onRemove: PropTypes.func,
        onAdd: PropTypes.func,
    },

    onChange: function(value) {
        this.setState({ value });
        if (this.props.onChange) this.props.onChange(value, this.props.index);
    },

    onRemove: function() {
        this.props.onRemove(this.props.index);
    },

    onAdd: function() {
        this.props.onAdd(this.state.value);
    },

    render: function() {
        const EditableText = sdk.getComponent('elements.EditableText');
        return

            <EditableText
                className="mx_EditableItem_editable"
                placeholderClassName="mx_EditableItem_editablePlaceholder"
                placeholder={this.props.placeholder}
                blurToCancel={false}
                editable={true}
                initialValue={this.props.initialValue}
                onValueChanged={this.onChange} />
            { this.props.onAdd ?

                :

            }

;
    },
});

module.exports = React.createClass({
    displayName: 'EditableItemList',

    propTypes: {
        items: PropTypes.arrayOf(PropTypes.string).isRequired,
        onNewItemChanged: PropTypes.func,
        onItemAdded: PropTypes.func,
        onItemEdited: PropTypes.func,
        onItemRemoved: PropTypes. func,
    },

    getDefaultProps: function() {
        return {
            onItemAdded: () => {},
            onItemEdited: () => {},
            onItemRemoved: () => {},
        };
    },

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

    componentWillReceiveProps: function(nextProps) {
    },

    componentWillMount: function() {
    },

    componentDidMount: function() {
    },

    onItemAdded: function(value) {
        console.info('onItemAdded', value);
        this.props.onItemAdded(value);
    },

    onItemEdited: function(value, index) {
        console.info('onItemEdited', value, index);
        if (value.length === 0) {
            this.onItemRemoved(index);
        } else {
            this.onItemEdited(value, index);
        }
    },

    onItemRemoved: function(index) {
        console.info('onItemRemoved', index);
        this.props.onItemRemoved(index);
    },

    onNewItemChanged: function(value) {
        this.props.onNewItemChanged(value);
    },

    render: function() {
        const editableItems = this.props.items.map((item, index) => {
            return <EditableItem
                key={index}
                index={index}
                initialValue={item}
                onChange={this.onItemEdited}
                onRemove={this.onItemRemoved}
                placeholder={this.props.placeholder}
            />;
        });

        const label = this.props.items.length > 0 ?
            this.props.itemsLabel : this.props.noItemsLabel;

        console.info('New item:', this.props.newItem);

        return (

                { label }

            { editableItems }
            <EditableItem
                key={-1}
                initialValue={this.props.newItem}
                onAdd={this.onItemAdded}
                onChange={this.onNewItemChanged}
                placeholder={this.props.placeholder}
            />

);
    },
});
<commit_msg>Remove cruft from EIL<commit_after>/*
Copyright 2017 New Vector Ltd.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import React from 'react';
import PropTypes from 'prop-types';
import sdk from '../../../index';
import {_t} from '../../../languageHandler.js';

const EditableItem = React.createClass({
    displayName: 'EditableItem',

    propTypes: {
        initialValue: PropTypes.string,
        index: PropTypes.number,
        placeholder: PropTypes.string,

        onChange: PropTypes.func,
        onRemove: PropTypes.func,
        onAdd: PropTypes.func,
    },

    onChange: function(value) {
        this.setState({ value });
        if (this.props.onChange) this.props.onChange(value, this.props.index);
    },

    onRemove: function() {
        this.props.onRemove(this.props.index);
    },

    onAdd: function() {
        this.props.onAdd(this.state.value);
    },

    render: function() {
        const EditableText = sdk.getComponent('elements.EditableText');
        return

            <EditableText
                className="mx_EditableItem_editable"
                placeholderClassName="mx_EditableItem_editablePlaceholder"
                placeholder={this.props.placeholder}
                blurToCancel={false}
                editable={true}
                initialValue={this.props.initialValue}
                onValueChanged={this.onChange} />
            { this.props.onAdd ?

                :

            }

;
    },
});

module.exports = React.createClass({
    displayName: 'EditableItemList',

    propTypes: {
        items: PropTypes.arrayOf(PropTypes.string).isRequired,
        onNewItemChanged: PropTypes.func,
        onItemAdded: PropTypes.func,
        onItemEdited: PropTypes.func,
        onItemRemoved: PropTypes. func,
    },

    getDefaultProps: function() {
        return {
            onItemAdded: () => {},
            onItemEdited: () => {},
            onItemRemoved: () => {},
            onNewItemChanged: () => {},
        };
    },

    onItemAdded: function(value) {
        this.props.onItemAdded(value);
    },

    onItemEdited: function(value, index) {
        if (value.length === 0) {
            this.onItemRemoved(index);
        } else {
            this.onItemEdited(value, index);
        }
    },

    onItemRemoved: function(index) {
        this.props.onItemRemoved(index);
    },

    onNewItemChanged: function(value) {
        this.props.onNewItemChanged(value);
    },

    render: function() {
        const editableItems = this.props.items.map((item, index) => {
            return <EditableItem
                key={index}
                index={index}
                initialValue={item}
                onChange={this.onItemEdited}
                onRemove={this.onItemRemoved}
                placeholder={this.props.placeholder}
            />;
        });

        const label = this.props.items.length > 0 ?
            this.props.itemsLabel : this.props.noItemsLabel;

        return (

                { label }

            { editableItems }
            <EditableItem
                key={-1}
                initialValue={this.props.newItem}
                onAdd={this.onItemAdded}
                onChange={this.onNewItemChanged}
                placeholder={this.props.placeholder}
            />

);
    },
});
<|endoftext|>