<commit_before>CodeMirror.defineMode('smalltalk', function(config, modeConfig) {

	var specialChars = /[+\-/\\*~<>=@%|&?!.:;^]/;
	var keywords = /true|false|nil|self|super|thisContext/;

	var Context = function(tokenizer, parent) {
		this.next = tokenizer;
		this.parent = parent;
	};

	var Token = function(name, context, eos) {
		this.name = name;
		this.context = context;
		this.eos = eos;
	};

	var next = function(stream, context, state) {
		var token = new Token(null, context, false);
		var char = stream.next();

		if (char === '"') {
			token = nextComment(stream, new Context(nextComment, context));

		} else if (char === '\'') {
			token = nextString(stream, new Context(nextString, context));

		} else if (char === '#') {
			stream.eatWhile(/[^ .]/);
			token.name = 'string-2';

		} else if (char === '$') {
			stream.eatWhile(/[^ ]/);
			token.name = 'string-2';

		} else if (char === '|' && state.expectVariable) {
			token.context = new Context(nextTemporaries, context);

		} else if (/[\[\]{}()]/.test(char)) {
			token.name = 'bracket';
			token.eos = /[\[{(]/.test(char);

		} else if (specialChars.test(char)) {
			stream.eatWhile(specialChars);
			token.name = 'operator';
			token.eos = char !== ';'; // ; cascaded message expression

		} else if (/\d/.test(char)) {
			stream.eatWhile(/[\w\d]/);
			token.name = 'number'

		} else if (/[\w_]/.test(char)) {
			stream.eatWhile(/[\w\d_]/);
			token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null;

		} else {
			token.eos = state.expectVariable;
		}

		return token;
	};

	var nextComment = function(stream, context) {
		stream.eatWhile(/[^"]/);
		return new Token('comment', stream.eat('"') ? context.parent : context, true);
	};

	var nextString = function(stream, context) {
		stream.eatWhile(/[^']/);
		return new Token('string', stream.eat('\'') ? context.parent : context, false);
	};

	var nextTemporaries = function(stream, context, state) {
		var token = new Token(null, context, false);
		var char = stream.next();

		if (char === '|') {
			token.context = context.parent;
			token.eos = true;

		} else {
			stream.eatWhile(/[^|]/);
			token.name = 'variable';
		}

		return token;
	}

	return {
		startState: function() {
			return {context: new Context(next, null), expectVariable: true};
		},

		token: function(stream, state) {
			if (stream.eatSpace()) {
				return null;
			}

			var token = state.context.next(stream, state.context, state);
			state.context = token.context;
			state.expectVariable = token.eos;
			return token.name;
		},
	};

});

CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'});<commit_msg>fixup! [smalltalk mode] rewrite smalltalk mode<commit_after>CodeMirror.defineMode('smalltalk', function(config, modeConfig) {

	var specialChars = /[+\-/\\*~<>=@%|&?!.:;^]/;
	var keywords = /true|false|nil|self|super|thisContext/;

	var Context = function(tokenizer, parent) {
		this.next = tokenizer;
		this.parent = parent;
	};

	var Token = function(name, context, eos) {
		this.name = name;
		this.context = context;
		this.eos = eos;
	};

	var State = function() {
		this.context = new Context(next, null);
		this.expectVariable = true;
		this.indentation = 0;
		this.userIndentationDelta = 0;
	};

	State.prototype.userIndent = function(indentation) {
		this.userIndentationDelta = indentation > 0 ? (indentation / config.indentUnit - this.indentation) : 0;
	};

	var next = function(stream, context, state) {
		var token = new Token(null, context, false);
		var char = stream.next();

		if (char === '"') {
			token = nextComment(stream, new Context(nextComment, context));

		} else if (char === '\'') {
			token = nextString(stream, new Context(nextString, context));

		} else if (char === '#') {
			stream.eatWhile(/[^ .]/);
			token.name = 'string-2';

		} else if (char === '$') {
			stream.eatWhile(/[^ ]/);
			token.name = 'string-2';

		} else if (char === '|' && state.expectVariable) {
			token.context = new Context(nextTemporaries, context);

		} else if (/[\[\]{}()]/.test(char)) {
			token.name = 'bracket';
			token.eos = /[\[{(]/.test(char);

			if (char === '[') {
				state.indentation++;
			} else if (char === ']') {
				state.indentation = Math.max(0, state.indentation - 1);
			}

		} else if (specialChars.test(char)) {
			stream.eatWhile(specialChars);
			token.name = 'operator';
			token.eos = char !== ';'; // ; cascaded message expression

		} else if (/\d/.test(char)) {
			stream.eatWhile(/[\w\d]/);
			token.name = 'number'

		} else if (/[\w_]/.test(char)) {
			stream.eatWhile(/[\w\d_]/);
			token.name = state.expectVariable ? (keywords.test(stream.current()) ? 'keyword' : 'variable') : null;

		} else {
			token.eos = state.expectVariable;
		}

		return token;
	};

	var nextComment = function(stream, context) {
		stream.eatWhile(/[^"]/);
		return new Token('comment', stream.eat('"') ? context.parent : context, true);
	};

	var nextString = function(stream, context) {
		stream.eatWhile(/[^']/);
		return new Token('string', stream.eat('\'') ? context.parent : context, false);
	};

	var nextTemporaries = function(stream, context, state) {
		var token = new Token(null, context, false);
		var char = stream.next();

		if (char === '|') {
			token.context = context.parent;
			token.eos = true;

		} else {
			stream.eatWhile(/[^|]/);
			token.name = 'variable';
		}

		return token;
	}

	return {
		startState: function() {
			return new State;
		},

		token: function(stream, state) {
			state.userIndent(stream.indentation());

			if (stream.eatSpace()) {
				return null;
			}

			var token = state.context.next(stream, state.context, state);
			state.context = token.context;
			state.expectVariable = token.eos;

			state.lastToken = token;
			return token.name;
		},

		blankLine: function(state) {
			state.userIndent(0);
		},

		indent: function(state, textAfter) {
			var i = state.context.next === next && textAfter && textAfter.charAt(0) === ']' ? -1 : state.userIndentationDelta;
			return (state.indentation + i) * config.indentUnit;
		},

		electricChars: ']'
	};

});

CodeMirror.defineMIME('text/x-stsrc', {name: 'smalltalk'});<|endoftext|>


<commit_before><commit_msg>git-svn-id: svn://fileserver/activex/AVS/Sources/TeamlabOffice/trunk/ServerComponents@53313 954022d7-b5bf-4e40-9824-e11837661b57<commit_after><|endoftext|>


<commit_before>const commandLineArgs = require('command-line-args');
const sleep = require('system-sleep');
const raml = require('raml-parser');
const deasync = require('deasync');
const http = require('http');
const uuid = require('uuid');
const util = require('util');
const fs = require('fs');
const ip = require('ip');

function ServiceRAMLParser() {
  var self = this;

  self.extractResources = function(filename) {
    // Parse RAML file to retrieve available resources list
    return raml.loadFile(filename).then(function(data) {
      var resources = [];
      data.resources.forEach(function(node) {
        self.extractNodeResources(node).forEach(function(resource) {
          resources.push(resource);
        })
      });
      return resources;
    }, function(error) {
      throw new Error(error);
    });
  }

  self.extractNodeResources = function(node) {
    var paths = [];

    if (node.resources) {
      node.resources.forEach(function(childNode) {
        self.extractNodeResources(childNode).forEach(function(path) {
          paths.push(node.relativeUri + path);
        });
      });
    }

    if (node.methods instanceof Array && node.methods.length > 0){
      paths.push(node.relativeUri);
    }

    return paths;
  }
}
function ServiceConsulRegistrator(host, port) {
  self = this;
  self.host = host;
  self.port = port;
  self.attempts = 10;
  self.attemptTimeout = 500;

  self.registerService = function(serviceData) {
    return registerServiceBase(serviceData, 'PUT');
  }

  self.deregisterService = function(serviceData) {
    return registerServiceBase(serviceData, 'DELETE');
  }

  self.registerResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'PUT');
  }

  self.deregisterResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'DELETE');
  }

  function registerServiceBase(serviceData, mode) {
    var requestPath = mode === 'PUT' ? 'register' : 'deregister/' + serviceData.ID;
    var data = JSON.stringify(serviceData);
    var settings = {
      host: self.host,
      port: self.port,
      path: '/v1/agent/service/' + requestPath,
      method: 'PUT'
    }

    // Send the request
    for (var i = 0; i < self.attempts; i++) {
      var response = awaitRequest(settings, data);

      if (response.statusCode === 200) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function registerResourcesBase(serviceData, resourceArray, mode) {
    var settings = [];
    var data = [];

    // Prepare settings and data for requests
    resourceArray.forEach(function(resource) {
      var setting = {
        host: self.host,
        port: self.port,
        path: '/v1/kv/resources' + resource,
        method: mode
      }

      settings.push(setting);
      data.push(serviceData.Name);
    });

    // Send all requests at once
    for (var i = 0; i < self.attempts; i++) {
      var responses = awaitRequests(settings, data);
      var success = !responses.some(function(response) { return response.statusCode !== 200 });

      if (success) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function awaitRequests(settingArray, dataArray) {
    // Runs multiple parallel HTTP requests and waits for their completion
    var size = settingArray.length;
    var results = settingArray.map(function() { return null; });
    var completed = 0;

    for (var i = 0; i < size; i++) {
      (function(i) {
        var request = http.request(settingArray[i], function(response) {
          // This operation is safe in node.js
          completed++;
          results[i] = response;
        });

        request.write(dataArray[i]);
        request.end();
      })(i);
    };

    while(completed !== size) {
      deasync.runLoopOnce();
    }

    return results;
  }

  function awaitRequest(settings, data) {
    // Runs a single HTTP request synchronously
    var result;
    var request = http.request(settings, function(response) {
      result = response;
    });

    request.write(data);
    request.end();

    while(!result) {
      deasync.runLoopOnce();
    }

    return result;
  }
}
function RegistrationService(host, port) {
  var self = this;

  self.consulHost = host;
  self.consulPort = port;
  self.filename = '.consul_service_id~';

  self.registrator = new ServiceConsulRegistrator(self.consulHost, self.consulPort);;
  self.parser = new ServiceRAMLParser();

  self.register = function(args) {
    console.log("Registering service...");
    registrationBase(args, 'register');
    console.log("Registration finished!");
  }
  self.deregister = function(args) {
    console.log("Deregistering service...");
    registrationBase(args, 'deregister');
    console.log("Deregistration finished!");
  }

  function registrationBase(args, mode) {
    registerService(args.service, mode);

    if (args.raml) {
      registerRaml(args.service, args.raml, mode);
    }

    if (args.path) {
      registerPath(args.service, args.path, mode);
    }
  }
  function registerService(service, mode) {
    service.ID = generateServiceId(service.Name);

    if (mode === 'register') {
      self.registrator.registerService(service);
    } else {
      self.registrator.deregisterService(service);
    }
  }
  function registerRaml(service, filename, mode) {
    var completed = false;
    service.ID = generateServiceId(service.Name);

    self.parser.extractResources(filename).then(function(resources) {
      if (mode === 'register') {
        self.registrator.registerResources(service, resources);
      } else {
        self.registrator.deregisterResources(service, resources);
      }

      completed = true;
    });

    // Wait for RAML parsing
    while(!completed) {
      deasync.runLoopOnce();
    }
  }
  function registerPath(service, paths, mode) {
    service.ID = generateServiceId(service.Name);

    for (var i = 0; i < paths.length; i++) {
      if (paths[i].indexOf("/") !== 0) {
        paths[i] = "/" + paths[i];
      }
    }

    if (mode === 'register') {
      self.registrator.registerResources(service, paths);
    } else {
      self.registrator.deregisterResources(service, paths);
    }
  }

  function generateServiceId(serviceName) {
    var serviceId = null;

    try {
      fs.accessSync(self.filename, fs.R_OK | fs.W_OK);
      serviceId = fs.readFileSync(fs.openSync(self.filename, 'r+')).toString();
    } catch (err) {
      serviceId = uuid() + '-' + serviceName;
      fs.writeSync(fs.openSync(self.filename, 'w+'), serviceId);
    }

    return serviceId;
  }
  function clearServiceId() {
    try {
      fs.unlinkSync(self.filename);
    } catch (err) {
      console.error("Service ID file could not be found or accessed.");
    }
  }
}
function CommandLineResolver() {
  var self = this;

  self.processArgs = function(mapping) {
    mapping = mapping || getCommandArgsMapping();

    var args = commandLineArgs(mapping);
    var settings = getSettings(args);
    validateSettings(settings);

    return settings;
  }

  function validateSettings(settings) {
    if (!settings.command) {
      throw new Error("Command parameter is missing.");
    } else if (!(settings.raml || settings.path)) {
      throw new Error("You must either provide path or file parameter.");
    }
  }
  function getSettings(args) {
    var settings = {
      service: getDefaultServiceValues()
    };

    // Fill config file data
    if (args['config-file']) {
      var config = JSON.parse(fs.readFileSync(args['config-file']).toString());
      fillBaseSettings(settings, config);

      if (config['service']) {
        for (var key in config.service) {
          settings.service[key] = config.service[key];
        }
      }
    }

    // Fill command line arguments
    fillBaseSettings(settings, args);
    if (args['name']) {
      settings.service.Name = args.name;
    }

    return settings;
  }
  function getCommandArgsMapping() {
    return [
      { name: 'config-file', alias: 'f', type: String },
      { name: 'command', alias: 'c', type: String },
      { name: 'name', alias: 'n', type: String },
      { name: 'raml', alias: 'r', type: String },
      { name: 'path', alias: 'p', type: String, multiple: true }
    ];
  }
  function getDefaultServiceValues() {
    return {
      'ID': null,
      'Name': null,
      'Port': 80,
      'Address': ip.address(),
      'Tags': [],
      'EnableTagOverride': false
    };
  }
  function fillBaseSettings(settings, args) {
    var keys = ['command', 'path', 'raml'];
    for (var i = 0; i < keys.length; i++) {
      var keyword = keys[i];
      if (args[keyword]) {
        settings[keyword] = args[keyword];
      }
    }
  }
}

// Function ran while executing as script
(function register() {
  function formatError(error) {
    var templates = {
      "original": "%s",
      "formatted": "%s\n(original message: \"%s\")"
    };

    var messages = {
      "ECONNRESET": "Could not reach Consul host: " + consulHost + ":" + consulPort
    };

    var errorMessage = error.message;
    var formatMessage = messages[error.code];

    return formatMessage ?
      util.format(templates.formatted, formatMessage, errorMessage) : errorMessage;
  }

  function getServices(consulHost, consulPort) {
    var result;

    var settings = {
      host: consulHost,
      port: consulPort,
      path: '/v1/catalog/services',
      method: 'GET'
    }

    var request = http.request(settings, function(response) {
      response.on('data', function (chunk) {
        result = chunk;
      });
    });

    request.end();

    while(!result) {
      deasync.runLoopOnce();
    }

    return result;
  }

  function awaitConsul(consulHost, consulPort) {
    var services;

    for (var i = 0; i < 10; i++) {
      sleep(1000);

      services = JSON.parse(getServices(consulHost, consulPort));

      // Checks whether consul service has already registered
      if (services.consul) {
        break;
      }
    }
  }

  var consulHost = process.env.CONSUL_HOST || 'consul';
  var consulPort = process.env.CONSUL_PORT || '8500';

  awaitConsul(consulHost, consulPort);

  var registration = new RegistrationService(consulHost, consulPort);

  try {
    // Retrieve arguments passed to script
    var args = new CommandLineResolver().processArgs();

    if (args.command === 'register') {
      registration.register(args);
    } else if (args.command === 'deregister') {
      registration.deregister(args);
    } else {
      throw new Error("Invalid command. It should be either 'register' or 'deregister'.")
    }
  } catch(err) {
    console.error("[ERROR]:")
    console.error(formatError(err));
    process.exit(1);
  }
})();
<commit_msg>OLMIS-1729 Adjusted consul registration script<commit_after>const commandLineArgs = require('command-line-args');
const sleep = require('system-sleep');
const raml = require('raml-parser');
const deasync = require('deasync');
const http = require('http');
const uuid = require('uuid');
const util = require('util');
const fs = require('fs');
const ip = require('ip');

function ServiceRAMLParser() {
  var self = this;

  self.extractResources = function(filename) {
    // Parse RAML file to retrieve available resources list
    return raml.loadFile(filename).then(function(data) {
      var resources = [];
      data.resources.forEach(function(node) {
        self.extractNodeResources(node).forEach(function(resource) {
          resources.push(resource);
        })
      });
      return resources;
    }, function(error) {
      throw new Error(error);
    });
  }

  self.extractNodeResources = function(node) {
    var paths = [];

    if (node.resources) {
      node.resources.forEach(function(childNode) {
        self.extractNodeResources(childNode).forEach(function(path) {
          paths.push(node.relativeUri + path);
        });
      });
    }

    if (node.methods instanceof Array && node.methods.length > 0){
      paths.push(node.relativeUri);
    }

    return paths;
  }
}
function ServiceConsulRegistrator(host, port) {
  self = this;
  self.host = host;
  self.port = port;
  self.attempts = 10;
  self.attemptTimeout = 500;

  self.registerService = function(serviceData) {
    return registerServiceBase(serviceData, 'PUT');
  }

  self.deregisterService = function(serviceData) {
    return registerServiceBase(serviceData, 'DELETE');
  }

  self.registerResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'PUT');
  }

  self.deregisterResources = function(serviceData, resourceArray) {
    return registerResourcesBase(serviceData, resourceArray, 'DELETE');
  }

  function registerServiceBase(serviceData, mode) {
    var requestPath = mode === 'PUT' ? 'register' : 'deregister/' + serviceData.ID;
    var data = JSON.stringify(serviceData);
    var settings = {
      host: self.host,
      port: self.port,
      path: '/v1/agent/service/' + requestPath,
      method: 'PUT'
    }

    // Send the request
    for (var i = 0; i < self.attempts; i++) {
      var response = awaitRequest(settings, data);

      if (response.statusCode === 200) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function registerResourcesBase(serviceData, resourceArray, mode) {
    var settings = [];
    var data = [];

    // Prepare settings and data for requests
    resourceArray.forEach(function(resource) {
      var setting = {
        host: self.host,
        port: self.port,
        path: '/v1/kv/resources' + resource,
        method: mode
      }

      settings.push(setting);
      data.push(serviceData.Name);
    });

    // Send all requests at once
    for (var i = 0; i < self.attempts; i++) {
      var responses = awaitRequests(settings, data);
      var success = !responses.some(function(response) { return response.statusCode !== 200 });

      if (success) {
        break;
      }

      sleep(self.attemptTimeout);
    }
  }

  function awaitRequests(settingArray, dataArray) {
    // Runs multiple parallel HTTP requests and waits for their completion
    var size = settingArray.length;
    var results = settingArray.map(function() { return null; });
    var completed = 0;

    for (var i = 0; i < size; i++) {
      (function(i) {
        var request = http.request(settingArray[i], function(response) {
          // This operation is safe in node.js
          completed++;
          results[i] = response;
        });

        request.write(dataArray[i]);
        request.end();
      })(i);
    };

    while(completed !== size) {
      deasync.runLoopOnce();
    }

    return results;
  }

  function awaitRequest(settings, data) {
    // Runs a single HTTP request synchronously
    var result;
    var request = http.request(settings, function(response) {
      result = response;
    });

    request.write(data);
    request.end();

    while(!result) {
      deasync.runLoopOnce();
    }

    return result;
  }
}
function RegistrationService(host, port) {
  var self = this;

  self.consulHost = host;
  self.consulPort = port;
  self.filename = '.consul_service_id~';

  self.registrator = new ServiceConsulRegistrator(self.consulHost, self.consulPort);
  self.parser = new ServiceRAMLParser();

  self.register = function(args) {
    console.log("Registering service...");
    registrationBase(args, 'register');
    console.log("Registration finished!");
  }
  self.deregister = function(args) {
    console.log("Deregistering service...");
    registrationBase(args, 'deregister');
    console.log("Deregistration finished!");
  }

  function registrationBase(args, mode) {
    registerService(args.service, mode);

    if (args.raml) {
      registerRaml(args.service, args.raml, mode);
    }

    if (args.path) {
      registerPath(args.service, args.path, mode);
    }
  }
  function registerService(service, mode) {
    service.ID = generateServiceId(service.Name);

    if (mode === 'register') {
      self.registrator.registerService(service);
    } else {
      self.registrator.deregisterService(service);
    }
  }
  function registerRaml(service, filename, mode) {
    var completed = false;
    service.ID = generateServiceId(service.Name);

    self.parser.extractResources(filename).then(function(resources) {
      if (mode === 'register') {
        self.registrator.registerResources(service, resources);
      } else {
        self.registrator.deregisterResources(service, resources);
      }

      completed = true;
    });

    // Wait for RAML parsing
    while(!completed) {
      deasync.runLoopOnce();
    }
  }
  function registerPath(service, paths, mode) {
    service.ID = generateServiceId(service.Name);

    for (var i = 0; i < paths.length; i++) {
      if (paths[i].indexOf("/") !== 0) {
        paths[i] = "/" + paths[i];
      }
    }

    if (mode === 'register') {
      self.registrator.registerResources(service, paths);
    } else {
      self.registrator.deregisterResources(service, paths);
    }
  }

  function generateServiceId(serviceName) {
    var serviceId = null;

    try {
      fs.accessSync(self.filename, fs.R_OK | fs.W_OK);
      serviceId = fs.readFileSync(fs.openSync(self.filename, 'r+')).toString();
    } catch (err) {
      serviceId = uuid() + '-' + serviceName;
      fs.writeSync(fs.openSync(self.filename, 'w+'), serviceId);
    }

    return serviceId;
  }
  function clearServiceId() {
    try {
      fs.unlinkSync(self.filename);
    } catch (err) {
      console.error("Service ID file could not be found or accessed.");
    }
  }
}
function CommandLineResolver() {
  var self = this;

  self.processArgs = function(mapping) {
    mapping = mapping || getCommandArgsMapping();

    var args = commandLineArgs(mapping);
    var settings = getSettings(args);
    validateSettings(settings);

    return settings;
  }

  function validateSettings(settings) {
    if (!settings.command) {
      throw new Error("Command parameter is missing.");
    } else if (!(settings.raml || settings.path)) {
      throw new Error("You must either provide path or file parameter.");
    }
  }
  function getSettings(args) {
    var settings = {
      service: getDefaultServiceValues()
    };

    // Fill config file data
    if (args['config-file']) {
      var config = JSON.parse(fs.readFileSync(args['config-file']).toString());
      fillBaseSettings(settings, config);

      if (config['service']) {
        for (var key in config.service) {
          settings.service[key] = config.service[key];
        }
      }
    }

    // Fill command line arguments
    fillBaseSettings(settings, args);
    if (args['name']) {
      settings.service.Name = args.name;
    }

    return settings;
  }
  function getCommandArgsMapping() {
    return [
      { name: 'config-file', alias: 'f', type: String },
      { name: 'command', alias: 'c', type: String },
      { name: 'name', alias: 'n', type: String },
      { name: 'raml', alias: 'r', type: String },
      { name: 'path', alias: 'p', type: String, multiple: true }
    ];
  }
  function getDefaultServiceValues() {
    return {
      'ID': null,
      'Name': null,
      'Port': 80,
      'Address': ip.address(),
      'Tags': [],
      'EnableTagOverride': false
    };
  }
  function fillBaseSettings(settings, args) {
    var keys = ['command', 'path', 'raml'];
    for (var i = 0; i < keys.length; i++) {
      var keyword = keys[i];
      if (args[keyword]) {
        settings[keyword] = args[keyword];
      }
    }
  }
}

// Function ran while executing as script
(function register() {
  function formatError(error) {
    var templates = {
      "original": "%s",
      "formatted": "%s\n(original message: \"%s\")"
    };

    var messages = {
      "ECONNRESET": "Could not reach Consul host: " + consulHost + ":" + consulPort
    };

    var errorMessage = error.message;
    var formatMessage = messages[error.code];

    return formatMessage ?
      util.format(templates.formatted, formatMessage, errorMessage) : errorMessage;
  }

  function awaitConsul(consulHost, consulPort) {
    var result;

    var settings = {
      host: consulHost,
      port: consulPort,
      path: '/v1/catalog/services'
    }

    for (var i = 0; i < 5; i++) {
      sleep(1000);
      var request = http.get(settings, function(response) {
        result = response;
      });

      while(!result) {
        deasync.runLoopOnce();
      }

      if (result.statusCode === 200) {
        return true;
      }
    }
    return false;
  }

  var consulHost = process.env.CONSUL_HOST || 'consul';
  var consulPort = process.env.CONSUL_PORT || '8500';

  if (!awaitConsul(consulHost, consulPort)) {
    throw new Error("The Consul service has not started up properly.");
  }

  var registration = new RegistrationService(consulHost, consulPort);

  try {
    // Retrieve arguments passed to script
    var args = new CommandLineResolver().processArgs();

    if (args.command === 'register') {
      registration.register(args);
    } else if (args.command === 'deregister') {
      registration.deregister(args);
    } else {
      throw new Error("Invalid command. It should be either 'register' or 'deregister'.")
    }
  } catch(err) {
    console.error("[ERROR]:")
    console.error(formatError(err));
    process.exit(1);
  }
})();
<|endoftext|>


<commit_before>import _ from 'underscore';
import Vue from 'vue';

import { modes } from '../constants';
import {
  sanitizeFiles,
  sanitizeAssessmentItems,
  validateNodeFiles,
  validateAssessmentItem,
  validateNodeDetails,
  parseNode,
} from '../utils';
import { getSelected } from './utils';
import State from 'edit_channel/state';
import Constants from 'edit_channel/constants/index';

export function RESET_STATE(state) {
  Object.assign(state, {
    nodes: [],
    selectedIndices: [],
    changes: {},
    mode: modes.VIEW_ONLY,
  });
}

export function SET_MODE(state, mode) {
  state.mode = mode;
}

export const OPEN_DIALOG = (state, { title, message, submitLabel, onSubmit, onCancel }) => {
  const closeDialog = () => {
    state.dialog = {
      open: false,
      title: '',
      message: '',
      submitLabel: '',
      onSubmit: () => {},
      onCancel: () => {},
    };
  };

  state.dialog = {
    open: true,
    title,
    message,
    submitLabel,
    onSubmit: () => {
      if (typeof onSubmit === 'function') {
        onSubmit();
      }
      closeDialog();
    },
    onCancel: () => {
      if (typeof onCancel === 'function') {
        onCancel();
      }
      closeDialog();
    },
  };
};

/*********** AGGREGATE CHANGES ***********/

const editableFields = [
  'language',
  'title',
  'description',
  'license',
  'license_description',
  'copyright_holder',
  'author',
  'role_visibility',
  'aggregator',
  'provider',
];

const extraFields = ['mastery_model', 'm', 'n', 'randomize'];

function _generateSharedData(items, fields) {
  return _.reduce(
    fields,
    (sharedData, field) => {
      let values = _.pluck(items, field);
      let allIdentical = items.length === 1 || _.every(values, f => f === values[0]);
      sharedData[field] = {
        value: allIdentical ? values[0] : null,
        varied: !allIdentical, // Indicate items differ in values for this field
      };
      return sharedData;
    },
    {}
  );
}

export function SET_CHANGES(state) {
  state.changes = {};
  let selected = getSelected(state);
  state.changes = _generateSharedData(selected, editableFields);

  let extraFieldItems = _.pluck(selected, 'extra_fields');
  state.changes.extra_fields = _generateSharedData(extraFieldItems, extraFields);

  if (state.selectedIndices) {
    let tags = _.map(selected, n => _.pluck(n.tags, 'tag_name'));
    state.changes.tags = _.intersection.apply(_, tags);
  } else {
    state.changes.tags = [];
  }

  _.each(state.nodes, (node, i) => {
    if (!state.selectedIndices.includes(i)) node.isNew = false;
  });
}

/*********** SET NODES ***********/

export function SET_NODES(state, nodes) {
  _.each(nodes, node => {
    node = parseNode(node);
    node.changesStaged = false;
    node['_COMPLETE'] = false;
  });
  state.nodes = nodes;
}

export function SET_LOADED_NODES(state, nodes) {
  _.each(_.values(nodes), value => {
    // First, try to find a matching id. If none exists (i.e. the node is new), use sort_order
    let match =
      _.findWhere(state.nodes, { id: value.id }) ||
      _.findWhere(state.nodes, { sort_order: value.sort_order });
    if (match) {
      _.extendOwn(match, parseNode(value));
      match.changesStaged = false;
      match['_COMPLETE'] = true;
    }
  });

  _.defer(() => SET_CHANGES(state));
}

/**
 * Save new assessment items data to a node with a given index
 * in an array of all nodes.
 */
export const SET_NODE_ASSESSMENT_ITEMS = (state, { nodeIdx, assessmentItems }) => {
  const nodes = [...state.nodes];

  nodes[nodeIdx] = {
    ...nodes[nodeIdx],
    assessment_items: assessmentItems,
  };

  Vue.set(state, 'nodes', nodes);
};

/*********** VALIDATE NODES ***********/

/**
 * Sanitize assessment items of a node with a given index.
 */
export const SANITIZE_NODE_ASSESSMENT_ITEMS = (state, { nodeIdx }) => {
  let assessmentItems = [];

  if (state.nodes[nodeIdx].assessment_items) {
    assessmentItems = [...state.nodes[nodeIdx].assessment_items];
  }
  const sanitizedAssessmentItems = sanitizeAssessmentItems(assessmentItems);

  Vue.set(state.nodes[nodeIdx], 'assessment_items', sanitizedAssessmentItems);
};

/**
 * Sanitize files of a node with a given index.
 */
export const SANITIZE_NODE_FILES = (state, { nodeIdx }) => {
  let files = [];

  if (state.nodes[nodeIdx].files && state.nodes[nodeIdx]['_COMPLETE']) {
    files = [...state.nodes[nodeIdx].files];
  }

  const sanitizedFiles = sanitizeFiles(files);
  Vue.set(state.nodes[nodeIdx], 'files', sanitizedFiles);
};

/**
 * Validate node details (title, licence etc.) and save validation results
 * to state.validation.
 */
export const VALIDATE_NODE_DETAILS = (state, { nodeIdx }) => {
  // cleanup previous node details validation
  // setup a new node validation object if there is none
  const previousValidationIdx = state.validation.findIndex(
    nodeValidation => nodeValidation.nodeIdx === nodeIdx
  );
  let validationIdx;
  if (previousValidationIdx === -1) {
    state.validation.push({
      nodeIdx,
      errors: {},
    });
    validationIdx = state.validation.length - 1;
  } else {
    Vue.set(state.validation[previousValidationIdx].errors, 'details', []);
    validationIdx = previousValidationIdx;
  }

  const node = state.nodes[nodeIdx];

  Vue.set(state.validation[validationIdx].errors, 'details', validateNodeDetails(node));
};

/**
 * Validate node files and save validation results
 * to state.validation.
 */
export const VALIDATE_NODE_FILES = (state, { nodeIdx }) => {
  // cleanup previous node details validation
  // setup a new node validation object if there is none
  const previousValidationIdx = state.validation.findIndex(
    nodeValidation => nodeValidation.nodeIdx === nodeIdx
  );
  let validationIdx;
  if (previousValidationIdx === -1) {
    state.validation.push({
      nodeIdx,
      errors: {},
    });
    validationIdx = state.validation.length - 1;
  } else {
    Vue.set(state.validation[previousValidationIdx].errors, 'files', []);
    validationIdx = previousValidationIdx;
  }

  const node = state.nodes[nodeIdx];

  Vue.set(state.validation[validationIdx].errors, 'files', validateNodeFiles(node));
};

/**
 * Validate node assessment items and save validation results
 * to state.validation.
 */
export const VALIDATE_NODE_ASSESSMENT_ITEMS = (state, { nodeIdx }) => {
  // cleanup previous node assessment items validation
  // setup a new node validation object if there is none
  const previousValidationIdx = state.validation.findIndex(
    nodeValidation => nodeValidation.nodeIdx === nodeIdx
  );
  let validationIdx;
  if (previousValidationIdx === -1) {
    state.validation.push({
      nodeIdx,
      errors: {},
    });
    validationIdx = state.validation.length - 1;
  } else {
    Vue.set(state.validation[previousValidationIdx].errors, 'assessment_items', []);
    validationIdx = previousValidationIdx;
  }

  if (!state.nodes[nodeIdx].assessment_items || !state.nodes[nodeIdx].assessment_items.length) {
    return;
  }

  const assessmentItems = state.nodes[nodeIdx].assessment_items;
  const assessmentItemsErrors = [];

  assessmentItems.forEach(item => {
    assessmentItemsErrors.push(validateAssessmentItem(item));
  });

  Vue.set(state.validation[validationIdx].errors, 'assessment_items', assessmentItemsErrors);
};

/*********** SELECT NODES ***********/

export function RESET_SELECTED(state) {
  state.selectedIndices = [];
  state.changes = {};
}

export function SET_SELECTED(state, payload) {
  state.selectedIndices = payload;
  SET_CHANGES(state);
}

export function SET_NODE(state, index) {
  state.selectedIndices = [index];
  SET_CHANGES(state);
}

export function SELECT_NODE(state, index) {
  state.selectedIndices.push(index);
  SET_CHANGES(state);
}

export function DESELECT_NODE(state, index) {
  state.selectedIndices = _.reject(state.selectedIndices, i => i === index);
  SET_CHANGES(state);
}

export function SELECT_ALL_NODES(state) {
  state.selectedIndices = _.range(state.nodes.length);
  SET_CHANGES(state);
}

/*********** SET FIELDS ***********/

export function UPDATE_NODE(state, payload) {
  let selected = getSelected(state);
  _.each(selected, node => {
    _.assign(node, payload);
    node.changed = true;
    node.changesStaged = true;
  });
  SET_CHANGES(state);
}

export function SET_TAGS(state, tags) {
  let removed = _.difference(state.changes.tags, tags);
  let selected = getSelected(state);

  _.each(selected, node => {
    // Node tags + new tags - old tags
    node.tags = _.chain(node.tags)
      .map(t => t.tag_name || t) // Loaded values are in dict form,
      .union(tags)
      .difference(removed)
      .map(t => ({ tag_name: t, channel: State.current_channel.id }))
      .value();
    node.changesStaged = true;
    node.changed = true;
  });

  let newTags = _.difference(tags, State.Store.getters.contentTags);
  _.each(newTags, tag => {
    State.Store.commit('ADD_CONTENT_TAG', {
      tag_name: tag,
      channel: State.current_channel.id,
    });
  });
  _.each(removed, tag => {
    State.Store.commit('REMOVE_CONTENT_TAG_BY_NAME', tag);
  });
  SET_CHANGES(state);
}

export function UPDATE_EXTRA_FIELDS(state, obj) {
  let selected = getSelected(state);
  _.each(selected, node => {
    if (!_.isEqual(node.extra_fields, obj)) {
      _.assign(node.extra_fields, obj);
      node.changed = true;
      node.changesStaged = true;
    }
  });
  SET_CHANGES(state);
}

/*********** NODE OPERATIONS ***********/

export function ADD_NODE(state, payload) {
  let preferences = payload.kind === 'topic' ? {} : window.preferences;
  let data = _.reduce(
    editableFields,
    (dict, field) => {
      switch (field) {
        case 'license':
          dict.license = _.findWhere(Constants.Licenses, { license_name: preferences.license });
          dict.license = (dict.license && dict.license.id) || null;
          break;
        default:
          dict[field] = preferences[field];
      }
      return dict;
    },
    {}
  );
  let extraFieldData = _.reduce(
    extraFields,
    (dict, field) => {
      switch (field) {
        case 'm':
          dict.m = preferences.m_value || null;
          break;
        case 'n':
          dict.n = preferences.n_value || null;
          break;
        default:
          dict[field] = preferences[field];
      }
      return dict;
    },
    {}
  );
  state.nodes.push({
    ...data,
    files: [],
    assessment_items: [],
    prerequisite: [],
    ancestors: [],
    extra_fields: extraFieldData,
    tags: [],
    role_visibility: 'learner',
    changesStaged: true,
    isNew: true,
    parent: state.currentNode.id,
    sort_order: state.currentNode.metadata.max_sort_order + state.nodes.length + 1,
    _COMPLETE: true,
    ...payload,
  });
  _.defer(() => {
    // Wait for node to render
    state.selectedIndices = [state.nodes.length - 1];
    SET_CHANGES(state);
  });
}

export function REMOVE_NODE(state, index) {
  state.nodes = _.reject(state.nodes, (n, i) => i === index);
  state.selectedIndices = _.reject(state.selectedIndices, i => i === index);
  SET_CHANGES(state);
}

export function PREP_NODES_FOR_SAVE(state) {
  _.each(state.nodes, node => (node.isNew = false));
}

/*********** FILE OPERATIONS ***********/

export function ADD_NODES_FROM_FILES(state, newFiles) {
  newFiles.forEach(file => {
    ADD_NODE(state, {
      title: file.name,
      kind: file.kind,
      metadata: {
        resource_size: file.file_size,
      },
      files: [file],
    });
  });
}

export function ADD_FILE_TO_NODE(state, payload) {
  if (payload.file) {
    state.nodes[payload.index].files = _.reject(state.nodes[payload.index].files, f => {
      return f.preset.id === payload.file.preset.id;
    });
    state.nodes[payload.index].files.push(payload.file);
    state.nodes[payload.index].changesStaged = true;
    SET_CHANGES(state);
  }
}

export function REMOVE_FILE_FROM_NODE(state, payload) {
  state.nodes[payload.index].files = _.reject(state.nodes[payload.index].files, f => {
    return f.id === payload.fileID;
  });
  state.nodes[payload.index].changesStaged = true;
  SET_CHANGES(state);
}
<commit_msg>Allow files with the same preset but different languages<commit_after>import _ from 'underscore';
import Vue from 'vue';

import { modes } from '../constants';
import {
  sanitizeFiles,
  sanitizeAssessmentItems,
  validateNodeFiles,
  validateAssessmentItem,
  validateNodeDetails,
  parseNode,
} from '../utils';
import { getSelected } from './utils';
import State from 'edit_channel/state';
import Constants from 'edit_channel/constants/index';

export function RESET_STATE(state) {
  Object.assign(state, {
    nodes: [],
    selectedIndices: [],
    changes: {},
    mode: modes.VIEW_ONLY,
  });
}

export function SET_MODE(state, mode) {
  state.mode = mode;
}

export const OPEN_DIALOG = (state, { title, message, submitLabel, onSubmit, onCancel }) => {
  const closeDialog = () => {
    state.dialog = {
      open: false,
      title: '',
      message: '',
      submitLabel: '',
      onSubmit: () => {},
      onCancel: () => {},
    };
  };

  state.dialog = {
    open: true,
    title,
    message,
    submitLabel,
    onSubmit: () => {
      if (typeof onSubmit === 'function') {
        onSubmit();
      }
      closeDialog();
    },
    onCancel: () => {
      if (typeof onCancel === 'function') {
        onCancel();
      }
      closeDialog();
    },
  };
};

/*********** AGGREGATE CHANGES ***********/

const editableFields = [
  'language',
  'title',
  'description',
  'license',
  'license_description',
  'copyright_holder',
  'author',
  'role_visibility',
  'aggregator',
  'provider',
];

const extraFields = ['mastery_model', 'm', 'n', 'randomize'];

function _generateSharedData(items, fields) {
  return _.reduce(
    fields,
    (sharedData, field) => {
      let values = _.pluck(items, field);
      let allIdentical = items.length === 1 || _.every(values, f => f === values[0]);
      sharedData[field] = {
        value: allIdentical ? values[0] : null,
        varied: !allIdentical, // Indicate items differ in values for this field
      };
      return sharedData;
    },
    {}
  );
}

export function SET_CHANGES(state) {
  state.changes = {};
  let selected = getSelected(state);
  state.changes = _generateSharedData(selected, editableFields);

  let extraFieldItems = _.pluck(selected, 'extra_fields');
  state.changes.extra_fields = _generateSharedData(extraFieldItems, extraFields);

  if (state.selectedIndices) {
    let tags = _.map(selected, n => _.pluck(n.tags, 'tag_name'));
    state.changes.tags = _.intersection.apply(_, tags);
  } else {
    state.changes.tags = [];
  }

  _.each(state.nodes, (node, i) => {
    if (!state.selectedIndices.includes(i)) node.isNew = false;
  });
}

/*********** SET NODES ***********/

export function SET_NODES(state, nodes) {
  _.each(nodes, node => {
    node = parseNode(node);
    node.changesStaged = false;
    node['_COMPLETE'] = false;
  });
  state.nodes = nodes;
}

export function SET_LOADED_NODES(state, nodes) {
  _.each(_.values(nodes), value => {
    // First, try to find a matching id. If none exists (i.e. the node is new), use sort_order
    let match =
      _.findWhere(state.nodes, { id: value.id }) ||
      _.findWhere(state.nodes, { sort_order: value.sort_order });
    if (match) {
      _.extendOwn(match, parseNode(value));
      match.changesStaged = false;
      match['_COMPLETE'] = true;
    }
  });

  _.defer(() => SET_CHANGES(state));
}

/**
 * Save new assessment items data to a node with a given index
 * in an array of all nodes.
 */
export const SET_NODE_ASSESSMENT_ITEMS = (state, { nodeIdx, assessmentItems }) => {
  const nodes = [...state.nodes];

  nodes[nodeIdx] = {
    ...nodes[nodeIdx],
    assessment_items: assessmentItems,
  };

  Vue.set(state, 'nodes', nodes);
};

/*********** VALIDATE NODES ***********/

/**
 * Sanitize assessment items of a node with a given index.
 */
export const SANITIZE_NODE_ASSESSMENT_ITEMS = (state, { nodeIdx }) => {
  let assessmentItems = [];

  if (state.nodes[nodeIdx].assessment_items) {
    assessmentItems = [...state.nodes[nodeIdx].assessment_items];
  }
  const sanitizedAssessmentItems = sanitizeAssessmentItems(assessmentItems);

  Vue.set(state.nodes[nodeIdx], 'assessment_items', sanitizedAssessmentItems);
};

/**
 * Sanitize files of a node with a given index.
 */
export const SANITIZE_NODE_FILES = (state, { nodeIdx }) => {
  let files = [];

  if (state.nodes[nodeIdx].files && state.nodes[nodeIdx]['_COMPLETE']) {
    files = [...state.nodes[nodeIdx].files];
  }

  const sanitizedFiles = sanitizeFiles(files);
  Vue.set(state.nodes[nodeIdx], 'files', sanitizedFiles);
};

/**
 * Validate node details (title, licence etc.) and save validation results
 * to state.validation.
 */
export const VALIDATE_NODE_DETAILS = (state, { nodeIdx }) => {
  // cleanup previous node details validation
  // setup a new node validation object if there is none
  const previousValidationIdx = state.validation.findIndex(
    nodeValidation => nodeValidation.nodeIdx === nodeIdx
  );
  let validationIdx;
  if (previousValidationIdx === -1) {
    state.validation.push({
      nodeIdx,
      errors: {},
    });
    validationIdx = state.validation.length - 1;
  } else {
    Vue.set(state.validation[previousValidationIdx].errors, 'details', []);
    validationIdx = previousValidationIdx;
  }

  const node = state.nodes[nodeIdx];

  Vue.set(state.validation[validationIdx].errors, 'details', validateNodeDetails(node));
};

/**
 * Validate node files and save validation results
 * to state.validation.
 */
export const VALIDATE_NODE_FILES = (state, { nodeIdx }) => {
  // cleanup previous node details validation
  // setup a new node validation object if there is none
  const previousValidationIdx = state.validation.findIndex(
    nodeValidation => nodeValidation.nodeIdx === nodeIdx
  );
  let validationIdx;
  if (previousValidationIdx === -1) {
    state.validation.push({
      nodeIdx,
      errors: {},
    });
    validationIdx = state.validation.length - 1;
  } else {
    Vue.set(state.validation[previousValidationIdx].errors, 'files', []);
    validationIdx = previousValidationIdx;
  }

  const node = state.nodes[nodeIdx];

  Vue.set(state.validation[validationIdx].errors, 'files', validateNodeFiles(node));
};

/**
 * Validate node assessment items and save validation results
 * to state.validation.
 */
export const VALIDATE_NODE_ASSESSMENT_ITEMS = (state, { nodeIdx }) => {
  // cleanup previous node assessment items validation
  // setup a new node validation object if there is none
  const previousValidationIdx = state.validation.findIndex(
    nodeValidation => nodeValidation.nodeIdx === nodeIdx
  );
  let validationIdx;
  if (previousValidationIdx === -1) {
    state.validation.push({
      nodeIdx,
      errors: {},
    });
    validationIdx = state.validation.length - 1;
  } else {
    Vue.set(state.validation[previousValidationIdx].errors, 'assessment_items', []);
    validationIdx = previousValidationIdx;
  }

  if (!state.nodes[nodeIdx].assessment_items || !state.nodes[nodeIdx].assessment_items.length) {
    return;
  }

  const assessmentItems = state.nodes[nodeIdx].assessment_items;
  const assessmentItemsErrors = [];

  assessmentItems.forEach(item => {
    assessmentItemsErrors.push(validateAssessmentItem(item));
  });

  Vue.set(state.validation[validationIdx].errors, 'assessment_items', assessmentItemsErrors);
};

/*********** SELECT NODES ***********/

export function RESET_SELECTED(state) {
  state.selectedIndices = [];
  state.changes = {};
}

export function SET_SELECTED(state, payload) {
  state.selectedIndices = payload;
  SET_CHANGES(state);
}

export function SET_NODE(state, index) {
  state.selectedIndices = [index];
  SET_CHANGES(state);
}

export function SELECT_NODE(state, index) {
  state.selectedIndices.push(index);
  SET_CHANGES(state);
}

export function DESELECT_NODE(state, index) {
  state.selectedIndices = _.reject(state.selectedIndices, i => i === index);
  SET_CHANGES(state);
}

export function SELECT_ALL_NODES(state) {
  state.selectedIndices = _.range(state.nodes.length);
  SET_CHANGES(state);
}

/*********** SET FIELDS ***********/

export function UPDATE_NODE(state, payload) {
  let selected = getSelected(state);
  _.each(selected, node => {
    _.assign(node, payload);
    node.changed = true;
    node.changesStaged = true;
  });
  SET_CHANGES(state);
}

export function SET_TAGS(state, tags) {
  let removed = _.difference(state.changes.tags, tags);
  let selected = getSelected(state);

  _.each(selected, node => {
    // Node tags + new tags - old tags
    node.tags = _.chain(node.tags)
      .map(t => t.tag_name || t) // Loaded values are in dict form,
      .union(tags)
      .difference(removed)
      .map(t => ({ tag_name: t, channel: State.current_channel.id }))
      .value();
    node.changesStaged = true;
    node.changed = true;
  });

  let newTags = _.difference(tags, State.Store.getters.contentTags);
  _.each(newTags, tag => {
    State.Store.commit('ADD_CONTENT_TAG', {
      tag_name: tag,
      channel: State.current_channel.id,
    });
  });
  _.each(removed, tag => {
    State.Store.commit('REMOVE_CONTENT_TAG_BY_NAME', tag);
  });
  SET_CHANGES(state);
}

export function UPDATE_EXTRA_FIELDS(state, obj) {
  let selected = getSelected(state);
  _.each(selected, node => {
    if (!_.isEqual(node.extra_fields, obj)) {
      _.assign(node.extra_fields, obj);
      node.changed = true;
      node.changesStaged = true;
    }
  });
  SET_CHANGES(state);
}

/*********** NODE OPERATIONS ***********/

export function ADD_NODE(state, payload) {
  let preferences = payload.kind === 'topic' ? {} : window.preferences;
  let data = _.reduce(
    editableFields,
    (dict, field) => {
      switch (field) {
        case 'license':
          dict.license = _.findWhere(Constants.Licenses, { license_name: preferences.license });
          dict.license = (dict.license && dict.license.id) || null;
          break;
        default:
          dict[field] = preferences[field];
      }
      return dict;
    },
    {}
  );
  let extraFieldData = _.reduce(
    extraFields,
    (dict, field) => {
      switch (field) {
        case 'm':
          dict.m = preferences.m_value || null;
          break;
        case 'n':
          dict.n = preferences.n_value || null;
          break;
        default:
          dict[field] = preferences[field];
      }
      return dict;
    },
    {}
  );
  state.nodes.push({
    ...data,
    files: [],
    assessment_items: [],
    prerequisite: [],
    ancestors: [],
    extra_fields: extraFieldData,
    tags: [],
    role_visibility: 'learner',
    changesStaged: true,
    isNew: true,
    parent: state.currentNode.id,
    sort_order: state.currentNode.metadata.max_sort_order + state.nodes.length + 1,
    _COMPLETE: true,
    ...payload,
  });
  _.defer(() => {
    // Wait for node to render
    state.selectedIndices = [state.nodes.length - 1];
    SET_CHANGES(state);
  });
}

export function REMOVE_NODE(state, index) {
  state.nodes = _.reject(state.nodes, (n, i) => i === index);
  state.selectedIndices = _.reject(state.selectedIndices, i => i === index);
  SET_CHANGES(state);
}

export function PREP_NODES_FOR_SAVE(state) {
  _.each(state.nodes, node => (node.isNew = false));
}

/*********** FILE OPERATIONS ***********/

export function ADD_NODES_FROM_FILES(state, newFiles) {
  newFiles.forEach(file => {
    ADD_NODE(state, {
      title: file.name,
      kind: file.kind,
      metadata: {
        resource_size: file.file_size,
      },
      files: [file],
    });
  });
}

export function ADD_FILE_TO_NODE(state, payload) {
  if (payload.file) {
    state.nodes[payload.index].files = _.reject(state.nodes[payload.index].files, f => {
      return (
        f.preset.id === payload.file.preset.id &&
        (!payload.file.preset.multi_language || payload.file.language.id === f.language.id)
      );
    });
    state.nodes[payload.index].files.push(payload.file);
    state.nodes[payload.index].changesStaged = true;
    SET_CHANGES(state);
  }
}

export function REMOVE_FILE_FROM_NODE(state, payload) {
  state.nodes[payload.index].files = _.reject(state.nodes[payload.index].files, f => {
    return f.id === payload.fileID;
  });
  state.nodes[payload.index].changesStaged = true;
  SET_CHANGES(state);
}
<|endoftext|>


<commit_before>/* @ngInject */
module.exports = function ($scope, $urlRouter, $stateParams, $modal, AuthService, AUTH_EVENTS, $http) {
  $scope.currentUser = null;

  $scope.updateCurrentUser = function () {
    $scope.currentUser = AuthService.getCurrentUser();
    $scope.updateCurrentUserHeaders();
  };

  $scope.updateCurrentUserHeaders = function () {
    var token, email;
    if ($scope.currentUser) {
      token = $scope.currentUser.access_token;
      email = $scope.currentUser.email;
    }
    var defaultHeaders = {
      "Accept": "application/json",
      "X-User-Token": token,
      "X-User-Email": email,
    };
    $http.defaults.headers.common = defaultHeaders;
  };

  $scope.$on(AUTH_EVENTS.loginSuccess, function () {
    $scope.updateCurrentUser();
    if ($scope.currentUser.initialized === false) {
      $modal.open(
        require('init-person-modal')({
          person: $scope.currentUser,
        })
      ).result.then(function () {
        $urlRouter.sync();
      }, function (err) {
        // handle error
        $modal.open(
          require('error-modal')({ error: err })
        )
      });
    } else {
      $urlRouter.sync();
    }
  });

  $scope.$on(AUTH_EVENTS.logoutSuccess, function () {
    $scope.updateCurrentUser();
  });

  $scope.updateCurrentUser();
};
<commit_msg>handle user initialization modal backdrop click<commit_after>/* @ngInject */
module.exports = function ($scope, $urlRouter, $stateParams, $modal, AuthService, AUTH_EVENTS, $http) {
  $scope.currentUser = null;

  $scope.updateCurrentUser = function () {
    $scope.currentUser = AuthService.getCurrentUser();
    $scope.updateCurrentUserHeaders();
  };

  $scope.updateCurrentUserHeaders = function () {
    var token, email;
    if ($scope.currentUser) {
      token = $scope.currentUser.access_token;
      email = $scope.currentUser.email;
    }
    var defaultHeaders = {
      "Accept": "application/json",
      "X-User-Token": token,
      "X-User-Email": email,
    };
    $http.defaults.headers.common = defaultHeaders;
  };

  $scope.initializeUser = function () {
    $modal.open(
      require('init-person-modal')({
        person: $scope.currentUser,
      })
    ).result.then(function () {
      $urlRouter.sync();
    }, function (err) {
      // handle error
      if (err === "backdrop click") {
        $scope.initializeUser()
      } else if (err) {
        $modal.open(
          require('error-modal')({ error: err })
        )
      }
    });
  }

  $scope.$on(AUTH_EVENTS.loginSuccess, function () {
    $scope.updateCurrentUser();
    if ($scope.currentUser.initialized === false) {
      $scope.initializeUser()
    } else {
      $urlRouter.sync();
    }
  });

  $scope.$on(AUTH_EVENTS.logoutSuccess, function () {
    $scope.updateCurrentUser();
  });

  $scope.updateCurrentUser();
};
<|endoftext|>


<commit_before>/**
    Key-value mapping UI element.

    This element generates a button that pops up a modal containing pairs of keys and values.
    Expects to be used in app manager, in the context of an application module.
    Used primarily in case list/detail display properties. Keys may be strings
    (as in ID Mapping format) or calcuations (as in Conditional ID Mapping format).
    Values may be strings (as in both ID Mapping formats) or references to images
    (as in Icon format). Supports translations.

    Options:
        Required:
            items: Array of current values. Each item should be the javascript equivalent
                of a MappingItem.
            lang: Current language.
            langs: All languages.
            property_name: Observable representing the name of the overall mapping.
                May be either a string or one of the elements defined in hqwebapp/js/ui-element.
                Used primarily to add title to modal. If values_are_icons, this should be a slug.
        Optional:
            keys_are_conditions: True iff KEYS are calculations rather than strings.
            values_are_icons: True iff values are image references ("jr://file...")
            module_id: Module id, required iff values_are_icons being used in a module context
                (e.g., case list/detail config).
            multimedia: Only needed if values_are_icons. Object containing multimedia
                to be passed as appMenuMediaManager's objectMap. Keys are image references;
                values are objects as generated by CommCareMultimedia.get_media_info
            buttonText: Text for button that opens modal. Defaults to "Edit".
 */
hqDefine('hqwebapp/js/ui_elements/ui-element-key-val-mapping', function () {
    'use strict';
    var module = {};

    // To autogenerate cssid from random string
    // copied from http://stackoverflow.com/questions/7627000/javascript-convert-string-to-safe-class-name-for-css
    var makeSafeForCSS = function (name) {
        if (!name) {
            return "";
        }
        return name.replace(/[^a-z0-9]/g, function (s) {
            var c = s.charCodeAt(0);
            if (c === 32) return '-';
            if (c >= 65 && c <= 90) return '_' + s.toLowerCase();
            return '__' + ('000' + c.toString(16)).slice(-4);
        });
    };

    /**
    * MapItem is a ko representation for `item` objects.
    *
    * @param item: a raw object which contains keys called `key` and `value`.
    *              the `value` in a item itself is an object, a mapping
    *              of language codes to strings
    * @param mappingContext: an object which has context of current UI language and whether
    *                 `value` of MapItem is a file-path to an icon or a simple string
    */
    var MapItem = function (item, index, mappingContext) {
        var self = {};
        self.key = ko.observable(item.key);
        self.editing = ko.observable(false);

        self.cssId = ko.computed(function () {
            return makeSafeForCSS(self.key()) || '_blank_';
        });

        // util function to generate icon-name of the format "module<module_id>_list_icon_<property_name>_<hash_of_item.key>"
        self.generateIconPath = function () {
            var randomFourDigits = Math.floor(Math.random() * 9000) + 1000;
            var iconPrefix =  "jr://file/commcare/image/module" + mappingContext.module_id + "_list_icon_" + mappingContext.getPropertyName() + "_";
            return iconPrefix + randomFourDigits + ".png";
        };

        var app_manager = hqImport('app_manager/js/app_manager_media');
        var uploaders = hqImport("app_manager/js/nav_menu_media_common");
        // attach a media-manager if item.value is a file-path to icon
        if (mappingContext.values_are_icons()) {
            var actualPath = item.value[mappingContext.lang];
            var defaultIconPath = actualPath || self.generateIconPath();
            self.iconManager = app_manager.appMenuMediaManager({
                ref: {
                    "path": actualPath,
                    "icon_type": "icon-picture",
                    "media_type": "Image",
                    "media_class": "CommCareImage",
                    "icon_class": "icon-picture",
                },
                objectMap: mappingContext.multimedia,
                uploadController: uploaders.iconUploader,
                defaultPath: defaultIconPath,
                inputElement: $("#" + self.cssId()),
            });
        }

        self.toggleEditMode = function () {
            self.editing(!self.editing());
        };

        self.value = ko.computed(function () {
            // ko.observable for item.value
            var new_value = [];
            var langs = _.union(_(item.value).keys(), [mappingContext.lang]) ;
            _.each(langs, function (lang) {
                // return ko reference to path in `iconManager` for current UI language value
                if (mappingContext.values_are_icons() && lang === mappingContext.lang) {
                    new_value.push([lang, self.iconManager.customPath]);
                }
                // return new ko.observable for other languages
                else {
                    new_value.push([lang, ko.observable(item.value[lang])]);
                }
            });
            return _.object(new_value);
        });

        self.key.subscribe(function (newValue) {
            if (mappingContext.duplicatedItems.indexOf(newValue) === -1 && mappingContext._isItemDuplicated(newValue)) {
                mappingContext.duplicatedItems.push(newValue);
            }

        });

        self.key.subscribe(function (oldValue) {
            var index = mappingContext.duplicatedItems.indexOf(oldValue);
            if (index !== -1 && !mappingContext._isItemDuplicated(oldValue, 2)) {
                mappingContext.duplicatedItems.remove(oldValue);
            }
        }, null, "beforeChange");

        return self;
    };

    /**
     * A MapList is an ordered list of MapItem objects
     */
    var MapList = function (options) {
        hqImport("hqwebapp/js/assert_properties").assert(options, [
            'lang',
            'langs',
            'items',
            'property_name',
        ], [
            'buttonText',
            'keys_are_conditions',
            'module_id',
            'multimedia',
            'values_are_icons',
        ]);
        if (options.values_are_icons && !options.module_id) {
            throw new Error("Module ID must be provided if values are icons.");
        }

        var self = {};
        self.lang = options.lang;
        self.langs = [options.lang].concat(options.langs);
        self.module_id = options.module_id;
        self.items = ko.observableArray();
        self.duplicatedItems = ko.observableArray();
        self.values_are_icons = ko.observable(options.values_are_icons || false);
        self.keys_are_conditions = ko.observable(options.keys_are_conditions || false);
        self.multimedia = options.multimedia;

        self.getPropertyName = function () {
            if (_.isObject(options.property_name) && _.isFunction(options.property_name.val)) {
                return options.property_name.val();
            }
            return options.property_name;
        };

        self.labels = ko.computed(function () {
            if (self.values_are_icons()) {
                return {
                    placeholder: django.gettext('Calculation'),
                    duplicated: django.gettext('Calculation is duplicated'),
                    addButton: django.gettext('Add Image'),
                    badXML: django.gettext('Calculation contains an invalid character.'),
                };
            }
            else if (self.keys_are_conditions()) {
                return {
                    placeholder: django.gettext('Calculation'),
                    duplicated: django.gettext('Calculation is duplicated'),
                    addButton: django.gettext('Add Key, Value Mapping'),
                    badXML: django.gettext('Calculation contains an invalid character.'),
                };
            }
            else {
                return {
                    placeholder: django.gettext('Key'),
                    duplicated: django.gettext('Key is duplicated'),
                    addButton: django.gettext('Add Key, Value Mapping'),
                    badXML: django.gettext('Key contains an invalid character.'),
                };
            }
        });

        self.setItems = function (items) {
            self.items(_(items).map(function (item, i) {
                return new MapItem(item, i, self);
            }));
        };
        self.setItems(options.items);

        self.backup = function (value) {
            var backup;
            for (var i = 0; i < self.langs.length; i += 1) {
                var lang = self.langs[i];
                backup = value[lang];
                if (backup && backup() !== '') {
                    return {lang: lang, value: backup()};
                }
            }
            return {lang: null, value: 'value'};
        };
        self.removeItem = function (item) {
            self.items.remove(item);
            if (!self._isItemDuplicated(ko.utils.unwrapObservable(item.key)))
                self.duplicatedItems.remove(ko.utils.unwrapObservable(item.key));
        };
        self.addItem = function () {
            var raw_item = {key: '', value: {}};
            raw_item.value[self.lang] = '';

            var item = new MapItem(raw_item, self.items.length, self);
            self.items.push(item);
            if (self.duplicatedItems.indexOf('') === -1 && self._isItemDuplicated('')) {
                self.duplicatedItems.push('');
            }
        };

        self._isItemDuplicated = function (key, max_counts) {
            if (typeof(max_counts) === 'undefined') max_counts = 1;
            var items = self.getItems();
            var counter = 0;
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (ko.utils.unwrapObservable(item.key) === key) {
                    counter++;
                    if (counter > max_counts) return true;
                }
            }
            return false;
        };

        self.isItemDuplicated = function (key) {
            return self.duplicatedItems.indexOf(key) !== -1;
        };

        self.hasBadXML = function (key) {
            if (self.values_are_icons() || self.keys_are_conditions()) {
                // Expressions can contain whatever
                return false;
            }

            // IDs shouldn't have invalid XML characters
            return key.match(/[&<>"']/);
        };

        self.keyHasError = function (key) {
            return self.isItemDuplicated(key) || self.hasBadXML(key);
        };

        self.hasError = function () {
            return self.duplicatedItems().length > 0
                || _.find(self.items(), function (i) { return self.hasBadXML(i.key()); });
        };

        self.getItems = function () {
            return _(self.items()).map(function (item) {
                return {
                    key: ko.utils.unwrapObservable(item.key),
                    value: _.object(_(item.value()).map(function (value, lang) {
                        return [lang, ko.utils.unwrapObservable(value)];
                    })),
                };
            });
        };

        return self;
    };

    module.new = function (options) {
        var m = MapList(options);
        m.edit = ko.observable(true);
        m.buttonText = options.buttonText || gettext("Edit"),
        m.values_are_icons = ko.observable(options.values_are_icons || false);
        m.keys_are_conditions = ko.observable(options.keys_are_conditions || false);
        m.openModal = function () {
            // create a throw-away modal every time
            // lets us create a sandbox for editing that you can cancel
            var $modalDiv = $(document.createElement("div"));
            $modalDiv.attr("data-bind", "template: 'key_value_mapping_modal'");
            var copy = MapList({
                lang: options.lang,
                langs: options.langs,
                module_id: options.module_id,
                items: m.getItems(),
                values_are_icons: m.values_are_icons(),
                keys_are_conditions: m.keys_are_conditions(),
                multimedia: m.multimedia,
                property_name: options.property_name,
            });
            $modalDiv.koApplyBindings({
                modalTitle: ko.computed(function () {
                    return _.template(gettext('Edit mapping for <%= property %>'))({
                        property: m.getPropertyName(),
                    });
                }),
                mapList: copy,
                save: function (data, e) {
                    if (copy.duplicatedItems().length > 0) {
                        e.stopImmediatePropagation();
                    } else {
                        m.setItems(copy.getItems());
                    }
                },
            });

            var $modal = $modalDiv.find('.modal');
            $modal.appendTo('body');
            $modal.modal({
                show: true,
                backdrop: 'static',
            });
            $modal.on('hidden', function () {
                $modal.remove();
            });
        };
        m.setEdit = function (edit) {
            m.edit(edit);
        };
        var $div = $(document.createElement("div"));
        $div.attr("data-bind", "template: \'key_value_mapping_template\'");
        $div.koApplyBindings(m);
        m.ui = $div;
        hqImport("hqwebapp/js/main").eventize(m);
        m.items.subscribe(function () {
            m.fire('change');
        });
        return m;
    };

    return module;

});
<commit_msg>Removed assertion that module_id is present if values_are_icons<commit_after>/**
    Key-value mapping UI element.

    This element generates a button that pops up a modal containing pairs of keys and values.
    Expects to be used in app manager, in the context of an application module.
    Used primarily in case list/detail display properties. Keys may be strings
    (as in ID Mapping format) or calcuations (as in Conditional ID Mapping format).
    Values may be strings (as in both ID Mapping formats) or references to images
    (as in Icon format). Supports translations.

    Options:
        Required:
            items: Array of current values. Each item should be the javascript equivalent
                of a MappingItem.
            lang: Current language.
            langs: All languages.
            property_name: Observable representing the name of the overall mapping.
                May be either a string or one of the elements defined in hqwebapp/js/ui-element.
                Used primarily to add title to modal. If values_are_icons, this should be a slug.
        Optional:
            keys_are_conditions: True iff KEYS are calculations rather than strings.
            values_are_icons: True iff values are image references ("jr://file...")
            module_id: Module id, should be provided if values_are_icons is being used in a module context
                (e.g., case list/detail config).
            multimedia: Only needed if values_are_icons. Object containing multimedia
                to be passed as appMenuMediaManager's objectMap. Keys are image references;
                values are objects as generated by CommCareMultimedia.get_media_info
            buttonText: Text for button that opens modal. Defaults to "Edit".
 */
hqDefine('hqwebapp/js/ui_elements/ui-element-key-val-mapping', function () {
    'use strict';
    var module = {};

    // To autogenerate cssid from random string
    // copied from http://stackoverflow.com/questions/7627000/javascript-convert-string-to-safe-class-name-for-css
    var makeSafeForCSS = function (name) {
        if (!name) {
            return "";
        }
        return name.replace(/[^a-z0-9]/g, function (s) {
            var c = s.charCodeAt(0);
            if (c === 32) return '-';
            if (c >= 65 && c <= 90) return '_' + s.toLowerCase();
            return '__' + ('000' + c.toString(16)).slice(-4);
        });
    };

    /**
    * MapItem is a ko representation for `item` objects.
    *
    * @param item: a raw object which contains keys called `key` and `value`.
    *              the `value` in a item itself is an object, a mapping
    *              of language codes to strings
    * @param mappingContext: an object which has context of current UI language and whether
    *                 `value` of MapItem is a file-path to an icon or a simple string
    */
    var MapItem = function (item, index, mappingContext) {
        var self = {};
        self.key = ko.observable(item.key);
        self.editing = ko.observable(false);

        self.cssId = ko.computed(function () {
            return makeSafeForCSS(self.key()) || '_blank_';
        });

        // util function to generate icon-name of the format "module<module_id>_list_icon_<property_name>_<hash_of_item.key>"
        self.generateIconPath = function () {
            var randomFourDigits = Math.floor(Math.random() * 9000) + 1000;
            var iconPrefix =  "jr://file/commcare/image/module" + mappingContext.module_id + "_list_icon_" + mappingContext.getPropertyName() + "_";
            return iconPrefix + randomFourDigits + ".png";
        };

        var app_manager = hqImport('app_manager/js/app_manager_media');
        var uploaders = hqImport("app_manager/js/nav_menu_media_common");
        // attach a media-manager if item.value is a file-path to icon
        if (mappingContext.values_are_icons()) {
            var actualPath = item.value[mappingContext.lang];
            var defaultIconPath = actualPath || self.generateIconPath();
            self.iconManager = app_manager.appMenuMediaManager({
                ref: {
                    "path": actualPath,
                    "icon_type": "icon-picture",
                    "media_type": "Image",
                    "media_class": "CommCareImage",
                    "icon_class": "icon-picture",
                },
                objectMap: mappingContext.multimedia,
                uploadController: uploaders.iconUploader,
                defaultPath: defaultIconPath,
                inputElement: $("#" + self.cssId()),
            });
        }

        self.toggleEditMode = function () {
            self.editing(!self.editing());
        };

        self.value = ko.computed(function () {
            // ko.observable for item.value
            var new_value = [];
            var langs = _.union(_(item.value).keys(), [mappingContext.lang]) ;
            _.each(langs, function (lang) {
                // return ko reference to path in `iconManager` for current UI language value
                if (mappingContext.values_are_icons() && lang === mappingContext.lang) {
                    new_value.push([lang, self.iconManager.customPath]);
                }
                // return new ko.observable for other languages
                else {
                    new_value.push([lang, ko.observable(item.value[lang])]);
                }
            });
            return _.object(new_value);
        });

        self.key.subscribe(function (newValue) {
            if (mappingContext.duplicatedItems.indexOf(newValue) === -1 && mappingContext._isItemDuplicated(newValue)) {
                mappingContext.duplicatedItems.push(newValue);
            }

        });

        self.key.subscribe(function (oldValue) {
            var index = mappingContext.duplicatedItems.indexOf(oldValue);
            if (index !== -1 && !mappingContext._isItemDuplicated(oldValue, 2)) {
                mappingContext.duplicatedItems.remove(oldValue);
            }
        }, null, "beforeChange");

        return self;
    };

    /**
     * A MapList is an ordered list of MapItem objects
     */
    var MapList = function (options) {
        hqImport("hqwebapp/js/assert_properties").assert(options, [
            'lang',
            'langs',
            'items',
            'property_name',
        ], [
            'buttonText',
            'keys_are_conditions',
            'module_id',
            'multimedia',
            'values_are_icons',
        ]);

        var self = {};
        self.lang = options.lang;
        self.langs = [options.lang].concat(options.langs);
        self.module_id = options.module_id;
        self.items = ko.observableArray();
        self.duplicatedItems = ko.observableArray();
        self.values_are_icons = ko.observable(options.values_are_icons || false);
        self.keys_are_conditions = ko.observable(options.keys_are_conditions || false);
        self.multimedia = options.multimedia;

        self.getPropertyName = function () {
            if (_.isObject(options.property_name) && _.isFunction(options.property_name.val)) {
                return options.property_name.val();
            }
            return options.property_name;
        };

        self.labels = ko.computed(function () {
            if (self.values_are_icons()) {
                return {
                    placeholder: django.gettext('Calculation'),
                    duplicated: django.gettext('Calculation is duplicated'),
                    addButton: django.gettext('Add Image'),
                    badXML: django.gettext('Calculation contains an invalid character.'),
                };
            }
            else if (self.keys_are_conditions()) {
                return {
                    placeholder: django.gettext('Calculation'),
                    duplicated: django.gettext('Calculation is duplicated'),
                    addButton: django.gettext('Add Key, Value Mapping'),
                    badXML: django.gettext('Calculation contains an invalid character.'),
                };
            }
            else {
                return {
                    placeholder: django.gettext('Key'),
                    duplicated: django.gettext('Key is duplicated'),
                    addButton: django.gettext('Add Key, Value Mapping'),
                    badXML: django.gettext('Key contains an invalid character.'),
                };
            }
        });

        self.setItems = function (items) {
            self.items(_(items).map(function (item, i) {
                return new MapItem(item, i, self);
            }));
        };
        self.setItems(options.items);

        self.backup = function (value) {
            var backup;
            for (var i = 0; i < self.langs.length; i += 1) {
                var lang = self.langs[i];
                backup = value[lang];
                if (backup && backup() !== '') {
                    return {lang: lang, value: backup()};
                }
            }
            return {lang: null, value: 'value'};
        };
        self.removeItem = function (item) {
            self.items.remove(item);
            if (!self._isItemDuplicated(ko.utils.unwrapObservable(item.key)))
                self.duplicatedItems.remove(ko.utils.unwrapObservable(item.key));
        };
        self.addItem = function () {
            var raw_item = {key: '', value: {}};
            raw_item.value[self.lang] = '';

            var item = new MapItem(raw_item, self.items.length, self);
            self.items.push(item);
            if (self.duplicatedItems.indexOf('') === -1 && self._isItemDuplicated('')) {
                self.duplicatedItems.push('');
            }
        };

        self._isItemDuplicated = function (key, max_counts) {
            if (typeof(max_counts) === 'undefined') max_counts = 1;
            var items = self.getItems();
            var counter = 0;
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                if (ko.utils.unwrapObservable(item.key) === key) {
                    counter++;
                    if (counter > max_counts) return true;
                }
            }
            return false;
        };

        self.isItemDuplicated = function (key) {
            return self.duplicatedItems.indexOf(key) !== -1;
        };

        self.hasBadXML = function (key) {
            if (self.values_are_icons() || self.keys_are_conditions()) {
                // Expressions can contain whatever
                return false;
            }

            // IDs shouldn't have invalid XML characters
            return key.match(/[&<>"']/);
        };

        self.keyHasError = function (key) {
            return self.isItemDuplicated(key) || self.hasBadXML(key);
        };

        self.hasError = function () {
            return self.duplicatedItems().length > 0
                || _.find(self.items(), function (i) { return self.hasBadXML(i.key()); });
        };

        self.getItems = function () {
            return _(self.items()).map(function (item) {
                return {
                    key: ko.utils.unwrapObservable(item.key),
                    value: _.object(_(item.value()).map(function (value, lang) {
                        return [lang, ko.utils.unwrapObservable(value)];
                    })),
                };
            });
        };

        return self;
    };

    module.new = function (options) {
        var m = MapList(options);
        m.edit = ko.observable(true);
        m.buttonText = options.buttonText || gettext("Edit"),
        m.values_are_icons = ko.observable(options.values_are_icons || false);
        m.keys_are_conditions = ko.observable(options.keys_are_conditions || false);
        m.openModal = function () {
            // create a throw-away modal every time
            // lets us create a sandbox for editing that you can cancel
            var $modalDiv = $(document.createElement("div"));
            $modalDiv.attr("data-bind", "template: 'key_value_mapping_modal'");
            var copy = MapList({
                lang: options.lang,
                langs: options.langs,
                module_id: options.module_id,
                items: m.getItems(),
                values_are_icons: m.values_are_icons(),
                keys_are_conditions: m.keys_are_conditions(),
                multimedia: m.multimedia,
                property_name: options.property_name,
            });
            $modalDiv.koApplyBindings({
                modalTitle: ko.computed(function () {
                    return _.template(gettext('Edit mapping for <%= property %>'))({
                        property: m.getPropertyName(),
                    });
                }),
                mapList: copy,
                save: function (data, e) {
                    if (copy.duplicatedItems().length > 0) {
                        e.stopImmediatePropagation();
                    } else {
                        m.setItems(copy.getItems());
                    }
                },
            });

            var $modal = $modalDiv.find('.modal');
            $modal.appendTo('body');
            $modal.modal({
                show: true,
                backdrop: 'static',
            });
            $modal.on('hidden', function () {
                $modal.remove();
            });
        };
        m.setEdit = function (edit) {
            m.edit(edit);
        };
        var $div = $(document.createElement("div"));
        $div.attr("data-bind", "template: \'key_value_mapping_template\'");
        $div.koApplyBindings(m);
        m.ui = $div;
        hqImport("hqwebapp/js/main").eventize(m);
        m.items.subscribe(function () {
            m.fire('change');
        });
        return m;
    };

    return module;

});
<|endoftext|>