<commit_before>
import winston from "winston";
import zmq from "zmq";
import _ from "underscore";

import InputVertex from "./InputVertex";
import SK from "../../sk";
import m from "../MagicFilters";

const MAIN_SWITCHER_LABEL = "mainSwitcher";
const SPLIT_SCREEN_TOP_SWITCHER_LABEL = "splitScreenTopSwitcher";
const SPLIT_SCREEN_BOTTOM_SWITCHER_LABEL = "splitScreenBottomSwitcher";

export default class MagicVertex extends InputVertex {
  constructor({id}) {
    super({id});
    this.rewriteStream = false;
    this.videoOutputURL = this.getUDPOutput();
    this.audioOutputURL = this.getUDPOutput();
  }

  handleInitialPull() {
    this.doc.inputs.forEach((input) => {
      input.sockets.forEach((socket) => {
        socket.url = this.getUDPInput();
      });
    });
    const newVertex = {
      inputs: this.doc.inputs,
      outputs: [{
        name: "default",
        sockets: [{
          url: this.videoOutputURL,
          type: "video",
        }, {
          url: this.audioOutputURL,
          type: "audio",
        }]
      }]

    };
    SK.vertices.update(this.doc.id, newVertex)
    .then(() => {
      this.init();
    })
    .catch((err) => {
      winston.error(err);
    });
  }

  init() {
    super.init();
    try {
      this.ffmpeg = this.createffmpeg();
      this.zmqAddress = `tcp://0.0.0.0:${this.getTCP()}`;

      const videoInputSockets = [];
      const audioInputSockets = [];
      let currentIdx = 0;

      this.doc.inputs.forEach((input, inputIdx) => {
        input.sockets.forEach((socket, socketIdx) => {
          socket.name = `${input.name}-${socketIdx}`;
          this.ffmpeg
            .input(socket.url)
            .inputFormat("mpegts")
            .inputOptions([
              "-thread_queue_size 512",
              // "-avioflags direct",
            ]);

          // Set up video input
          if (socket.type === "video") {
            videoInputSockets.push(socket);
            this.ffmpeg.magic(
              `${currentIdx}:v`,
              m.framerate("30"),
              // m.realtime({limit: 2000000}),
              // m.setpts(`(RTCTIME - ${this.SERVER_START_TIME}) / (TB * 1000000)`),
              // m.setpts(`PTS-STARTPTS`),
              m.scale(1920, 1080),
              m.split(3),
              `${socket.name}-default`,
              `${socket.name}-splitTop`,
              `${socket.name}-splitBottom`
            );
          }

          // Set up audio input
          else if (socket.type === "audio") {
            audioInputSockets.push(socket);
            this.ffmpeg.magic(
              `${currentIdx}:a`,
              m.volume({
                _label: `${socket.name}-volume`
              }),
              `${socket.name}-adjusted`
            );
          }

          else {
            throw new Error(`Unknown input type: ${input.type}`);
          }
          currentIdx += 1;
        });
      });

      // Define splitscreen switcher
      this.ffmpeg
        .magic(
          ...videoInputSockets.map(s => `${s.name}-splitTop`),
          m.streamselect({
            inputs: videoInputSockets.length,
            map: 0,
            _label: SPLIT_SCREEN_TOP_SWITCHER_LABEL
          }),
          m.crop({
            w: "iw",
            h: "540",
            x: "0",
            y: "270",
          }),
          "splitScreenTop"
        )

        .magic(
          ...videoInputSockets.map(s => `${s.name}-splitBottom`),
          m.streamselect({
            inputs: videoInputSockets.length,
            map: 1,
            _label: SPLIT_SCREEN_BOTTOM_SWITCHER_LABEL
          }),
          m.crop({
            w: "iw",
            h: "540",
            x: "0",
            y: "270",
          }),
          "splitScreenBottom"
        )

        .magic(
          "splitScreenTop",
          "splitScreenBottom",
          m.vstack(),
          "splitScreenOut"
        );

      this.ffmpeg
        .outputOptions([
          "-copyts",
          "-copytb 1",
          "-async 1",
          "-vsync passthrough",
          "-probesize 2147483647",
          "-pix_fmt yuv420p",
          // "-profile:v baseline",
          // "-use_wallclock_as_timestamps 1",
          "-fflags +igndts",
          // "-loglevel verbose",
        ])
        .magic(
          ...videoInputSockets.map(s => `${s.name}-default`),
          "splitScreenOut",
          m.streamselect({inputs: videoInputSockets.length + 1, map: 2, _label: MAIN_SWITCHER_LABEL}),
          m.zmq({bind_address: this.zmqAddress}),
          m.framerate("30"),
          "videoOutput"
        )
        .outputOptions([
          "-map [videoOutput]",
          "-preset veryfast",
          // "-b:v 4000k",
          // "-minrate 4000k",
          // "-maxrate 4000k",
          // "-bufsize 1835k",
          // "-frame_drop_threshold 60",
        ])
        .once("progress", () => {
          const socket = zmq.socket("req");
          socket.on("connect", (fd, ep) => {
            let idx = 0;
            let label = this.ffmpeg.filterLabels[MAIN_SWITCHER_LABEL];
          //   setInterval(function() {
          //     idx += 1;
          //     if (idx >= videoInputNames.length + 1) {
          //       idx = 0;
          //     }
          //     socket.send(`${label} map ${idx}`);
          //   }, 3000);
          //   this.info("connect, endpoint:", ep);
          });
          socket.on("connect_delay", (fd, ep) => {this.info("connect_delay, endpoint:", ep);});
          socket.on("connect_retry", (fd, ep) => {this.info("connect_retry, endpoint:", ep);});
          socket.on("listen", (fd, ep) => {this.info("listen, endpoint:", ep);});
          socket.on("bind_error", (fd, ep) => {this.info("bind_error, endpoint:", ep);});
          socket.on("accept", (fd, ep) => {this.info("accept, endpoint:", ep);});
          socket.on("accept_error", (fd, ep) => {this.info("accept_error, endpoint:", ep);});
          socket.on("close", (fd, ep) => {this.info("close, endpoint:", ep);});
          socket.on("close_error", (fd, ep) => {this.info("close_error, endpoint:", ep);});
          socket.on("disconnect", (fd, ep) => {this.info("disconnect, endpoint:", ep);});
          socket.monitor(500, 0);
          socket.connect(this.zmqAddress);
        })
        .output(this.videoOutputURL)
        .outputFormat("mpegts")
        .videoCodec("libx264")

        .magic(
          ...audioInputSockets.map(s => `${s.name}-adjusted`),
          m.amix({
            inputs: audioInputSockets.length
          }),
          "audioOutput"
        )

        .output(this.audioOutputURL)
        .outputOptions([
          "-map [audioOutput]"
        ])
        .outputFormat("mpegts")
        .audioCodec("aac");

        // .input(this.inputURL)
        // .inputFormat("mpegts")
        // // .inputOptions("-itsoffset 00:00:05")
        // .outputOptions([
        // ])
        // .videoCodec("libx264")
        // .audioCodec("pcm_s16le")
        // .outputOptions([
        //   "-preset ultrafast",
        //   "-tune zerolatency",
        //   "-x264opts keyint=5:min-keyint=",
        //   "-pix_fmt yuv420p",
        //   "-filter_complex",
        //   [
        //     `[0:a]asetpts='(RTCTIME - ${this.SERVER_START_TIME}) / (TB * 1000000)'[out_audio]`,
        //     `[0:v]setpts='(RTCTIME - ${this.SERVER_START_TIME}) / (TB * 1000000)'[out_video]`,
        //   ].join(";")
        // ])

        // // Video output
        // .output(this.videoOutputURL)
        // .outputOptions([
        //   "-map [out_video]",
        // ])
        // .outputFormat("mpegts")

        // // Audio output
        // .output(this.audioOutputURL)
        // .outputOptions([
        //   "-map [out_audio]",
        // ])
        // .outputFormat("mpegts");

      this.ffmpeg.run();
    }
    catch (err) {
      this.error(err);
      this.retry();
    }
  }
}
<commit_msg>Implement overlay<commit_after>
import winston from "winston";
import zmq from "zmq";
import _ from "underscore";

import InputVertex from "./InputVertex";
import SK from "../../sk";
import m from "../MagicFilters";

const MAIN_SWITCHER_LABEL = "mainSwitcher";
const SPLIT_SCREEN_TOP_SWITCHER_LABEL = "splitScreenTopSwitcher";
const SPLIT_SCREEN_BOTTOM_SWITCHER_LABEL = "splitScreenBottomSwitcher";
const SPLIT_SCREEN_BOTTOM_CROP_LABEL = "splitScreenBottomCrop";
const SPLIT_SCREEN_BOTTOM_OVERLAY_LABEL = "splitScreenBottomOverlay";

export default class MagicVertex extends InputVertex {
  constructor({id}) {
    super({id});
    this.rewriteStream = false;
    this.debug = true;
    this.videoOutputURL = this.getUDPOutput();
    this.audioOutputURL = this.getUDPOutput();
  }

  handleInitialPull() {
    this.doc.inputs.forEach((input) => {
      input.sockets.forEach((socket) => {
        socket.url = this.getUDPInput();
      });
    });
    const newVertex = {
      inputs: this.doc.inputs,
      outputs: [{
        name: "default",
        sockets: [{
          url: this.videoOutputURL,
          type: "video",
        }, {
          url: this.audioOutputURL,
          type: "audio",
        }]
      }]

    };
    SK.vertices.update(this.doc.id, newVertex)
    .then(() => {
      this.init();
    })
    .catch((err) => {
      winston.error(err);
    });
  }

  init() {
    super.init();
    try {
      this.ffmpeg = this.createffmpeg();
      this.zmqAddress = `tcp://0.0.0.0:${this.getTCP()}`;

      const videoInputSockets = [];
      const audioInputSockets = [];
      let currentIdx = 0;

      this.doc.inputs.forEach((input, inputIdx) => {
        input.sockets.forEach((socket, socketIdx) => {
          socket.name = `${input.name}-${socketIdx}`;
          this.ffmpeg
            .input(socket.url)
            .inputFormat("mpegts")
            .inputOptions([
              "-thread_queue_size 512",
              // "-avioflags direct",
            ]);

          // Set up video input
          if (socket.type === "video") {
            videoInputSockets.push(socket);
            this.ffmpeg.magic(
              `${currentIdx}:v`,
              m.framerate("30"),
              // m.realtime({limit: 2000000}),
              // m.setpts(`(RTCTIME - ${this.SERVER_START_TIME}) / (TB * 1000000)`),
              // m.setpts(`PTS-STARTPTS`),
              m.scale(1920, 1080),
              m.split(3),
              `${socket.name}-default`,
              `${socket.name}-splitTop`,
              `${socket.name}-splitBottom`
            );
          }

          // Set up audio input
          else if (socket.type === "audio") {
            audioInputSockets.push(socket);
            this.ffmpeg.magic(
              `${currentIdx}:a`,
              m.volume({
                _label: `${socket.name}-volume`
              }),
              `${socket.name}-adjusted`
            );
          }

          else {
            throw new Error(`Unknown input type: ${input.type}`);
          }
          currentIdx += 1;
        });
      });

      // Define splitscreen switcher
      this.ffmpeg
        .magic(
          ...videoInputSockets.map(s => `${s.name}-splitTop`),
          m.streamselect({
            inputs: videoInputSockets.length,
            map: 0,
            _label: SPLIT_SCREEN_TOP_SWITCHER_LABEL
          }),
          // m.crop({
          //   w: "iw",
          //   h: "540",
          //   x: "0",
          //   y: "270",
          // }),
          "splitScreenTop"
        )

        .magic(
          ...videoInputSockets.map(s => `${s.name}-splitBottom`),
          m.streamselect({
            inputs: videoInputSockets.length,
            map: 1,
            _label: SPLIT_SCREEN_BOTTOM_SWITCHER_LABEL
          }),
          // m.crop({
          //   w: "iw",
          //   h: "540",
          //   x: "0",
          //   y: "270",
          //   _label: SPLIT_SCREEN_BOTTOM_CROP_LABEL,
          // }),
          m.scale({
            w: 924,
            h: 693
          }),
          m.crop({
            w: 924,
            h: 674
          }),
          "splitScreenBottom"
        )

        .magic(
          "splitScreenTop",
          "splitScreenBottom",
          m.overlay({
            _label: SPLIT_SCREEN_BOTTOM_OVERLAY_LABEL,
            x: 974,
            y: 385
          }),
          "splitScreenOut"
        );

      this.ffmpeg
        .outputOptions([
          "-copyts",
          "-copytb 1",
          "-async 1",
          "-vsync passthrough",
          // "-sws_flags +neighbor",
          "-probesize 2147483647",
          "-pix_fmt yuv420p",
          // "-profile:v baseline",
          // "-use_wallclock_as_timestamps 1",
          "-fflags +igndts",
          // "-loglevel verbose",
        ])
        .magic(
          ...videoInputSockets.map(s => `${s.name}-default`),
          "splitScreenOut",
          m.streamselect({inputs: videoInputSockets.length + 1, map: 2, _label: MAIN_SWITCHER_LABEL}),
          m.zmq({bind_address: this.zmqAddress}),
          m.framerate("30"),
          "videoOutput"
        )
        .outputOptions([
          "-map [videoOutput]",
          "-preset veryfast",
          // "-x264opts keyint=20:min-keyint=",
          // "-b:v 4000k",
          // "-minrate 4000k",
          // "-maxrate 4000k",
          // "-bufsize 1835k",
          // "-frame_drop_threshold 60",
        ])
        .once("progress", () => {
          const socket = zmq.socket("req");
          socket.on("connect", (fd, ep) => {
            let idx = 0;
            let label = this.ffmpeg.filterLabels[MAIN_SWITCHER_LABEL];
          //   setInterval(function() {
          //     idx += 1;
          //     if (idx >= videoInputNames.length + 1) {
          //       idx = 0;
          //     }
          //     socket.send(`${label} map ${idx}`);
          //   }, 3000);
          //   this.info("connect, endpoint:", ep);
          });
          socket.on("connect_delay", (fd, ep) => {this.info("connect_delay, endpoint:", ep);});
          socket.on("connect_retry", (fd, ep) => {this.info("connect_retry, endpoint:", ep);});
          socket.on("listen", (fd, ep) => {this.info("listen, endpoint:", ep);});
          socket.on("bind_error", (fd, ep) => {this.info("bind_error, endpoint:", ep);});
          socket.on("accept", (fd, ep) => {this.info("accept, endpoint:", ep);});
          socket.on("accept_error", (fd, ep) => {this.info("accept_error, endpoint:", ep);});
          socket.on("close", (fd, ep) => {this.info("close, endpoint:", ep);});
          socket.on("close_error", (fd, ep) => {this.info("close_error, endpoint:", ep);});
          socket.on("disconnect", (fd, ep) => {this.info("disconnect, endpoint:", ep);});
          socket.monitor(500, 0);
          socket.connect(this.zmqAddress);
        })
        .output(this.videoOutputURL)
        .outputFormat("mpegts")
        .videoCodec("libx264")

        .magic(
          ...audioInputSockets.map(s => `${s.name}-adjusted`),
          m.amix({
            inputs: audioInputSockets.length
          }),
          "audioOutput"
        )

        .output(this.audioOutputURL)
        .outputOptions([
          "-map [audioOutput]"
        ])
        .outputFormat("mpegts")
        .audioCodec("aac");

        // .input(this.inputURL)
        // .inputFormat("mpegts")
        // // .inputOptions("-itsoffset 00:00:05")
        // .outputOptions([
        // ])
        // .videoCodec("libx264")
        // .audioCodec("pcm_s16le")
        // .outputOptions([
        //   "-preset ultrafast",
        //   "-tune zerolatency",
        //   "-x264opts keyint=5:min-keyint=",
        //   "-pix_fmt yuv420p",
        //   "-filter_complex",
        //   [
        //     `[0:a]asetpts='(RTCTIME - ${this.SERVER_START_TIME}) / (TB * 1000000)'[out_audio]`,
        //     `[0:v]setpts='(RTCTIME - ${this.SERVER_START_TIME}) / (TB * 1000000)'[out_video]`,
        //   ].join(";")
        // ])

        // // Video output
        // .output(this.videoOutputURL)
        // .outputOptions([
        //   "-map [out_video]",
        // ])
        // .outputFormat("mpegts")

        // // Audio output
        // .output(this.audioOutputURL)
        // .outputOptions([
        //   "-map [out_audio]",
        // ])
        // .outputFormat("mpegts");

      this.ffmpeg.run();
    }
    catch (err) {
      this.error(err);
      this.retry();
    }
  }
}
<|endoftext|>


<commit_before>"use strict"

if (typeof (require) != 'undefined') {
	var loader = require('../../../../node_modules/Butterfly-js/dist/butterfly.min.js').loader;
}

/**
 * This module contains the definition of the Warrior entitie
 */
loader.addModule('GameEntities',
'Canvas',
function (canvas) {
	var entities = {},
		Warrior;

	(function () {
		var _radius = 10;

		/* Warrior Class */
		Warrior = function (name, x, y) {
			this.name = name;
			this.keys = 0;
			this.x  = this.originalX = x;
			this.y = this.originalY = y;
			this.r = _radius;
			this.angle = this.originalAngle = 0;
			this.speed = this.originalSpeed = 0;
			this.graphicLoaded = false;
		};

		function _walk (warrior, enable) {
			warrior.speed = enable ? 5 : 0;
		}

		Warrior.prototype.walkLeft = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI;
		};

		Warrior.prototype.walkRight = function (enable) {
			_walk(this, enable);
			this.angle = 0;
		};

		Warrior.prototype.walkUp = function (enable) {
			_walk(this, enable);
			this.angle = 3 * Math.PI / 2;
		};

		Warrior.prototype.walkDown = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI / 2;
		};

		/**
		 * Method to update the warrior position according to its speed
		 */
		Warrior.prototype.updatePosition = function () {
			this.x += Math.cos(this.angle) * this.speed;
			this.y += Math.sin(this.angle) * this.speed;
		};

		Warrior.prototype.bumpBack = function () {
			this.x -= Math.cos(this.angle) * this.speed;
			this.y -= Math.sin(this.angle) * this.speed;

			this.speed = 0;
		};

		Warrior.prototype.setGraphic = function (graphic) {
			this.graphic = graphic;
		};

		/**
		 * Reset the warrior to its original values
		 */
		Warrior.prototype.reset = function () {
			this.x  = this.originalX;
			this.y = this.originalY;
			this.angle = this.originalAngle;
			this.speed = this.originalSpeed;
		};

		/**
		 * Draw the warrior on the screen
		 */
		Warrior.prototype.draw = function () {
			canvas.drawImage(
				this.graphic,
				this.x - this.graphic.width / 2,
				this.y - this.graphic.height / 2
			);
		};

		Warrior.prototype.addKey = function () {
			this.keys++;
		};

		Warrior.prototype.useKey = function () {
			this.keys--;
		};

		Warrior.prototype.getNumberOfKeys = function () {
			return this.keys;
		};
		/* End Warrior Class */
	})();

	entities.Warrior = Warrior;

	return entities;
});
<commit_msg>ADVENTURE - warrior drawn according to the camera<commit_after>"use strict"

if (typeof (require) != 'undefined') {
	var loader = require('../../../../node_modules/Butterfly-js/dist/butterfly.min.js').loader;
}

/**
 * This module contains the definition of the Warrior entitie
 */
loader.addModule('GameEntities',
'Canvas',
function (canvas) {
	var entities = {},
		Warrior;

	(function () {
		var _radius = 10;

		/* Warrior Class */
		Warrior = function (name, x, y) {
			this.name = name;
			this.keys = 0;
			this.x  = this.originalX = x;
			this.y = this.originalY = y;
			this.r = _radius;
			this.angle = this.originalAngle = 0;
			this.speed = this.originalSpeed = 0;
			this.graphicLoaded = false;
		};

		function _walk (warrior, enable) {
			warrior.speed = enable ? 5 : 0;
		}

		Warrior.prototype.walkLeft = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI;
		};

		Warrior.prototype.walkRight = function (enable) {
			_walk(this, enable);
			this.angle = 0;
		};

		Warrior.prototype.walkUp = function (enable) {
			_walk(this, enable);
			this.angle = 3 * Math.PI / 2;
		};

		Warrior.prototype.walkDown = function (enable) {
			_walk(this, enable);
			this.angle = Math.PI / 2;
		};

		/**
		 * Method to update the warrior position according to its speed
		 */
		Warrior.prototype.updatePosition = function () {
			this.x += Math.cos(this.angle) * this.speed;
			this.y += Math.sin(this.angle) * this.speed;
		};

		Warrior.prototype.bumpBack = function () {
			this.x -= Math.cos(this.angle) * this.speed;
			this.y -= Math.sin(this.angle) * this.speed;

			this.speed = 0;
		};

		Warrior.prototype.setGraphic = function (graphic) {
			this.graphic = graphic;
		};

		/**
		 * Reset the warrior to its original values
		 */
		Warrior.prototype.reset = function () {
			this.x  = this.originalX;
			this.y = this.originalY;
			this.angle = this.originalAngle;
			this.speed = this.originalSpeed;
		};

		/**
		 * Draw the warrior on the screen
		 */
		Warrior.prototype.draw = function (x, y) {
			canvas.drawImage(
				this.graphic,
				x - this.graphic.width / 2,
				y - this.graphic.height / 2
			);
		};

		Warrior.prototype.addKey = function () {
			this.keys++;
		};

		Warrior.prototype.useKey = function () {
			this.keys--;
		};

		Warrior.prototype.getNumberOfKeys = function () {
			return this.keys;
		};
		/* End Warrior Class */
	})();

	entities.Warrior = Warrior;

	return entities;
});
<|endoftext|>


<commit_before>var _ = require('underscore');
var Promise = require("bluebird");
var SSH = require("simple-ssh");

var events = require('events');
var EventEmitter = events.EventEmitter;

module.exports = SshClient;

/**
 * The SSH client
 * @param initPromise initial promise, that loads IP, credentials, server info for a requested servers
 * @constructor
 *
 * @example
 *
 * compute.servers()
 *      .execSsh(server)
 *      .run(['cd ~;ls', 'ls -l', 'ls -all']).then(processSuccess, processErrors)
 *      .run('ls -all').then(processSuccess);
 */
function SshClient(initPromise) {
    var self,
        clients,
        emitterMethods = ['on', 'emit'],
        serverProps;

    var defaultTimeout = 60 * 1000;

    function getPromise() {
        var emitter = new EventEmitter();
        var promise = new Promise(function (resolve, reject) {
            emitter.on('complete', function (result) {
                resolve(result);
            });

            emitter.on('error', function (errors) {
                reject(errors);
            });
        });

        _.each(emitterMethods, function(method) {
            promise[method] = _.bind(emitter[method], emitter);
        });

        promise.executedCommands = 0;
        promise.results = [];
        promise.errors = [];

        return promise;
    }

    function init() {
        self = initPromise.then(initClient);

        self.thenPromise = self.then;
    }

    init();

    function initClient(props) {
        serverProps = props;
    }

    function setupClients() {
        clients = _.map(serverProps, function(opts) {

            var sshClient = new SSH({
                host: opts.ipAddress.publicIPAddress,
                user: opts.credentials.userName,
                pass: opts.credentials.password,
                timeout: defaultTimeout
            });
            sshClient.server = opts.server;

            return sshClient;
        });
    }

    /**
     * Method allows execute commands provided as array or string.
     *
     * @param commands {String|Array<String>} the command name
     * @returns {SshClient}
     *
     * @memberof SshClient
     * @instance
     * @function run
     */
    self.run = function(commands) {

        self = self.then(execCommand(commands));

        return self;
    };

    /**
     * The Promise.then wrapper function.

     * The first param in processSuccess and processErrors will be in format:
     *
{
     *
     server: serverId,
     *
     result: the array of output for each command
     *
}
     * @param processSuccess {function} the successful promise handler
     * @param processErrors {function} the failure promise handler
     * @returns {SshClient}
     *
     * @memberof SshClient
     * @instance
     * @function then
     *
     * @example
     * [
     *     {
     *       "server": "de1altdweb598",
     *       "results": [
     *         {
     *           "cmd": "ls",
     *           "result": "anaconda-ks.cfg\ninstall.log\ninstall.log.syslog\n"
     *         }
     *       ]
     *     }
     * ]
     */
    self.then = function(processSuccess, processErrors) {

        self = self.thenPromise(processSuccess, processErrors);

        self.run = this.run;
        self.thenPromise = self.then;
        self.then = this.then;

        return self;
    };

    function execCommand(commands) {
        return function() {
            setupClients();
            return Promise.all(_.map(clients, function(client) {
                return execute(client, commands);
            }));
        };
    }

    function execute(client, commands) {
        var allCommands = splitCommands(commands),
            clientPromise = getPromise();

        _.each(allCommands, function(command) {
            client.exec(command, {
                out: handleEvent(command, clientPromise, "complete"),
                err: handleEvent(command, clientPromise, "error"),
                exit: function(code) {
                    clientPromise.executedCommands++;

                    if (clientPromise.executedCommands === allCommands.length) {
                        if (clientPromise.errors.length > 0) {
                            clientPromise.emit('error',
                                {
                                    server: client.server.id,
                                    results: clientPromise.errors
                                }
                            );
                            return;
                        }
                        clientPromise.emit('complete',
                            {
                                server: client.server.id,
                                results: clientPromise.results
                            }
                        );
                    }
                }
            });
        });

        client.start({
            fail: function(err) {
                console.error("Cannot connect to " + client.server.id);
                console.error(err);
            }
        });

        return clientPromise;
    }

    function splitCommands(commands) {
        return _.chain(_.asArray(commands))
            .map(function(command) {
                return command.split(";");
            })
            .flatten()
            .value();
    }

    function handleEvent(command, promise, event) {

        return function(result) {
            var resultObj = {
                cmd: command,
                result: result
            };

            if (event === 'complete') {
                promise.results.push(resultObj);
            } else {
                promise.errors.push(resultObj);
            }
        };
    }

    return self;
}<commit_msg>#154 Unit tests coverage improvement. fix ssh-client test<commit_after>var _ = require('underscore');
var Promise = require("bluebird");
var SSH = require("simple-ssh");

var events = require('events');
var EventEmitter = events.EventEmitter;

module.exports = SshClient;

/**
 * The SSH client
 * @param initPromise initial promise, that loads IP, credentials, server info for a requested servers
 * @constructor
 *
 * @example
 *
 * compute.servers()
 *      .execSsh(server)
 *      .run(['cd ~;ls', 'ls -l', 'ls -all']).then(processSuccess, processErrors)
 *      .run('ls -all').then(processSuccess);
 */
function SshClient(initPromise) {
    var self,
        clients,
        emitterMethods = ['on', 'emit'],
        serverProps;

    var defaultTimeout = 3 * 60 * 1000;

    function getPromise() {
        var emitter = new EventEmitter();
        var promise = new Promise(function (resolve, reject) {
            emitter.on('complete', function (result) {
                resolve(result);
            });

            emitter.on('error', function (errors) {
                reject(errors);
            });
        });

        _.each(emitterMethods, function(method) {
            promise[method] = _.bind(emitter[method], emitter);
        });

        promise.executedCommands = 0;
        promise.results = [];
        promise.errors = [];

        return promise;
    }

    function init() {
        self = initPromise.then(initClient);

        self.thenPromise = self.then;
    }

    init();

    function initClient(props) {
        serverProps = props;
    }

    function setupClients() {
        clients = _.map(serverProps, function(opts) {

            var sshClient = new SSH({
                host: opts.ipAddress.publicIPAddress,
                user: opts.credentials.userName,
                pass: opts.credentials.password,
                timeout: defaultTimeout
            });
            sshClient.server = opts.server;

            return sshClient;
        });
    }

    /**
     * Method allows execute commands provided as array or string.
     *
     * @param commands {String|Array<String>} the command name
     * @returns {SshClient}
     *
     * @memberof SshClient
     * @instance
     * @function run
     */
    self.run = function(commands) {

        self = self.then(execCommand(commands));

        return self;
    };

    /**
     * The Promise.then wrapper function.

     * The first param in processSuccess and processErrors will be in format:
     *
{
     *
     server: serverId,
     *
     result: the array of output for each command
     *
}
     * @param processSuccess {function} the successful promise handler
     * @param processErrors {function} the failure promise handler
     * @returns {SshClient}
     *
     * @memberof SshClient
     * @instance
     * @function then
     *
     * @example
     * [
     *     {
     *       "server": "de1altdweb598",
     *       "results": [
     *         {
     *           "cmd": "ls",
     *           "result": "anaconda-ks.cfg\ninstall.log\ninstall.log.syslog\n"
     *         }
     *       ]
     *     }
     * ]
     */
    self.then = function(processSuccess, processErrors) {

        self = self.thenPromise(processSuccess, processErrors);

        self.run = this.run;
        self.thenPromise = self.then;
        self.then = this.then;

        return self;
    };

    function execCommand(commands) {
        return function() {
            setupClients();
            return Promise.all(_.map(clients, function(client) {
                return execute(client, commands);
            }));
        };
    }

    function execute(client, commands) {
        var allCommands = splitCommands(commands),
            clientPromise = getPromise();

        _.each(allCommands, function(command) {
            client.exec(command, {
                out: handleEvent(command, clientPromise, "complete"),
                err: handleEvent(command, clientPromise, "error"),
                exit: function(code) {
                    clientPromise.executedCommands++;

                    if (clientPromise.executedCommands === allCommands.length) {
                        if (clientPromise.errors.length > 0) {
                            clientPromise.emit('error',
                                {
                                    server: client.server.id,
                                    results: clientPromise.errors
                                }
                            );
                            return;
                        }
                        clientPromise.emit('complete',
                            {
                                server: client.server.id,
                                results: clientPromise.results
                            }
                        );
                    }
                }
            });
        });

        client.start({
            fail: function(err) {
                console.error("Cannot connect to " + client.server.id);
                console.error(err);
            }
        });

        return clientPromise;
    }

    function splitCommands(commands) {
        return _.chain(_.asArray(commands))
            .map(function(command) {
                return command.split(";");
            })
            .flatten()
            .value();
    }

    function handleEvent(command, promise, event) {

        return function(result) {
            var resultObj = {
                cmd: command,
                result: result
            };

            if (event === 'complete') {
                promise.results.push(resultObj);
            } else {
                promise.errors.push(resultObj);
            }
        };
    }

    return self;
}<|endoftext|>


<commit_before>/*
 *  acabed - webeditor for blinkenlights xml files
 *  Copyright (C) 2010 Raffael Mancini <raffael.mancini@hcl-club.lu>
 *                     Franz Pletz <fpletz@fnordicwalking.de>
 *
 *  This file is part of acabed.
 *
 *  acabed is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  acabed is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

var Editor = new Class({
    initialize: function(movie_player) {
        this.movie_player = movie_player;
        this.set_color(new Color(0, 0, 0));
        this.current_tool = new PenTool();
        this.clipboard = null;

        // initialize matrix click handler
        this.movie_player.matrix_table.addEvent('click', (function(row, col) {
            var current_frame = this.movie_player.current_frame();
            this.current_tool.apply_to(current_frame, row, col, this.current_color);
            this.movie_player.render(current_frame);

            console.info('set color to %s', this.current_color.to_string());
        }).bind(this));

        this.movie_player.matrix_table.addEvent('mouseup', (function() {
            this.current_tool.reset();
        }).bind(this));

        return this;
    },

    set_color: function(c) {
        $('current-color').setStyle('background-color', c.to_string());
        this.current_color = c;
    },

    current_frame_to_clipboard: function() {
        this.clipboard = this.movie_player.current_frame().copy();
    },

    clipboard_to_current_position: function() {
        if (this.clipboard !== null) {
            this.movie_player.movie.add_frame_at(this.movie_player.current_frame_no+1);
            this.movie_player.movie.set_frame(this.movie_player.current_frame_no+1, this.clipboard.copy());
            this.movie_player.forward(1);
        }
    },
});

// Dirty hack
function fix_frame(xml) {
    return xml.innerHTML.replace(/__frame/g, 'frame');
}

function init_editor() {

    var mv = new Movie();
    var mt = new CanvasTable('matrix-table');
    //var mt = new MatrixTable('matrix-table');
    var mp = new MoviePlayer(mv, mt);
    var ed = new Editor(mp);
    var pc = new PlayerControls('player-controls', {'movie_player': mp});

    var actions = new RadioContainer('pixel-tools', {
        widgets: [
            new ImageButton('draw-button', {
                image: '/assets/icons/pencil.png',
                tooltip: 'Farben malen',
                active: true,
                events: {
                    click: function() {
                        ed.current_tool = new PenTool();
                        MessageWidget.msg('Click auf ein Fenster um ein mit der aktuellen Farbe zu färben');
                    },
                },
            }),
            // new ImageButton('select-button', {
            //     image: '/assets/icons/layer-select.png',
            //     tooltip: 'Bereich auswählen',
            //     events: {
            //         click: function() {alert("test");},
            //     }
            // }),
            new ImageButton('fill-button', {
                image: '/assets/icons/paint-can.png',
                tooltip: 'Füllen',
                active: false,
                events: {
                    click: function() {
                        ed.current_tool = new FillTool();
                        MessageWidget.msg('Click auf ein Fenster um das aktuelle Bild mit der aktuellen Farbe zu füllen');
                    },
                }
            }),

            new ImageButton('move-button', {
                image: '/assets/icons/arrow-move.png',
                tooltip: 'Bewegen',
                active: false,
                events: {
                    click: function() {
                        ed.current_tool = new MoveTool(ed.movie_player.movie.height, ed.movie_player.movie.width);
                        MessageWidget.msg('Click auf ein Fenster um den Bildinhalt in Richtung Fenster zu bewegen');
                    },
                }
            }),

        ],
    });

    var loadingDialog = new ModalDialog(
	'loading-dialog',

	new Widget('loading', {
	    text: 'Der Film wird geladen'
	}),

	{
	    title: 'Bitte Warten',
	}
    );

    var frame_inspector = new ObjectInspector(mp.current_frame(), {
        id: 'frame-inspector',
        items: [
            {
                id: 'duration',
                title: 'Duration [ms]',
                description: 'Display time of the current frame.',
                type: 'number',
                max: '50'
            }
        ]
    });

    var movie_inspector = new ObjectInspector(mv, {
        id: 'movie-inspector',
        items: [
            {
                id: 'title',
                title: 'Title',
                description: 'The name of the animation.',
                type: 'text',
                max: '50'
            },
            {
                id: 'description',
                title: 'Description',
                description: 'An optional description that describes the work.',
                type: 'multiline',
                max: '1500',
                height: '5'
            },
            {
                id: 'author',
                title: 'Author',
                description: 'Name of the author',
                type: 'text',
                max: '50'
            },
            {
                id: 'email',
                title: 'E-Mail',
                description: 'Author E-Mail address',
                type: 'text',
                max: '50'
            },
            {
                id: 'loop',
                title: 'Loop video',
                description: 'Should this animation be played more than once.',
                type: 'bool',
            },
            {
                id: 'max_duration',
                title: 'Maximal duration [s]',
                description: 'Maximal play time',
                type: 'number'
            },
        ]
    });

    // Update Frame info
    mp.addEvent('render', (function() {
        frame_inspector.set_model(mp.current_frame());
    }).bind(mp));

    var inspectors = new WidgetContainer('right', {
        widgets: [frame_inspector,
                  movie_inspector],
    });

    var file_toolbar = new WidgetContainer('file-toolbar', {
        widgets: [
            new ImageButton('new-movie-button', {
                image: '/assets/icons/film.png',
                tooltip: 'Neuer Film erstellen',
                events: {
                    click: function() {
                        mp.stop();

                        var d = new ModalDialog('really-new-movie',
                            new Widget('question', {
                                text: 'Wollen sie wirklich einen neuen Film anfangen? Dies verwirft den aktuellen Film!'
                            }),
                            {
                                title: 'Achtung',
                                buttons: [
                                    new Button('open-button', {
                                        text: 'Neuer Film',
                                        events: {
                                            click: function() {
                                                Dajaxice.animations.load_editor('Dajax.process');
                                                MessageWidget.msg('Ein neuer Film wurde erstellt');
                                                ModalDialog.destroy();
                                            },
                                        }
                                    }),
                                     new Button('cancel-button', {
                                        text: 'Abbrechen',
                                        events: {
                                            click: function() {
                                                ModalDialog.destroy();
                                            },
                                        }
                                    }),
                               ],
                            }
                        );
                        d.show();
                    },
                },
            }),
            new ImageButton('load-movie-button', {
                image: '/assets/icons/folder-open-film.png',
                tooltip: 'Film vom Server öffnen',
                events: {
                    click: function() {
                        var d = new ModalDialog('movie-list',
                            new Widget('movie-list', {
                                text: 'lorem ipsum'
                            }),
                            {
                                title: 'Animation öffnen',
                                buttons: [
                                    new Button('open-button', {
                                        text: 'Öffnen',
                                        events: {
                                            click: function() {
                                            },
                                        }
                                    }),
                                     new Button('cancel-button', {
                                        text: 'Abbrechen',
                                        events: {
                                            click: function() {
                                                ModalDialog.destroy();
                                            },
                                        }
                                    }),
                               ],
                            }
                        );
                        d.show();
                    },
                },
            }),
            new ImageButton('save-movie-button', {
                image: '/assets/icons/disk.png',
                tooltip: 'Film abschicken',
                events: {
                    click: function() {
                        Dajaxice.animations.add('Dajax.process', {
                            'animation': mp.movie.to_json()});
                    },
                },
            }),
        ],
    });

    var port_toolbar = new WidgetContainer('port-toolbar', {
        widgets: [
            new FileButton('load-xml-button', {
                image: '/assets/icons/arrow-090.png',
                tooltip: 'Film vom Rechner öffnen',
                events: {
                    loaded: function(text) {
                    	mp.load_file(text);
                    	loadingDialog.hide();
                    },

		    clicked: function() {
			loadingDialog.show();
		    }
                },
            }),
            new ImageButton('download-xml-button', {
                image: '/assets/icons/arrow-270.png',
                tooltip: 'Film auf Rechner speichern',
                events: {
                    click: function() {
                        var uri = 'data:text/xml;charset=utf-8,';
                        uri += fix_frame(mp.movie.to_xml());
                        document.location = uri;
                    },
                },
            }),
            // new ImageButton('send-json-button', {
            //     image: '/assets/icons/arrow-curve-000-left.png',
            //     tooltip: 'wtf? xD',
            //     events: {
            //         click: function() {
            //             Dajaxice.animations.add('Dajax.process', {
            //                 'animation': mp.movie.to_json()
            //             });
            //         },
            //     },
            // }),
        ],
    });

    var frametools = new WidgetContainer('frame-tools', {
        widgets: [
            new ImageButton('duplicate-frame-button', {
                image: '/assets/icons/layers-arrange.png',
                tooltip: 'Aktuellen Frame duplizieren',
                events: {
                    click: function() {
                        console.info('duplicate frame: %d', mp.current_frame_no);
                        mv.duplicate_frame_at(mp.current_frame_no);
                        mp.forward(1);
                    },
                },
            }),
            new ImageButton('add-frame-button', {
                image: '/assets/icons/layer--plus.png',
                tooltip: 'Neuen leeren Frame hinzufügen',
                events: {
                    click: function() {
                        console.info('add frame');
                        mv.add_frame_at(mp.current_frame_no+1);
                        mp.forward(1);
                    },
                },
            }),
            new ImageButton('delete-frame-button', {
                image: '/assets/icons/layer--minus.png',
                tooltip: 'Aktuellen Frame löschen',
                events: {
                    click: function() {
                        console.info('remove frame');
                        if (mv.frames > 1) {
                            mv.remove_frame_at(mp.current_frame_no);
                            mp.update();
                        }
                    },
                }
            }),
            new ImageButton('copy-frame-button', {
                image: '/assets/icons/document-copy.png',
                tooltip: 'Frame kopieren',
                events: {
                    click: function() {
                        ed.current_frame_to_clipboard();
                    },
                }
            }),
            new ImageButton('paste-frame-button', {
                image: '/assets/icons/clipboard-paste.png',
                tooltip: 'Frame einfügen',
                events: {
                    click: function() {
                        ed.clipboard_to_current_position();
                    },
                }
            }),
        ],
    });

    // Color picker change callback sets current_color of editor
    var set_editor_color = function (s) {
        c = new Color();
        c.set_from_string(s);
        ed.set_color(c);
    };

    var picker = new Moopick({
        palletParentElement: $('color-tools'),
        palletID: 'colorfoo',
        styles: { width: '10px', height: '10px' }
    });

    var current_color_field = new Widget('current-color');

    picker.addEvents({
        'onColorClick': set_editor_color,
    });

    // Color picker change callback sets current_color of editor
    var palette = new Palette({
        palletParentElement: $('colorpalette-tools'),
        palletID: 'colorpalettefoo',
        styles: { width: '10px', height: '10px' }
    });
    palette.addEvents({
        'onColorClick': set_editor_color,
    });

    // Update slider max on Movie resizing
    mv.addEvent('modify', function() {
        mt.fireEvent('reset');
        pc.slider.set(mp.current_frame_no);
    });
    mt.addEvent('reset',function() { });

    // Set initial State
    mv.add_frame_at(0);
    mt.reset(4, 24);
};

<commit_msg>Don't let people leave our site<commit_after>/*
 *  acabed - webeditor for blinkenlights xml files
 *  Copyright (C) 2010 Raffael Mancini <raffael.mancini@hcl-club.lu>
 *                     Franz Pletz <fpletz@fnordicwalking.de>
 *
 *  This file is part of acabed.
 *
 *  acabed is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as
 *  published by the Free Software Foundation, either version 3 of the
 *  License, or (at your option) any later version.
 *
 *  acabed is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  You should have received a copy of the GNU Affero General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

var Editor = new Class({
    initialize: function(movie_player) {
        this.movie_player = movie_player;
        this.set_color(new Color(0, 0, 0));
        this.current_tool = new PenTool();
        this.clipboard = null;

        // initialize matrix click handler
        this.movie_player.matrix_table.addEvent('click', (function(row, col) {
            var current_frame = this.movie_player.current_frame();
            this.current_tool.apply_to(current_frame, row, col, this.current_color);
            this.movie_player.render(current_frame);

            console.info('set color to %s', this.current_color.to_string());
        }).bind(this));

        this.movie_player.matrix_table.addEvent('mouseup', (function() {
            this.current_tool.reset();
        }).bind(this));

        return this;
    },

    set_color: function(c) {
        $('current-color').setStyle('background-color', c.to_string());
        this.current_color = c;
    },

    current_frame_to_clipboard: function() {
        this.clipboard = this.movie_player.current_frame().copy();
    },

    clipboard_to_current_position: function() {
        if (this.clipboard !== null) {
            this.movie_player.movie.add_frame_at(this.movie_player.current_frame_no+1);
            this.movie_player.movie.set_frame(this.movie_player.current_frame_no+1, this.clipboard.copy());
            this.movie_player.forward(1);
        }
    },
});

// Dirty hack
function fix_frame(xml) {
    return xml.innerHTML.replace(/__frame/g, 'frame');
}

function init_editor() {
    var mv = new Movie();
    var mt = new CanvasTable('matrix-table');
    //var mt = new MatrixTable('matrix-table');
    var mp = new MoviePlayer(mv, mt);
    var ed = new Editor(mp);
    var pc = new PlayerControls('player-controls', {'movie_player': mp});

    var actions = new RadioContainer('pixel-tools', {
        widgets: [
            new ImageButton('draw-button', {
                image: '/assets/icons/pencil.png',
                tooltip: 'Farben malen',
                active: true,
                events: {
                    click: function() {
                        ed.current_tool = new PenTool();
                        MessageWidget.msg('Click auf ein Fenster um ein mit der aktuellen Farbe zu färben');
                    },
                },
            }),
            // new ImageButton('select-button', {
            //     image: '/assets/icons/layer-select.png',
            //     tooltip: 'Bereich auswählen',
            //     events: {
            //         click: function() {alert("test");},
            //     }
            // }),
            new ImageButton('fill-button', {
                image: '/assets/icons/paint-can.png',
                tooltip: 'Füllen',
                active: false,
                events: {
                    click: function() {
                        ed.current_tool = new FillTool();
                        MessageWidget.msg('Click auf ein Fenster um das aktuelle Bild mit der aktuellen Farbe zu füllen');
                    },
                }
            }),

            new ImageButton('move-button', {
                image: '/assets/icons/arrow-move.png',
                tooltip: 'Bewegen',
                active: false,
                events: {
                    click: function() {
                        ed.current_tool = new MoveTool(ed.movie_player.movie.height, ed.movie_player.movie.width);
                        MessageWidget.msg('Click auf ein Fenster um den Bildinhalt in Richtung Fenster zu bewegen');
                    },
                }
            }),

        ],
    });

    var loadingDialog = new ModalDialog(
	'loading-dialog',

	new Widget('loading', {
	    text: 'Der Film wird geladen'
	}),

	{
	    title: 'Bitte Warten',
	}
    );

    var frame_inspector = new ObjectInspector(mp.current_frame(), {
        id: 'frame-inspector',
        items: [
            {
                id: 'duration',
                title: 'Duration [ms]',
                description: 'Display time of the current frame.',
                type: 'number',
                max: '50'
            }
        ]
    });

    var movie_inspector = new ObjectInspector(mv, {
        id: 'movie-inspector',
        items: [
            {
                id: 'title',
                title: 'Title',
                description: 'The name of the animation.',
                type: 'text',
                max: '50'
            },
            {
                id: 'description',
                title: 'Description',
                description: 'An optional description that describes the work.',
                type: 'multiline',
                max: '1500',
                height: '5'
            },
            {
                id: 'author',
                title: 'Author',
                description: 'Name of the author',
                type: 'text',
                max: '50'
            },
            {
                id: 'email',
                title: 'E-Mail',
                description: 'Author E-Mail address',
                type: 'text',
                max: '50'
            },
            {
                id: 'loop',
                title: 'Loop video',
                description: 'Should this animation be played more than once.',
                type: 'bool',
            },
            {
                id: 'max_duration',
                title: 'Maximal duration [s]',
                description: 'Maximal play time',
                type: 'number'
            },
        ]
    });

    // Update Frame info
    mp.addEvent('render', (function() {
        frame_inspector.set_model(mp.current_frame());
    }).bind(mp));

    var inspectors = new WidgetContainer('right', {
        widgets: [frame_inspector,
                  movie_inspector],
    });

    var file_toolbar = new WidgetContainer('file-toolbar', {
        widgets: [
            new ImageButton('new-movie-button', {
                image: '/assets/icons/film.png',
                tooltip: 'Neuer Film erstellen',
                events: {
                    click: function() {
                        mp.stop();

                        var d = new ModalDialog('really-new-movie',
                            new Widget('question', {
                                text: 'Wollen sie wirklich einen neuen Film anfangen? Dies verwirft den aktuellen Film!'
                            }),
                            {
                                title: 'Achtung',
                                buttons: [
                                    new Button('open-button', {
                                        text: 'Neuer Film',
                                        events: {
                                            click: function() {
                                                Dajaxice.animations.load_editor('Dajax.process');
                                                MessageWidget.msg('Ein neuer Film wurde erstellt');
                                                ModalDialog.destroy();
                                            },
                                        }
                                    }),
                                     new Button('cancel-button', {
                                        text: 'Abbrechen',
                                        events: {
                                            click: function() {
                                                ModalDialog.destroy();
                                            },
                                        }
                                    }),
                               ],
                            }
                        );
                        d.show();
                    },
                },
            }),
            new ImageButton('load-movie-button', {
                image: '/assets/icons/folder-open-film.png',
                tooltip: 'Film vom Server öffnen',
                events: {
                    click: function() {
                        var d = new ModalDialog('movie-list',
                            new Widget('movie-list', {
                                text: 'lorem ipsum'
                            }),
                            {
                                title: 'Animation öffnen',
                                buttons: [
                                    new Button('open-button', {
                                        text: 'Öffnen',
                                        events: {
                                            click: function() {
                                            },
                                        }
                                    }),
                                     new Button('cancel-button', {
                                        text: 'Abbrechen',
                                        events: {
                                            click: function() {
                                                ModalDialog.destroy();
                                            },
                                        }
                                    }),
                               ],
                            }
                        );
                        d.show();
                    },
                },
            }),
            new ImageButton('save-movie-button', {
                image: '/assets/icons/disk.png',
                tooltip: 'Film abschicken',
                events: {
                    click: function() {
                        Dajaxice.animations.add('Dajax.process', {
                            'animation': mp.movie.to_json()});
                    },
                },
            }),
        ],
    });

    var port_toolbar = new WidgetContainer('port-toolbar', {
        widgets: [
            new FileButton('load-xml-button', {
                image: '/assets/icons/arrow-090.png',
                tooltip: 'Film vom Rechner öffnen',
                events: {
                    loaded: function(text) {
                    	mp.load_file(text);
                    	loadingDialog.hide();
                    },

		    clicked: function() {
			loadingDialog.show();
		    }
                },
            }),
            new ImageButton('download-xml-button', {
                image: '/assets/icons/arrow-270.png',
                tooltip: 'Film auf Rechner speichern',
                events: {
                    click: function() {
                        var uri = 'data:text/xml;charset=utf-8,';
                        uri += fix_frame(mp.movie.to_xml());
                        document.location = uri;
                    },
                },
            }),
            // new ImageButton('send-json-button', {
            //     image: '/assets/icons/arrow-curve-000-left.png',
            //     tooltip: 'wtf? xD',
            //     events: {
            //         click: function() {
            //             Dajaxice.animations.add('Dajax.process', {
            //                 'animation': mp.movie.to_json()
            //             });
            //         },
            //     },
            // }),
        ],
    });

    var frametools = new WidgetContainer('frame-tools', {
        widgets: [
            new ImageButton('duplicate-frame-button', {
                image: '/assets/icons/layers-arrange.png',
                tooltip: 'Aktuellen Frame duplizieren',
                events: {
                    click: function() {
                        console.info('duplicate frame: %d', mp.current_frame_no);
                        mv.duplicate_frame_at(mp.current_frame_no);
                        mp.forward(1);
                    },
                },
            }),
            new ImageButton('add-frame-button', {
                image: '/assets/icons/layer--plus.png',
                tooltip: 'Neuen leeren Frame hinzufügen',
                events: {
                    click: function() {
                        console.info('add frame');
                        mv.add_frame_at(mp.current_frame_no+1);
                        mp.forward(1);
                    },
                },
            }),
            new ImageButton('delete-frame-button', {
                image: '/assets/icons/layer--minus.png',
                tooltip: 'Aktuellen Frame löschen',
                events: {
                    click: function() {
                        console.info('remove frame');
                        if (mv.frames > 1) {
                            mv.remove_frame_at(mp.current_frame_no);
                            mp.update();
                        }
                    },
                }
            }),
            new ImageButton('copy-frame-button', {
                image: '/assets/icons/document-copy.png',
                tooltip: 'Frame kopieren',
                events: {
                    click: function() {
                        ed.current_frame_to_clipboard();
                    },
                }
            }),
            new ImageButton('paste-frame-button', {
                image: '/assets/icons/clipboard-paste.png',
                tooltip: 'Frame einfügen',
                events: {
                    click: function() {
                        ed.clipboard_to_current_position();
                    },
                }
            }),
        ],
    });

    // Color picker change callback sets current_color of editor
    var set_editor_color = function (s) {
        c = new Color();
        c.set_from_string(s);
        ed.set_color(c);
    };

    var picker = new Moopick({
        palletParentElement: $('color-tools'),
        palletID: 'colorfoo',
        styles: { width: '10px', height: '10px' }
    });

    var current_color_field = new Widget('current-color');

    picker.addEvents({
        'onColorClick': set_editor_color,
    });

    // Color picker change callback sets current_color of editor
    var palette = new Palette({
        palletParentElement: $('colorpalette-tools'),
        palletID: 'colorpalettefoo',
        styles: { width: '10px', height: '10px' }
    });
    palette.addEvents({
        'onColorClick': set_editor_color,
    });

    // Update slider max on Movie resizing
    mv.addEvent('modify', function() {
        mt.fireEvent('reset');
        pc.slider.set(mp.current_frame_no);
    });
    mt.addEvent('reset',function() { });

    // Prevent accidental unloading of page
    // FIXME: don't always prevent
    window.addEvent('beforeunload', function() {
        return false;
    });

    // Set initial State
    mv.add_frame_at(0);
    mt.reset(4, 24);
};

<|endoftext|>


<commit_before>// Copyright 2021, University of Colorado Boulder

/**
 * ManualConstraint tests
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import Node from '../../nodes/Node.js';
import Rectangle from '../../nodes/Rectangle.js';
import ManualConstraint from './ManualConstraint.js';

QUnit.module( 'ManualConstraint' );

QUnit.test( 'Identity', assert => {
  const a = new Rectangle( 0, 0, 100, 50, { fill: 'red' } );
  const b = new Rectangle( 0, 0, 100, 50, { fill: 'blue' } );
  const aContainer = new Node( { children: [ a ] } );
  const bContainer = new Node( { children: [ b ] } );
  const root = new Node( { children: [ aContainer, bContainer ] } );

  ManualConstraint.create( root, [ a, b ], ( aProxy, bProxy ) => {
    bProxy.left = aProxy.right;
  } );

  root.validateBounds();
  assert.equal( b.x, 100, 'x' );

  a.x = 100;

  root.validateBounds();
  assert.equal( b.x, 200, 'x after 100' );
} );

QUnit.test( 'Translation', assert => {
  const a = new Rectangle( 0, 0, 100, 50, { fill: 'red' } );
  const b = new Rectangle( 0, 0, 100, 50, { fill: 'blue' } );
  const aContainer = new Node( { children: [ a ] } );
  const bContainer = new Node( { children: [ b ], x: 50 } );
  const root = new Node( { children: [ aContainer, bContainer ] } );

  ManualConstraint.create( root, [ a, b ], ( aProxy, bProxy ) => {
    bProxy.left = aProxy.right;
  } );

  root.validateBounds();
  assert.equal( b.x, 50, 'x' );

  a.x = 100;

  root.validateBounds();
  assert.equal( b.x, 150, 'x after 100' );
} );

QUnit.test( 'Scale', assert => {
  const a = new Rectangle( 0, 0, 100, 50, { fill: 'red' } );
  const b = new Rectangle( 0, 0, 100, 50, { fill: 'blue' } );
  const aContainer = new Node( { children: [ a ], scale: 2 } );
  const bContainer = new Node( { children: [ b ] } );
  const root = new Node( { children: [ aContainer, bContainer ] } );

  ManualConstraint.create( root, [ a, b ], ( aProxy, bProxy ) => {
    bProxy.left = aProxy.right;
  } );

  root.validateBounds();
  assert.equal( b.x, 200, 'x' );

  a.x = 100;

  root.validateBounds();
  assert.equal( b.x, 400, 'x after 100' );
} );
<commit_msg>update copyright dates from daily grunt work<commit_after>// Copyright 2021-2022, University of Colorado Boulder

/**
 * ManualConstraint tests
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */

import Node from '../../nodes/Node.js';
import Rectangle from '../../nodes/Rectangle.js';
import ManualConstraint from './ManualConstraint.js';

QUnit.module( 'ManualConstraint' );

QUnit.test( 'Identity', assert => {
  const a = new Rectangle( 0, 0, 100, 50, { fill: 'red' } );
  const b = new Rectangle( 0, 0, 100, 50, { fill: 'blue' } );
  const aContainer = new Node( { children: [ a ] } );
  const bContainer = new Node( { children: [ b ] } );
  const root = new Node( { children: [ aContainer, bContainer ] } );

  ManualConstraint.create( root, [ a, b ], ( aProxy, bProxy ) => {
    bProxy.left = aProxy.right;
  } );

  root.validateBounds();
  assert.equal( b.x, 100, 'x' );

  a.x = 100;

  root.validateBounds();
  assert.equal( b.x, 200, 'x after 100' );
} );

QUnit.test( 'Translation', assert => {
  const a = new Rectangle( 0, 0, 100, 50, { fill: 'red' } );
  const b = new Rectangle( 0, 0, 100, 50, { fill: 'blue' } );
  const aContainer = new Node( { children: [ a ] } );
  const bContainer = new Node( { children: [ b ], x: 50 } );
  const root = new Node( { children: [ aContainer, bContainer ] } );

  ManualConstraint.create( root, [ a, b ], ( aProxy, bProxy ) => {
    bProxy.left = aProxy.right;
  } );

  root.validateBounds();
  assert.equal( b.x, 50, 'x' );

  a.x = 100;

  root.validateBounds();
  assert.equal( b.x, 150, 'x after 100' );
} );

QUnit.test( 'Scale', assert => {
  const a = new Rectangle( 0, 0, 100, 50, { fill: 'red' } );
  const b = new Rectangle( 0, 0, 100, 50, { fill: 'blue' } );
  const aContainer = new Node( { children: [ a ], scale: 2 } );
  const bContainer = new Node( { children: [ b ] } );
  const root = new Node( { children: [ aContainer, bContainer ] } );

  ManualConstraint.create( root, [ a, b ], ( aProxy, bProxy ) => {
    bProxy.left = aProxy.right;
  } );

  root.validateBounds();
  assert.equal( b.x, 200, 'x' );

  a.x = 100;

  root.validateBounds();
  assert.equal( b.x, 400, 'x after 100' );
} );
<|endoftext|>


<commit_before>import program from 'commander';
import '../loadenv';
import '../utils/db';
import { ParseFeed, ParsePodcast } from '../parsers/feed';
import chalk from 'chalk';
import logger from '../utils/logger';
import Podcast from '../models/podcast';
import RSS from '../models/rss';
import config from '../config';
import normalize from 'normalize-url';
import async_tasks from '../async_tasks';

// do stuff
export async function debugFeed(feedType, feedUrls) {
	// This is a small helper tool to quickly help debug issues with podcasts or RSS feeds
	logger.info(`Starting the ${feedType} Debugger \\0/`);
	logger.info(
		'Please report issues with RSS feeds here https://github.com/getstream/winds',
	);
	logger.info('Note that pull requests are much appreciated!');
	logger.info(`Handling ${feedUrls.length} urls`);

	for (let target of feedUrls) {
		target = normalize(target);
		logger.info(`Looking up the first ${program.limit} articles from ${target}`);

		function validate(response) {

			// validate the podcast or RSS feed
			logger.info('========== Validating Publication ==========');
			logger.info(`Title: ${response.title}`);
			logger.info(`Link: ${response.link}`);
			if (response.image && response.image.og) {
				logger.info(chalk.green('Image found :)'));
				logger.info(`Image: ${response.image.og}`);
			} else {
				logger.info(chalk.red('Image missing :('));
			}

			logger.info('========== Validating episodes/articles now ==========');

			// validate the articles or episodes
			let articles = response.articles ? response.articles : response.episodes;
			let selectedArticles = articles.slice(0, program.limit);
			logger.info(`Found ${articles.length} articles showing ${program.limit}`);

			if (selectedArticles.length) {
				for (let article of selectedArticles) {
					logger.info('======================================');
					logger.info(chalk.green(`Title: ${article.title}`));
					logger.info(`URL: ${article.url}`);
					logger.info(`Description: ${article.description}`);
					logger.info(`Publication Date: ${article.publicationDate}`);
					if (article.commentUrl) {
						logger.info(`Comments: ${article.commentUrl}`);
					}
					if (article.content) {
						logger.info(`Content: ${article.content}`);
					}

					// for RSS we rely on OG scraping, for podcasts the images are already in the feed
					if (article.images && article.images.og) {
						logger.info(chalk.green('Image found :)'));
						logger.info(`Image: ${article.images.og}`);
					} else {
						logger.info(chalk.red('Image missing :('));
					}
					if (feedType === 'podcast') {
						if (article.enclosure) {
							logger.info(chalk.green('Enclosure found :)'));
							logger.info(article.enclosure);
						} else {
							logger.info(chalk.red('Missing enclosure :('));
						}
					}
				}
			} else {
				logger.info(chalk.red('Didn\'t find any articles or episodes.'));
			}

			let schema = feedType === 'rss' ? RSS : Podcast;
			let lookup = { feedUrl: target };
			if (program.task) {
				logger.info('trying to create a task on the bull queue');
				schema
					.findOne(lookup)
					.catch(err => {
						console.log('failed', err);
					})
					.then(instance => {
						let queuePromise;

						if (!instance) {
							logger.info('failed to find publication');
							return;
						}

						if (feedType == 'rss') {
							queuePromise = async_tasks.RssQueueAdd(
								{
									rss: instance._id,
									url: instance.feedUrl,
								},
								{
									priority: 1,
									removeOnComplete: true,
									removeOnFail: true,
								},
							);
						} else {
							queuePromise = async_tasks.PodcastQueueAdd(
								{
									podcast: instance._id,
									url: instance.feedUrl,
								},
								{
									priority: 1,
									removeOnComplete: true,
									removeOnFail: true,
								},
							);
						}

						queuePromise
							.then(() => {
								if (feedType === 'rss') {
									logger.info(
										`Scheduled RSS feed to the queue for parsing ${target} with id ${
											instance._id
										}`,
									);
								} else {
									logger.info(
										`Scheduled Podcast to the queue for parsing ${target}`,
									);
								}
							})
							.catch(err => {
								logger.error('Failed to schedule task on og queue');
							});
					});
			}
		}

		if (feedType === 'rss') {
			let feedContent = await ParseFeed(target);
			validate(feedContent)
		} else {
			let podcastContent = await ParsePodcast(target);
			validate(podcastContent)
		}
		logger.info('Note that upgrading feedparser can sometimes improve parsing.');
	}
}
<commit_msg>debug f<commit_after>import program from 'commander';
import '../loadenv';
import '../utils/db';
import { ParseFeed, ParsePodcast } from '../parsers/feed';
import chalk from 'chalk';
import logger from '../utils/logger';
import Podcast from '../models/podcast';
import RSS from '../models/rss';
import config from '../config';
import normalize from 'normalize-url';
import async_tasks from '../async_tasks';

// do stuff
export async function debugFeed(feedType, feedUrls) {
	// This is a small helper tool to quickly help debug issues with podcasts or RSS feeds
	logger.info(`Starting the ${feedType} Debugger \\0/`);
	logger.info(
		'Please report issues with RSS feeds here https://github.com/getstream/winds',
	);
	logger.info('Note that pull requests are much appreciated!');
	logger.info(`Handling ${feedUrls.length} urls`);

	for (let target of feedUrls) {
		target = normalize(target);
		logger.info(`Looking up the first ${program.limit} articles from ${target}`);

		async function validate(response) {

			// validate the podcast or RSS feed
			logger.info('========== Validating Publication ==========');
			logger.info(`Title: ${response.title}`);
			logger.info(`Link: ${response.link}`);
			if (response.image && response.image.og) {
				logger.info(chalk.green('Image found :)'));
				logger.info(`Image: ${response.image.og}`);
			} else {
				logger.info(chalk.red('Image missing :('));
			}

			logger.info('========== Validating episodes/articles now ==========');

			// validate the articles or episodes
			let articles = response.articles ? response.articles : response.episodes;
			let selectedArticles = articles.slice(0, program.limit);
			logger.info(`Found ${articles.length} articles showing ${program.limit}`);

			if (selectedArticles.length) {
				for (let article of selectedArticles) {
					logger.info('======================================');
					logger.info(chalk.green(`Title: ${article.title}`));
					logger.info(`URL: ${article.url}`);
					logger.info(`Description: ${article.description}`);
					logger.info(`Publication Date: ${article.publicationDate}`);
					if (article.commentUrl) {
						logger.info(`Comments: ${article.commentUrl}`);
					}
					if (article.content) {
						logger.info(`Content: ${article.content}`);
					}

					// for RSS we rely on OG scraping, for podcasts the images are already in the feed
					if (article.images && article.images.og) {
						logger.info(chalk.green('Image found :)'));
						logger.info(`Image: ${article.images.og}`);
					} else {
						logger.info(chalk.red('Image missing :('));
					}
					if (feedType === 'podcast') {
						if (article.enclosure) {
							logger.info(chalk.green('Enclosure found :)'));
							logger.info(article.enclosure);
						} else {
							logger.info(chalk.red('Missing enclosure :('));
						}
					}
				}
			} else {
				logger.info(chalk.red('Didn\'t find any articles or episodes.'));
			}

			let schema = feedType === 'rss' ? RSS : Podcast;
			let lookup = { feedUrl: target };
			console.log(feedType, lookup)
			if (program.task) {
				logger.info('trying to create a task on the bull queue');
				let instance = await schema.findOne(lookup)
				console.log(instance)

				schema
					.findOne(lookup)
					.catch(err => {
						console.log('failed', err);
					})
					.then(instance => {
						let queuePromise;

						if (!instance) {
							logger.info('failed to find publication');
							return;
						}

						if (feedType == 'rss') {
							queuePromise = async_tasks.RssQueueAdd(
								{
									rss: instance._id,
									url: instance.feedUrl,
								},
								{
									priority: 1,
									removeOnComplete: true,
									removeOnFail: true,
								},
							);
						} else {
							queuePromise = async_tasks.PodcastQueueAdd(
								{
									podcast: instance._id,
									url: instance.feedUrl,
								},
								{
									priority: 1,
									removeOnComplete: true,
									removeOnFail: true,
								},
							);
						}

						queuePromise
							.then(() => {
								if (feedType === 'rss') {
									logger.info(
										`Scheduled RSS feed to the queue for parsing ${target} with id ${
											instance._id
										}`,
									);
								} else {
									logger.info(
										`Scheduled Podcast to the queue for parsing ${target}`,
									);
								}
							})
							.catch(err => {
								logger.error('Failed to schedule task on og queue');
							});
					});
			}
		}

		if (feedType === 'rss') {
			let feedContent = await ParseFeed(target);
			validate(feedContent)
		} else {
			let podcastContent = await ParsePodcast(target);
			validate(podcastContent)
		}
		logger.info('Note that upgrading feedparser can sometimes improve parsing.');
	}
}
<|endoftext|>


<commit_before>import Component from '@ember/component';
import {computed} from '@ember/object';
import {reads} from '@ember/object/computed';
import {inject as service} from '@ember/service';
import {set} from '@ember/object';

const US = {flag: '🇺🇸', name: 'US', baseUrl: 'https://api.mailgun.net/v3'};
const EU = {flag: '🇪🇺', name: 'EU', baseUrl: 'https://api.eu.mailgun.net/v3'};

const CURRENCIES = [
    {
        label: 'USD - US Dollar', value: 'usd'
    },
    {
        label: 'AUD - Australian Dollar', value: 'aud'
    },
    {
        label: 'CAD - Canadian Dollar', value: 'cad'
    },
    {
        label: 'EUR - Euro', value: 'eur'
    },
    {
        label: 'GBP - British Pound', value: 'gbp'
    }
];

export default Component.extend({
    feature: service(),
    config: service(),
    mediaQueries: service(),

    currencies: null,

    // passed in actions
    setMembersSubscriptionSettings() {},

    defaultContentVisibility: reads('settings.defaultContentVisibility'),

    selectedCurrency: computed('subscriptionSettings.stripeConfig.plans.monthly.currency', function () {
        return CURRENCIES.findBy('value', this.get('subscriptionSettings.stripeConfig.plans.monthly.currency'));
    }),

    mailgunRegion: computed('settings.bulkEmailSettings.baseUrl', function () {
        if (!this.settings.get('bulkEmailSettings.baseUrl')) {
            return US;
        }

        return [US, EU].find((region) => {
            return region.baseUrl === this.settings.get('bulkEmailSettings.baseUrl');
        });
    }),

    blogDomain: computed('config.blogDomain', function () {
        let domain = this.config.blogDomain || '';
        const host = domain.replace('https://', '').replace('http://', '').split('/');
        return (host && host[0]) || '';
    }),

    subscriptionSettings: computed('settings.membersSubscriptionSettings', function () {
        let subscriptionSettings = this.settings.parseSubscriptionSettings(this.get('settings.membersSubscriptionSettings'));
        let stripeProcessor = subscriptionSettings.paymentProcessors.find((proc) => {
            return (proc.adapter === 'stripe');
        });
        let monthlyPlan = stripeProcessor.config.plans.find(plan => plan.interval === 'month');
        let yearlyPlan = stripeProcessor.config.plans.find(plan => plan.interval === 'year');

        // NOTE: need to be careful about division by zero if we introduce zero decimal currencies
        //       ref.: https://stripe.com/docs/currencies#zero-decimal
        monthlyPlan.amount = parseInt(monthlyPlan.amount) ? (monthlyPlan.amount / 100) : 0;
        yearlyPlan.amount = parseInt(yearlyPlan.amount) ? (yearlyPlan.amount / 100) : 0;

        stripeProcessor.config.plans = {
            monthly: monthlyPlan,
            yearly: yearlyPlan
        };
        subscriptionSettings.stripeConfig = stripeProcessor.config;
        subscriptionSettings.allowSelfSignup = !!subscriptionSettings.allowSelfSignup;
        subscriptionSettings.fromAddress = subscriptionSettings.fromAddress || '';

        return subscriptionSettings;
    }),

    bulkEmailSettings: computed('settings.bulkEmailSettings', function () {
        let bulkEmailSettings = this.get('settings.bulkEmailSettings') || {};
        const {apiKey = '', baseUrl = US.baseUrl, domain = ''} = bulkEmailSettings;
        return {apiKey, baseUrl, domain};
    }),

    hasBulkEmailConfig: computed('settings.bulkEmailSettings', function () {
        let bulkEmailSettings = this.get('settings.bulkEmailSettings');
        return !!bulkEmailSettings.isConfig;
    }),

    init() {
        this._super(...arguments);
        this.set('mailgunRegions', [US, EU]);
        this.set('currencies', CURRENCIES);
    },

    actions: {
        setDefaultContentVisibility(value) {
            this.setDefaultContentVisibility(value);
        },

        setBulkEmailSettings(key, event) {
            let bulkEmailSettings = this.get('settings.bulkEmailSettings') || {};
            bulkEmailSettings[key] = event.target.value;
            if (!bulkEmailSettings.baseUrl) {
                set(bulkEmailSettings, 'baseUrl', US.baseUrl);
            }
            this.setBulkEmailSettings(bulkEmailSettings);
        },

        setBulkEmailRegion(region) {
            let bulkEmailSettings = this.get('settings.bulkEmailSettings') || {};
            set(bulkEmailSettings, 'baseUrl', region.baseUrl);
            this.setBulkEmailSettings(bulkEmailSettings);
        },

        setSubscriptionSettings(key, event) {
            let subscriptionSettings = this.settings.parseSubscriptionSettings(this.get('settings.membersSubscriptionSettings'));
            let stripeProcessor = subscriptionSettings.paymentProcessors.find((proc) => {
                return (proc.adapter === 'stripe');
            });
            let stripeConfig = stripeProcessor.config;
            stripeConfig.product = {
                name: this.settings.get('title')
            };
            // TODO: this flag has to be removed as it doesn't serve any purpose
            if (key === 'isPaid') {
                subscriptionSettings.isPaid = event;
            }
            if (key === 'secret_token' || key === 'public_token') {
                stripeConfig[key] = event.target.value;
            }
            if (key === 'month' || key === 'year') {
                stripeConfig.plans = stripeConfig.plans.map((plan) => {
                    if (key === plan.interval) {
                        plan.amount = parseInt(event.target.value) ? (event.target.value * 100) : 0;
                    }
                    return plan;
                });
            }
            if (key === 'allowSelfSignup') {
                subscriptionSettings.allowSelfSignup = !subscriptionSettings.allowSelfSignup;
            }
            if (key === 'fromAddress') {
                subscriptionSettings.fromAddress = event.target.value;
            }

            if (key === 'currency') {
                stripeProcessor.config.plans.forEach((plan) => {
                    if (plan.name !== 'Complimentary') {
                        plan.currency = event.value;
                    }
                });

                // NOTE: need to keep Complimentary plans with all available currencies so they don't conflict
                //       when applied to members with existing subscriptions in different currencies (ref. https://stripe.com/docs/billing/customer#currency)
                let currentCurrencyComplimentary = stripeProcessor.config.plans.filter(plan => (plan.currency === event.value && plan.name === 'Complimentary'));

                if (!currentCurrencyComplimentary.length) {
                    let complimentary = {
                        name: 'Complimentary',
                        currency: event.value,
                        interval: 'year',
                        amount: '0'
                    };

                    stripeProcessor.config.plans.push(complimentary);
                }

                stripeProcessor.config.currency = event.value;
            }

            this.setMembersSubscriptionSettings(subscriptionSettings);
        }
    }
});
<commit_msg>🐛 Fixed complimentary plan currency change when yearly currency was changed<commit_after>import Component from '@ember/component';
import {computed} from '@ember/object';
import {reads} from '@ember/object/computed';
import {inject as service} from '@ember/service';
import {set} from '@ember/object';

const US = {flag: '🇺🇸', name: 'US', baseUrl: 'https://api.mailgun.net/v3'};
const EU = {flag: '🇪🇺', name: 'EU', baseUrl: 'https://api.eu.mailgun.net/v3'};

const CURRENCIES = [
    {
        label: 'USD - US Dollar', value: 'usd'
    },
    {
        label: 'AUD - Australian Dollar', value: 'aud'
    },
    {
        label: 'CAD - Canadian Dollar', value: 'cad'
    },
    {
        label: 'EUR - Euro', value: 'eur'
    },
    {
        label: 'GBP - British Pound', value: 'gbp'
    }
];

export default Component.extend({
    feature: service(),
    config: service(),
    mediaQueries: service(),

    currencies: null,

    // passed in actions
    setMembersSubscriptionSettings() {},

    defaultContentVisibility: reads('settings.defaultContentVisibility'),

    selectedCurrency: computed('subscriptionSettings.stripeConfig.plans.monthly.currency', function () {
        return CURRENCIES.findBy('value', this.get('subscriptionSettings.stripeConfig.plans.monthly.currency'));
    }),

    mailgunRegion: computed('settings.bulkEmailSettings.baseUrl', function () {
        if (!this.settings.get('bulkEmailSettings.baseUrl')) {
            return US;
        }

        return [US, EU].find((region) => {
            return region.baseUrl === this.settings.get('bulkEmailSettings.baseUrl');
        });
    }),

    blogDomain: computed('config.blogDomain', function () {
        let domain = this.config.blogDomain || '';
        const host = domain.replace('https://', '').replace('http://', '').split('/');
        return (host && host[0]) || '';
    }),

    subscriptionSettings: computed('settings.membersSubscriptionSettings', function () {
        let subscriptionSettings = this.settings.parseSubscriptionSettings(this.get('settings.membersSubscriptionSettings'));
        let stripeProcessor = subscriptionSettings.paymentProcessors.find((proc) => {
            return (proc.adapter === 'stripe');
        });
        let monthlyPlan = stripeProcessor.config.plans.find(plan => plan.interval === 'month');
        let yearlyPlan = stripeProcessor.config.plans.find(plan => plan.interval === 'year' && plan.name !== 'Complimentary');

        // NOTE: need to be careful about division by zero if we introduce zero decimal currencies
        //       ref.: https://stripe.com/docs/currencies#zero-decimal
        monthlyPlan.amount = parseInt(monthlyPlan.amount) ? (monthlyPlan.amount / 100) : 0;
        yearlyPlan.amount = parseInt(yearlyPlan.amount) ? (yearlyPlan.amount / 100) : 0;

        stripeProcessor.config.plans = {
            monthly: monthlyPlan,
            yearly: yearlyPlan
        };
        subscriptionSettings.stripeConfig = stripeProcessor.config;
        subscriptionSettings.allowSelfSignup = !!subscriptionSettings.allowSelfSignup;
        subscriptionSettings.fromAddress = subscriptionSettings.fromAddress || '';

        return subscriptionSettings;
    }),

    bulkEmailSettings: computed('settings.bulkEmailSettings', function () {
        let bulkEmailSettings = this.get('settings.bulkEmailSettings') || {};
        const {apiKey = '', baseUrl = US.baseUrl, domain = ''} = bulkEmailSettings;
        return {apiKey, baseUrl, domain};
    }),

    hasBulkEmailConfig: computed('settings.bulkEmailSettings', function () {
        let bulkEmailSettings = this.get('settings.bulkEmailSettings');
        return !!bulkEmailSettings.isConfig;
    }),

    init() {
        this._super(...arguments);
        this.set('mailgunRegions', [US, EU]);
        this.set('currencies', CURRENCIES);
    },

    actions: {
        setDefaultContentVisibility(value) {
            this.setDefaultContentVisibility(value);
        },

        setBulkEmailSettings(key, event) {
            let bulkEmailSettings = this.get('settings.bulkEmailSettings') || {};
            bulkEmailSettings[key] = event.target.value;
            if (!bulkEmailSettings.baseUrl) {
                set(bulkEmailSettings, 'baseUrl', US.baseUrl);
            }
            this.setBulkEmailSettings(bulkEmailSettings);
        },

        setBulkEmailRegion(region) {
            let bulkEmailSettings = this.get('settings.bulkEmailSettings') || {};
            set(bulkEmailSettings, 'baseUrl', region.baseUrl);
            this.setBulkEmailSettings(bulkEmailSettings);
        },

        setSubscriptionSettings(key, event) {
            let subscriptionSettings = this.settings.parseSubscriptionSettings(this.get('settings.membersSubscriptionSettings'));
            let stripeProcessor = subscriptionSettings.paymentProcessors.find((proc) => {
                return (proc.adapter === 'stripe');
            });
            let stripeConfig = stripeProcessor.config;
            stripeConfig.product = {
                name: this.settings.get('title')
            };
            // TODO: this flag has to be removed as it doesn't serve any purpose
            if (key === 'isPaid') {
                subscriptionSettings.isPaid = event;
            }
            if (key === 'secret_token' || key === 'public_token') {
                stripeConfig[key] = event.target.value;
            }
            if (key === 'month' || key === 'year') {
                stripeConfig.plans = stripeConfig.plans.map((plan) => {
                    if (key === plan.interval && plan.name !== 'Complimentary') {
                        plan.amount = parseInt(event.target.value) ? (event.target.value * 100) : 0;
                    }
                    return plan;
                });
            }
            if (key === 'allowSelfSignup') {
                subscriptionSettings.allowSelfSignup = !subscriptionSettings.allowSelfSignup;
            }
            if (key === 'fromAddress') {
                subscriptionSettings.fromAddress = event.target.value;
            }

            if (key === 'currency') {
                stripeProcessor.config.plans.forEach((plan) => {
                    if (plan.name !== 'Complimentary') {
                        plan.currency = event.value;
                    }
                });

                // NOTE: need to keep Complimentary plans with all available currencies so they don't conflict
                //       when applied to members with existing subscriptions in different currencies (ref. https://stripe.com/docs/billing/customer#currency)
                let currentCurrencyComplimentary = stripeProcessor.config.plans.filter(plan => (plan.currency === event.value && plan.name === 'Complimentary'));

                if (!currentCurrencyComplimentary.length) {
                    let complimentary = {
                        name: 'Complimentary',
                        currency: event.value,
                        interval: 'year',
                        amount: '0'
                    };

                    stripeProcessor.config.plans.push(complimentary);
                }

                stripeProcessor.config.currency = event.value;
            }

            this.setMembersSubscriptionSettings(subscriptionSettings);
        }
    }
});
<|endoftext|>