<commit_before>const promisify = require('es6-promisify');
const errors = require('feathers-errors');
const SchoolModel = require('../../school/model');
const UserModel = require('../../user/model');
const CourseModel = require('../../user-group/model').courseModel;
const ClassModel = require('../../user-group/model').classModel;
const aws = require('aws-sdk');
const fs = require('fs');
const path = require('path');
const logger = require('winston');
let awsConfig;
try {
	awsConfig = require("../../../../config/secrets.json").aws;
} catch (e) {
	logger.log('warn', 'The AWS config couldn\'t be read');
	awsConfig = {};
}

const AbstractFileStorageStrategy = require('./interface.js');

/**
 * verifies whether the given userId has permission for the given context (course, user, class)
 * @param userId [String]
 * @param storageContext [String], e.g. users/{userId}
 * @returns {*}
 */
const verifyStorageContext = (userId, storageContext) => {
	var values = storageContext.split("/");
	if (values.length < 2) return Promise.reject(new errors.BadRequest("StorageContext is invalid"));
	var context = values[0];
	switch (context) {
		case 'users':
			return UserModel.findById(values[1]).exec()
				.then(res => {
					if (!res || res._id != userId.toString()) {
						return Promise.reject(new errors.Forbidden("You don't have permissions!"));
					}
					return Promise.resolve(res);
				});
		case 'courses':
			// checks, a) whether the user is student or teacher of the course, b) the course exists
			return CourseModel.find({$and: [
				{$or:[{userIds: userId}, {teacherIds: userId}]},
				{_id: values[1]}
			]}).exec().then(res => {
				if (!res || res.length <= 0) {
					return Promise.reject(new errors.Forbidden("You don't have permissions!"));
				}
				return Promise.resolve(res);
			});
		case 'classes':
			// checks, a) whether the user is student or teacher of the class, b) the class exists
			return ClassModel.find({$and: [
				{$or:[{userIds: userId}, {teacherIds: userId}]},
				{_id: values[1]}
			]}).exec().then(res => {
				if (!res || res.length <= 0) {
					return Promise.reject(new errors.Forbidden("You don't have permissions!"));
				}
				return Promise.resolve(res);
			});
		default:
			return Promise.reject("StorageContext is invalid");
	}
};

const createAWSObject = (schoolId) => {
	if(!awsConfig.endpointUrl) throw new Error('AWS integration is not configured on the server');
	var config = new aws.Config(awsConfig);
	config.endpoint = new aws.Endpoint(awsConfig.endpointUrl);
	let bucketName = `bucket-${schoolId}`;
	var s3 = new aws.S3(config);
	return {s3: s3, bucket: bucketName};
};

/**
 * split files-list in files, that are in current directory, and the sub-directories
 * @param data is the files-list
 */
const splitFilesAndDirectories = (storageContext, data) => {
	let files = [];
	let directories = [];

	// gets name of current directory
	let values = storageContext.split("/").filter((v, index) => {
		return v && index > 1;
	});
	let currentDir = values.join("/");
	let currentDirRegEx = new RegExp("^(\/|)" + currentDir + '(\/|)', "i");

	data.forEach(entry => {
		if(!entry.path.match(currentDirRegEx)) return;
		const relativePath = entry.path.replace(currentDirRegEx, '');

		if(relativePath === '') {
			files.push(entry);
		} else {
			directories.push(relativePath.split("/")[0]);
		}
	});

	// delete duplicates in directories
	let withoutDuplicates = [];
	directories.forEach(d => {
		if (withoutDuplicates.indexOf(d) == -1) withoutDuplicates.push(d);
	});

	// remove .scfake fake file
	files = files.filter(f => f.name != ".scfake");

	return {
		files: files,
		directories: withoutDuplicates.map(v => {
			return {
				name: v
			};
		})
	};
};

const getFileMetadata = (storageContext, awsObjects, bucketName, s3) => {
	const headObject = promisify(s3.headObject, s3);
	const _getPath = (path) => {
		if (!path) {
			return "/";
		}

		// remove first and second directory from storageContext because it's just meta
		return `/${path.split("/").filter((v, index) => index > 1).join("/")}`;
	};

	const _getFileName = (path) => {
		if (!path) {
			return "";
		}

		// a file's name is in the last part of the path
		let values = path.split("/");
		return values[values.length - 1];
	};

	return Promise.all(awsObjects.map((object) => {

		return headObject({Bucket: bucketName, Key: object.Key})
			.then(res => {
				return {
					key: object.Key,
					name: _getFileName(object.Key),
					path: _getPath(res.Metadata.path),
					lastModified: res.LastModified,
					size: res.ContentLength,
					type: res.ContentType,
					thumbnail: res.Metadata.thumbnail
				};
		});
	}))
		.then(data => {
			return splitFilesAndDirectories(storageContext, data);
		});
};

class AWSS3Strategy extends AbstractFileStorageStrategy {

	create(schoolId) {
		if (!schoolId) return Promise.reject(new errors.BadRequest('No school id parameter given'));
		return SchoolModel.findById(schoolId).exec()
			.then((result) => {
				if (!result) return Promise.reject(new errors.NotFound('school not found'));
				const awsObject = createAWSObject(result._id);
				const createBucket = promisify(awsObject.s3.createBucket, awsObject.s3);
				return createBucket({Bucket: awsObject.bucket})
					.then(res => Promise.resolve({message: "Successfully created s3-bucket!", data: res}));
			});
	}

	getFiles(userId, storageContext) {
		if (!userId || !storageContext) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => UserModel.findById(userId).exec())
			.then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));

				var awsObject = createAWSObject(result.schoolId);
				const params = {
					Bucket: awsObject.bucket,
					Prefix: storageContext
				};
				return promisify(awsObject.s3.listObjectsV2, awsObject.s3)(params)
					.then(res => {
						return Promise.resolve(getFileMetadata(storageContext, res.Contents, awsObject.bucket, awsObject.s3));
					});
			});
	}

	deleteFile(userId, storageContext, fileName) {
		if (!userId || !storageContext || !fileName) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => UserModel.findById(userId).exec())
			.then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));
				const awsObject = createAWSObject(result.schoolId);
				const params = {
					Bucket: awsObject.bucket,
					Delete: {
						Objects: [
							{
								Key: `${storageContext}/${fileName}`
							}
						],
						Quiet: true
					}
				};
				return promisify(awsObject.s3.deleteObjects, awsObject.s3)(params);
			});
	}

	generateSignedUrl(userId, storageContext, fileName, fileType, action) {
		if (!userId || !storageContext || !fileName || !action || (action == 'putObject' && !fileType)) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => {
			return UserModel.findById(userId).exec().then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));

				const awsObject = createAWSObject(result.schoolId);
				let params = {
					Bucket: awsObject.bucket,
					Key: `${storageContext}/${fileName}`,
					Expires: 60
				};

				if(action == 'putObject') params.ContentType = fileType;

				return promisify(awsObject.s3.getSignedUrl, awsObject.s3)(action, params)
					.then(res => Promise.resolve({
						url: res,
						header: {
							"Content-Type": fileType,
							"x-amz-meta-path": storageContext,
							"x-amz-meta-name": fileName,
							"x-amz-meta-thumbnail": "https://schulcloud.org/images/login-right.png"
						}
					}));
			});
		});
	}

	createDirectory(userId, storageContext, dirName) {
		if (!userId || !storageContext || !dirName) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => {
				return UserModel.findById(userId).exec().then(result => {
					if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));

					const awsObject = createAWSObject(result.schoolId);
					const dirPath = `${storageContext}/${dirName}`;
					var fileStream = fs.createReadStream(path.join(__dirname, '..', 'resources', '.scfake'));
					let params = {
						Bucket: awsObject.bucket,
						Key: `${dirPath}/.scfake`,
						Body: fileStream,
						Metadata: {
							path: dirPath,
							name: '.scfake'
						}
					};

					return promisify(awsObject.s3.putObject, awsObject.s3)(params);
				});
			});
	}

	deleteDirectory(userId, storageContext) {
		if (!userId || !storageContext) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => UserModel.findById(userId).exec())
			.then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));
				const awsObject = createAWSObject(result.schoolId);
				const s3 = awsObject.s3;
				const params = {
					Bucket: awsObject.bucket,
					Prefix: storageContext
				};
				return this._deleteAllInDirectory(awsObject, params);
			});
	}

	_deleteAllInDirectory(awsObject, params) {
		return promisify(awsObject.s3.listObjectsV2, awsObject.s3)(params)
			.then(data => {
				if (data.Contents.length == 0) return Promise.resolve();

				const deleteParams = {Bucket: params.Bucket, Delete: {}};
				deleteParams.Delete.Objects = data.Contents;

				return promisify(awsObject.s3.deleteObjects, awsObject.s3)(deleteParams);
			})
			.then(deletionData => {
				if (deletionData.Deleted.length == 1000) return this._deleteAllInDirectory(awsObject, params);	// AWS S3 returns only 1000 items at once
				else return Promise.resolve(deletionData);
			});
	}
}

module.exports = AWSS3Strategy;
<commit_msg>Fixes for AWS SDK interaction<commit_after>const promisify = require('es6-promisify');
const errors = require('feathers-errors');
const SchoolModel = require('../../school/model');
const UserModel = require('../../user/model');
const CourseModel = require('../../user-group/model').courseModel;
const ClassModel = require('../../user-group/model').classModel;
const aws = require('aws-sdk');
const fs = require('fs');
const path = require('path');
const logger = require('winston');
let awsConfig;
try {
	awsConfig = require("../../../../config/secrets.json").aws;
} catch (e) {
	logger.log('warn', 'The AWS config couldn\'t be read');
	awsConfig = {};
}

const AbstractFileStorageStrategy = require('./interface.js');

/**
 * verifies whether the given userId has permission for the given context (course, user, class)
 * @param userId [String]
 * @param storageContext [String], e.g. users/{userId}
 * @returns {*}
 */
const verifyStorageContext = (userId, storageContext) => {
	var values = storageContext.split("/");
	if (values.length < 2) return Promise.reject(new errors.BadRequest("StorageContext is invalid"));
	var context = values[0];
	switch (context) {
		case 'users':
			return UserModel.findById(values[1]).exec()
				.then(res => {
					if (!res || res._id != userId.toString()) {
						return Promise.reject(new errors.Forbidden("You don't have permissions!"));
					}
					return Promise.resolve(res);
				});
		case 'courses':
			// checks, a) whether the user is student or teacher of the course, b) the course exists
			return CourseModel.find({$and: [
				{$or:[{userIds: userId}, {teacherIds: userId}]},
				{_id: values[1]}
			]}).exec().then(res => {
				if (!res || res.length <= 0) {
					return Promise.reject(new errors.Forbidden("You don't have permissions!"));
				}
				return Promise.resolve(res);
			});
		case 'classes':
			// checks, a) whether the user is student or teacher of the class, b) the class exists
			return ClassModel.find({$and: [
				{$or:[{userIds: userId}, {teacherIds: userId}]},
				{_id: values[1]}
			]}).exec().then(res => {
				if (!res || res.length <= 0) {
					return Promise.reject(new errors.Forbidden("You don't have permissions!"));
				}
				return Promise.resolve(res);
			});
		default:
			return Promise.reject("StorageContext is invalid");
	}
};

const createAWSObject = (schoolId) => {
	if(!awsConfig.endpointUrl) throw new Error('AWS integration is not configured on the server');
	var config = new aws.Config(awsConfig);
	config.endpoint = new aws.Endpoint(awsConfig.endpointUrl);
	let bucketName = `bucket-${schoolId}`;
	var s3 = new aws.S3(config);
	return {s3: s3, bucket: bucketName};
};

/**
 * split files-list in files, that are in current directory, and the sub-directories
 * @param data is the files-list
 */
const splitFilesAndDirectories = (storageContext, data) => {
	let files = [];
	let directories = [];

	// gets name of current directory
	let values = storageContext.split("/").filter((v, index) => {
		return v && index > 1;
	});
	let currentDir = values.join("/");
	let currentDirRegEx = new RegExp("^(\/|)" + currentDir + '(\/|)', "i");

	data.forEach(entry => {
		if(!entry.path.match(currentDirRegEx)) return;
		const relativePath = entry.path.replace(currentDirRegEx, '');

		if(relativePath === '') {
			files.push(entry);
		} else {
			directories.push(relativePath.split("/")[0]);
		}
	});

	// delete duplicates in directories
	let withoutDuplicates = [];
	directories.forEach(d => {
		if (withoutDuplicates.indexOf(d) == -1) withoutDuplicates.push(d);
	});

	// remove .scfake fake file
	files = files.filter(f => f.name != ".scfake");

	return {
		files: files,
		directories: withoutDuplicates.map(v => {
			return {
				name: v
			};
		})
	};
};

const getFileMetadata = (storageContext, awsObjects, bucketName, s3) => {
	const headObject = promisify(s3.headObject, s3);
	const _getPath = (path) => {
		if (!path) {
			return "/";
		}

		// remove first and second directory from storageContext because it's just meta
		return `/${path.split("/").filter((v, index) => index > 1).join("/")}`;
	};

	const _getFileName = (path) => {
		if (!path) {
			return "";
		}

		// a file's name is in the last part of the path
		let values = path.split("/");
		return values[values.length - 1];
	};

	return Promise.all(awsObjects.map((object) => {

		return headObject({Bucket: bucketName, Key: object.Key})
			.then(res => {
				return {
					key: object.Key,
					name: _getFileName(object.Key),
					path: _getPath(res.Metadata.path),
					lastModified: res.LastModified,
					size: res.ContentLength,
					type: res.ContentType,
					thumbnail: res.Metadata.thumbnail
				};
		});
	}))
		.then(data => {
			return splitFilesAndDirectories(storageContext, data);
		});
};

class AWSS3Strategy extends AbstractFileStorageStrategy {

	create(schoolId) {
		if (!schoolId) return Promise.reject(new errors.BadRequest('No school id parameter given'));
		return SchoolModel.findById(schoolId).exec()
			.then((result) => {
				if (!result) return Promise.reject(new errors.NotFound('school not found'));
				const awsObject = createAWSObject(result._id);
				const createBucket = promisify(awsObject.s3.createBucket, awsObject.s3);
				return createBucket({Bucket: awsObject.bucket})
					.then(res => Promise.resolve({message: "Successfully created s3-bucket!", data: res}));
			});
	}

	getFiles(userId, storageContext) {
		if (!userId || !storageContext) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => UserModel.findById(userId).exec())
			.then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));

				var awsObject = createAWSObject(result.schoolId);
				const params = {
					Bucket: awsObject.bucket,
					Prefix: storageContext
				};
				return promisify(awsObject.s3.listObjectsV2, awsObject.s3)(params)
					.then(res => {
						return Promise.resolve(getFileMetadata(storageContext, res.Contents, awsObject.bucket, awsObject.s3));
					});
			});
	}

	deleteFile(userId, storageContext, fileName) {
		if (!userId || !storageContext || !fileName) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => UserModel.findById(userId).exec())
			.then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));
				const awsObject = createAWSObject(result.schoolId);
				const params = {
					Bucket: awsObject.bucket,
					Delete: {
						Objects: [
							{
								Key: `${storageContext}/${fileName}`
							}
						],
						Quiet: true
					}
				};
				return promisify(awsObject.s3.deleteObjects, awsObject.s3)(params);
			});
	}

	generateSignedUrl(userId, storageContext, fileName, fileType, action) {
		if (!userId || !storageContext || !fileName || !action || (action == 'putObject' && !fileType)) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => {
			return UserModel.findById(userId).exec().then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));

				const awsObject = createAWSObject(result.schoolId);
				let params = {
					Bucket: awsObject.bucket,
					Key: `${storageContext}/${fileName}`,
					Expires: 60
				};

				if(action == 'putObject') params.ContentType = fileType;

				return promisify(awsObject.s3.getSignedUrl, awsObject.s3)(action, params)
					.then(res => Promise.resolve({
						url: res,
						header: {
							"Content-Type": fileType,
							"x-amz-meta-path": storageContext,
							"x-amz-meta-name": fileName,
							"x-amz-meta-thumbnail": "https://schulcloud.org/images/login-right.png"
						}
					}));
			});
		});
	}

	createDirectory(userId, storageContext, dirName) {
		if (!userId || !storageContext || !dirName) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => {
				return UserModel.findById(userId).exec().then(result => {
					if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));

					const awsObject = createAWSObject(result.schoolId);
					const dirPath = `${storageContext}/${dirName}`;
					var fileStream = fs.createReadStream(path.join(__dirname, '..', 'resources', '.scfake'));
					let params = {
						Bucket: awsObject.bucket,
						Key: `${dirPath}/.scfake`,
						Body: fileStream,
						Metadata: {
							path: dirPath,
							name: '.scfake'
						}
					};

					return promisify(awsObject.s3.putObject, awsObject.s3)(params);
				});
			});
	}

	deleteDirectory(userId, storageContext, dirName) {
		if (!userId || !storageContext || !dirName) return Promise.reject(new errors.BadRequest('Missing parameters'));
		return verifyStorageContext(userId, storageContext)
			.then(res => UserModel.findById(userId).exec())
			.then(result => {
				if (!result || !result.schoolId) return Promise.reject(errors.NotFound("User not found"));
				const awsObject = createAWSObject(result.schoolId);
				const s3 = awsObject.s3;
				const params = {
					Bucket: awsObject.bucket,
					Prefix: `${storageContext}/${dirName}`
				};
				return this._deleteAllInDirectory(awsObject, params);
			});
	}

	_deleteAllInDirectory(awsObject, params) {
		return promisify(awsObject.s3.listObjectsV2, awsObject.s3)(params)
			.then(data => {
				if (data.Contents.length == 0) return Promise.resolve();

				const deleteParams = {Bucket: params.Bucket, Delete: {}};
				deleteParams.Delete.Objects = data.Contents.map(c => ({Key: c.Key}));

				return promisify(awsObject.s3.deleteObjects, awsObject.s3)(deleteParams);
			})
			.then(deletionData => {
				if (deletionData.Deleted.length == 1000) return this._deleteAllInDirectory(awsObject, params);	// AWS S3 returns only 1000 items at once
				else return Promise.resolve(deletionData);
			});
	}
}

module.exports = AWSS3Strategy;
<|endoftext|>


<commit_before>'use strict';

/* Controllers */

angular.module('monocleApp.controllers', [])
  .controller('accountsController', ['$scope', '$location', '$interval', 'monocleAPIservice', function($scope, $location, $interval, monocleAPIservice) {
      $scope.emailFilter = null;
      $scope.errorFilter = null;
      $scope.providerFilter = null;
      $scope.stateFilter = null;
      $scope.accountsList = [];
      $scope.state_count = {};
      $scope.provider_count = {};
      $scope.predicate = 'id';

      $scope.sort = function (predicate) {
        if($scope.predicate == predicate) {
          $scope.reverse = !$scope.reverse;
        } else {
          $scope.reverse = false;
          $scope.predicate = predicate;
        }

      };

      $scope.select_account = function ( account_id ) {
            $location.url( "/account/" + account_id );
      };

      monocleAPIservice.getAccounts().success(function (response) {
          $scope.accountsList = response;
          var i = 0;
          var state_count = {}
          var provider_count = {}
          for(i = 0; i < response.length; i++) {
            if(typeof state_count[response[i].state] == 'undefined')
              state_count[response[i].state] = 0;

            state_count[response[i].state] += 1;

            if(typeof provider_count[response[i].provider] == 'undefined')
              provider_count[response[i].provider] = 0;

            provider_count[response[i].provider] += 1;

          }

          $scope.state_count = state_count;
          $scope.provider_count = provider_count;

      });

      $scope.filterState = function (state) {
        $scope.stateFilter = state;
      }

      $scope.filterProvider = function (provider) {
        $scope.providerFilter = provider;
      }

      $scope.accountFilter = function (account) {
            var keyword = new RegExp($scope.emailFilter, 'i');
            var error_keyword = new RegExp($scope.errorFilter, 'i');
            return (!$scope.emailFilter || keyword.test(account.email)) &&
               (!$scope.providerFilter || account.provider == $scope.providerFilter) &&
               (!$scope.stateFilter || account.state == $scope.stateFilter) &&
               (!$scope.errorFilter || keyword.test(account.sync_error));
      };

      $interval(monocleAPIservice.getAccounts, 3000);
  }])
  .controller('accountController', ['$scope', '$interval', 'monocleAPIservice', '$routeParams', function($scope, $interval, monocleAPIservice, $routeParams) {
      $scope.account = {};
      $scope.folders = []
      $scope.id = $routeParams.id;

      monocleAPIservice.getAccountDetails($scope.id).success(function (response) {
          $scope.account = response.account;
          $scope.folders = response.folders;
      });

      $scope.refreshDetails = function() {
        monocleAPIservice.getAccountDetails($scope.id).success(function (response) {
          $scope.account = response.account;
          $scope.folders = response.folders;
        });
      }

      $interval($scope.refreshDetails, 3000);

  }]);
<commit_msg>[monocle] Fix error filter.<commit_after>'use strict';

/* Controllers */

angular.module('monocleApp.controllers', [])
  .controller('accountsController', ['$scope', '$location', '$interval', 'monocleAPIservice', function($scope, $location, $interval, monocleAPIservice) {
      $scope.emailFilter = null;
      $scope.errorFilter = null;
      $scope.providerFilter = null;
      $scope.stateFilter = null;
      $scope.accountsList = [];
      $scope.state_count = {};
      $scope.provider_count = {};
      $scope.predicate = 'id';

      $scope.sort = function (predicate) {
        if($scope.predicate == predicate) {
          $scope.reverse = !$scope.reverse;
        } else {
          $scope.reverse = false;
          $scope.predicate = predicate;
        }

      };

      $scope.select_account = function ( account_id ) {
            $location.url( "/account/" + account_id );
      };

      monocleAPIservice.getAccounts().success(function (response) {
          $scope.accountsList = response;
          var i = 0;
          var state_count = {}
          var provider_count = {}
          for(i = 0; i < response.length; i++) {
            if(typeof state_count[response[i].state] == 'undefined')
              state_count[response[i].state] = 0;

            state_count[response[i].state] += 1;

            if(typeof provider_count[response[i].provider] == 'undefined')
              provider_count[response[i].provider] = 0;

            provider_count[response[i].provider] += 1;

          }

          $scope.state_count = state_count;
          $scope.provider_count = provider_count;

      });

      $scope.filterState = function (state) {
        $scope.stateFilter = state;
      }

      $scope.filterProvider = function (provider) {
        $scope.providerFilter = provider;
      }

      $scope.accountFilter = function (account) {
            var keyword = new RegExp($scope.emailFilter, 'i');
            var error_keyword = new RegExp($scope.errorFilter, 'i');
            return (!$scope.emailFilter || keyword.test(account.email)) &&
               (!$scope.providerFilter || account.provider == $scope.providerFilter) &&
               (!$scope.stateFilter || account.state == $scope.stateFilter) &&
               (!$scope.errorFilter || error_keyword.test(account.sync_error));
      };

      $interval(monocleAPIservice.getAccounts, 3000);
  }])
  .controller('accountController', ['$scope', '$interval', 'monocleAPIservice', '$routeParams', function($scope, $interval, monocleAPIservice, $routeParams) {
      $scope.account = {};
      $scope.folders = []
      $scope.id = $routeParams.id;

      monocleAPIservice.getAccountDetails($scope.id).success(function (response) {
          $scope.account = response.account;
          $scope.folders = response.folders;
      });

      $scope.refreshDetails = function() {
        monocleAPIservice.getAccountDetails($scope.id).success(function (response) {
          $scope.account = response.account;
          $scope.folders = response.folders;
        });
      }

      $interval($scope.refreshDetails, 3000);

  }]);
<|endoftext|>


<commit_before>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/**
 * The ContainerFactory is responsible for manage playback bootstrap and create containers.
 */

var _ = require('underscore');
var BaseObject = require('../../base/base_object');
var Container = require('../container');
var $ = require('jquery');

var ContainerFactory = BaseObject.extend({
  initialize: function(params, loader) {
    this.params = params;
    this.loader = loader;
  },
  createContainers: function() {
    return $.Deferred(function(promise) {
      promise.resolve( _.map(this.params.sources, function(source) {
        return this.createContainer(source);
      }, this)
        );
    }.bind(this));
  },
  findPlaybackPlugin: function(source) {
    return _.find(this.loader.playbackPlugins, function(p) { return p.canPlay(source) }, this);
  },
  createContainer: function(source) {
    var playbackPlugin = this.findPlaybackPlugin(source);
    var playback = new playbackPlugin({src: source, autoPlay: !!this.params.autoPlay});
    var container = new Container({playback: playback});
    var defer = $.Deferred();
    defer.promise(container);
    this.addContainerPlugins(container);
    this.listenTo(container, 'container:ready', function() {
      defer.resolve(container);
    }.bind(this));
    return container;
  },
  addContainerPlugins: function(container) {
    _.each(this.loader.containerPlugins, function(plugin) {
      container.addPlugin(new plugin(_.extend(this.params, {container: container})));
    }, this);
  }
});

module.exports = ContainerFactory;
<commit_msg>container factory: add parameters to playback instantiation<commit_after>// Copyright 2014 Globo.com Player authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

/**
 * The ContainerFactory is responsible for manage playback bootstrap and create containers.
 */

var _ = require('underscore');
var BaseObject = require('../../base/base_object');
var Container = require('../container');
var $ = require('jquery');

var ContainerFactory = BaseObject.extend({
  initialize: function(params, loader) {
    this.params = params;
    this.loader = loader;
  },
  createContainers: function() {
    return $.Deferred(function(promise) {
      promise.resolve( _.map(this.params.sources, function(source) {
        return this.createContainer(source);
      }, this)
        );
    }.bind(this));
  },
  findPlaybackPlugin: function(source) {
    return _.find(this.loader.playbackPlugins, function(p) { return p.canPlay(source) }, this);
  },
  createContainer: function(source) {
    var playbackPlugin = this.findPlaybackPlugin(source);
    var params = _.extend({}, this.params, {src: source, autoPlay: !!this.params.autoPlay});
    var playback = new playbackPlugin(params);
    var container = new Container({playback: playback});
    var defer = $.Deferred();
    defer.promise(container);
    this.addContainerPlugins(container);
    this.listenTo(container, 'container:ready', function() {
      defer.resolve(container);
    }.bind(this));
    return container;
  },
  addContainerPlugins: function(container) {
    _.each(this.loader.containerPlugins, function(plugin) {
      container.addPlugin(new plugin(_.extend(this.params, {container: container})));
    }, this);
  }
});

module.exports = ContainerFactory;
<|endoftext|>


<commit_before>const axios=require('axios');
const Terminal=require('./util').Terminal;
const FGFS_HOST='http://localhost:8123/'
module.exports=class{
    data=undefined;
    running=true;
    constructor(stream,_exit){
        this.terminal=new Terminal(stream,{
            outputEncoding:'shift-jis'
        });
        this._exit=_exit;
        this.terminal.pc98SetBottomLine(false);
        this.terminal.clrscr();
        this.terminal.locate(0,0);
        this.terminal.setcursor(false);
        this.terminal.print('Loading...');
        this.refresh();
    }
    drawFrame(){
        this.terminal.clrscr();
        this.terminal.locate(0,1);
        this.terminal.setattr(46);
        for(let i=0;i<80;i++){
            this.terminal.print(' ');
        }
        this.terminal.locate(Math.round((80-16)/2),1);
        this.terminal.print('飛行制御センター');

        this.terminal.locate(0,25);
        this.terminal.setattr(46);
        for(let i=0;i<80;i++){
            this.terminal.print(' ');
        }
        this.terminal.locate(2,25);
        this.terminal.print('Esc：終了');
        if(this.data){
            this.terminal.print('    s:ｻｳﾝﾄﾞｵﾝ/ｵﾌ    p:一時停止/再開');
        }
        this.terminal.setattr(0);

        //this.terminal.locate(0,25);
        //this.terminal.print('Press \'s\' to toggle sound.');
    }
    async refresh(){
        let data=null;
        try{
            data=await axios({
                method:'GET',
                url:FGFS_HOST+'/json/fgreport'
            });
        }catch(e){
            if(null!==this.data){
                this.data=null;
                this.drawFrame();
                this.terminal.locate(Math.floor((80-22)/2),12);
                this.terminal.print('飛行任務がありません。');
            }
            setTimeout(()=>{
                if(this.running){
                    this.refresh();
                }
            },1000);
            return;
        }
        setTimeout(()=>{
            if(this.running){
                this.refresh();
            }
        },1000);

        try{
            let fgreport={};
            for(let item of data.data.children){
                fgreport[item.name]=item.value;
            }

            if(!this.data){
                this.data=fgreport;
                this.drawFrame();
            }
            fgreport['longitude'] = Math.abs(fgreport['longitude-deg']).toFixed(6)+(fgreport['longitude-deg']>=0 ? 'E' : 'W');
            fgreport['latitude'] = Math.abs(fgreport['latitude-deg']).toFixed(6)+(fgreport['latitude-deg']>=0 ? 'N' : 'S');
            this.terminal.locate(0,3);
            this.terminal.print('機種：    '+fgreport.aircraft+'        ');
            this.terminal.locate(0,4);
            this.terminal.print('経度：    '+fgreport['longitude']+'        ');
            this.terminal.locate(0,5);
            this.terminal.print('緯度：    '+fgreport['latitude']+'        ');
            this.terminal.locate(0,6);
            this.terminal.print('飛行時間：'+fgreport['flight-time-string']+'        ');
            this.terminal.locate(0,7);
            this.terminal.print('残り時間：'+fgreport['ete-string']+'        ');
            this.terminal.locate(0,8);
            this.terminal.print('総距離：  '+fgreport['total-distance'].toFixed(1)+'nm        ');
            this.terminal.locate(0,9);
            this.terminal.print('残り距離：'+fgreport['distance-remaining-nm'].toFixed(1)+'nm        ');
            this.terminal.locate(0,10);
            this.terminal.print('飛行距離：'+(Number(fgreport['total-distance'])-Number(fgreport['distance-remaining-nm'])).toFixed(1)+'nm        ');

            this.terminal.locate(0,24);
            if(fgreport['crashed']){
                this.terminal.setattr(5,41);
                this.terminal.print('墜落しました');
            }else if(fgreport['paused']){
                this.terminal.setattr(21);
                this.terminal.print('一時停止');
            }else{
                this.terminal.setattr(20);
                this.terminal.print('飛行中');
            }
            this.terminal.setattr(0);
            this.terminal.print('                ');
        }catch(e){
            console.error(e);
        }
    }
    destroy(){
        try{
            this.running=false;
            this.terminal.reset();
        }catch(e){
            console.error(e);
        }finally{
            this._exit();
        }
    }
    async toggleSound(){
        let {data}=await axios({
            method:'GET',
            url:FGFS_HOST+'/json/sim/sound/enabled'
        });
        await axios({
            method:'GET',
            url:FGFS_HOST+`/props/sim/sound?submit=set&enabled=${data.value ? 'false' : 'true'}`
        });
    }
    async togglePause(){
        await axios({
            method:'GET',
            url:FGFS_HOST+'/run.cgi?value=pause'
        });
    }
    ondata(data){
        try{
            switch(data[0]){
                case 0x1b:
                    this.destroy();
                    return;
                break;
                case 0x73:
                    this.toggleSound();
                break;
                case 0x70:
                    this.togglePause();
                break;
            }
        }catch(e){
            console.error(e);
        }
    }
}
<commit_msg>Updated<commit_after>const axios=require('axios');
const Terminal=require('./util').Terminal;
const FGFS_HOST='http://localhost:8123/'
module.exports=class{
    data=undefined;
    running=true;
    constructor(stream,_exit){
        this.terminal=new Terminal(stream,{
            outputEncoding:'shift-jis'
        });
        this._exit=_exit;
        this.terminal.pc98SetBottomLine(false);
        this.terminal.clrscr();
        this.terminal.locate(0,0);
        this.terminal.setcursor(false);
        this.terminal.print('Loading...');
        this.refresh();
    }
    drawFrame(){
        this.terminal.clrscr();
        this.terminal.locate(0,1);
        this.terminal.setattr(46);
        for(let i=0;i<80;i++){
            this.terminal.print(' ');
        }
        this.terminal.locate(Math.round((80-16)/2),1);
        this.terminal.print('飛行制御センター');

        this.terminal.locate(0,25);
        this.terminal.setattr(46);
        for(let i=0;i<80;i++){
            this.terminal.print(' ');
        }
        this.terminal.locate(2,25);
        this.terminal.print('Esc：終了');
        if(this.data){
            this.terminal.print('    s:ｻｳﾝﾄﾞｵﾝ/ｵﾌ    p:一時停止/再開');
        }
        this.terminal.setattr(0);

        //this.terminal.locate(0,25);
        //this.terminal.print('Press \'s\' to toggle sound.');
    }
    async refresh(){
        let data=null;
        try{
            data=await axios({
                method:'GET',
                url:FGFS_HOST+'/json/fgreport'
            });
        }catch(e){
            if(null!==this.data){
                this.data=null;
                this.drawFrame();
                this.terminal.locate(Math.floor((80-22)/2),12);
                this.terminal.print('飛行任務がありません。');
            }
            setTimeout(()=>{
                if(this.running){
                    this.refresh();
                }
            },1000);
            return;
        }
        setTimeout(()=>{
            if(this.running){
                this.refresh();
            }
        },1000);

        try{
            let fgreport={};
            for(let item of data.data.children){
                fgreport[item.name]=item.value;
            }

            if(!this.data){
                this.data=fgreport;
                this.drawFrame();
            }
            fgreport['longitude'] = Math.abs(fgreport['longitude-deg']).toFixed(6)+(fgreport['longitude-deg']>=0 ? 'E' : 'W');
            fgreport['latitude'] = Math.abs(fgreport['latitude-deg']).toFixed(6)+(fgreport['latitude-deg']>=0 ? 'N' : 'S');
            this.terminal.locate(0,3);
            this.terminal.print('機種：    '+fgreport.aircraft+'        ');
            this.terminal.locate(0,4);
            this.terminal.print('経度：    '+fgreport['longitude']+'        ');
            this.terminal.locate(0,5);
            this.terminal.print('緯度：    '+fgreport['latitude']+'        ');
            this.terminal.locate(0,6);
            this.terminal.print('飛行時間：'+fgreport['flight-time-string']+'        ');
            this.terminal.locate(0,7);
            this.terminal.print('残り時間：'+fgreport['ete-string']+'        ');
            this.terminal.locate(0,8);
            this.terminal.print('総距離：  '+fgreport['total-distance'].toFixed(1)+'nm        ');
            this.terminal.locate(0,9);
            this.terminal.print('残り距離：'+fgreport['distance-remaining-nm'].toFixed(1)+'nm        ');
            this.terminal.locate(0,10);
            this.terminal.print('飛行距離：'+(Number(fgreport['total-distance'])-Number(fgreport['distance-remaining-nm'])).toFixed(1)+'nm        ');

            this.terminal.locate(0,24);
            if(fgreport['crashed']){
                this.terminal.setattr(5,41);
                this.terminal.print('墜落しました');
            }else if(fgreport['paused']){
                this.terminal.setattr(21);
                this.terminal.print('一時停止');
            }else{
                this.terminal.setattr(20);
                this.terminal.print('飛行中');
            }
            this.terminal.setattr(0);
            this.terminal.print('                ');
        }catch(e){
            console.error(e);
        }
    }
    destroy(){
        try{
            this.running=false;
        }catch(e){
            console.error(e);
        }finally{
            this._exit();
        }
    }
    async toggleSound(){
        let {data}=await axios({
            method:'GET',
            url:FGFS_HOST+'/json/sim/sound/enabled'
        });
        await axios({
            method:'GET',
            url:FGFS_HOST+`/props/sim/sound?submit=set&enabled=${data.value ? 'false' : 'true'}`
        });
    }
    async togglePause(){
        await axios({
            method:'GET',
            url:FGFS_HOST+'/run.cgi?value=pause'
        });
    }
    ondata(data){
        try{
            switch(data[0]){
                case 0x1b:
                    this.terminal.reset();
                    this.destroy();
                    return;
                break;
                case 0x73:
                    this.toggleSound();
                break;
                case 0x70:
                    this.togglePause();
                break;
            }
        }catch(e){
            console.error(e);
        }
    }
}
<|endoftext|>


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

import {loadScript} from '../3p/3p';
import {validateData} from '../3p/3p';

/**
 * @param {!Window} global
 * @param {!Object} data
 */
export function capirs(global, data) {
  validateData(data, ['begunAutoPad', 'begunBlockId']);

  if (data['customCss']) {
    const style = window.document.createElement('style');
    if (style.styleSheet) {
      style.styleSheet.cssText = data['customCss'];
    } else {
      style.appendChild(document.createTextNode(data['customCss']));
    }
    global.document.body.appendChild(style);
  }

  global['begun_callbacks'] = {
    lib: {
      init: () => {
        const block = global.document.createElement('div');
        block.id = 'x-' + Math.round(Math.random() * 1e8).toString(36);
        document.body.appendChild(block);

        global['Adf']['banner']['ssp'](block.id, data['params'], {
          'begun-auto-pad': data['begunAutoPad'],
          'begun-block-id': data['begunBlockId'],
        });
      },
    },
    block: {
      draw: feed => {
        const banner = feed['banners']['graph'][0];
        window.context.renderStart({
          width: banner['width'],
          height: banner['height'],
        });
        const reportId = 'capirs-' + banner['banner_id'];
        window.context.reportRenderedEntityIdentifier(reportId);
      },
      unexist: window.context.noContentAvailable,
    },
  };

  loadScript(global, '//ssp.rambler.ru/lpdid.js');
  loadScript(global, '//ssp.rambler.ru/capirs_async.js');
}
<commit_msg>amp-ad: Fix capirs ad initialisation 🐛 (#13988)<commit_after>/**
 * Copyright 2015 The AMP HTML Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS-IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import {loadScript} from '../3p/3p';
import {validateData} from '../3p/3p';

/**
 * @param {!Window} global
 * @param {!Object} data
 */
export function capirs(global, data) {
  validateData(data, ['begunAutoPad', 'begunBlockId']);

  if (data['customCss']) {
    const style = global.document.createElement('style');

    if (style.styleSheet) {
      style.styleSheet.cssText = data['customCss'];
    } else {
      style.appendChild(global.document.createTextNode(data['customCss']));
    }

    global.document.getElementById('c').appendChild(style);
  }

  global['begun_callbacks'] = {
    lib: {
      init: () => {
        const block = global.document.createElement('div');
        block.id = 'x-' + Math.round(Math.random() * 1e8).toString(36);

        global.document.getElementById('c').appendChild(block);

        global['Adf']['banner']['ssp'](block.id, data['params'], {
          'begun-auto-pad': data['begunAutoPad'],
          'begun-block-id': data['begunBlockId'],
        });
      },
    },
    block: {
      draw: feed => {
        const banner = feed['banners']['graph'][0];

        global.context.renderStart({
          width: getWidth(global, banner),
          height: banner.height,
        });

        const reportId = 'capirs-' + banner['banner_id'];
        global.context.reportRenderedEntityIdentifier(reportId);
      },
      unexist: global.context.noContentAvailable,
    },
  };

  loadScript(global, '//ssp.rambler.ru/lpdid.js');
  loadScript(global, '//ssp.rambler.ru/capirs_async.js');
}

/**
 * @param {!Window} global
 * @param {!Object} banner
 */
function getWidth(global, banner) {
  let width;

  if (isResponsiveAd(banner)) {
    width = Math.max(
        global.document.documentElement./*OK*/clientWidth,
        global.window./*OK*/innerWidth || 0
    );
  } else {
    width = banner.width;
  }

  return width;
}

/**
 * @param {!Object} banner
 * @return {boolean}
 */
function isResponsiveAd(banner) {
  return banner.width.indexOf('%') !== -1;
}
<|endoftext|>


<commit_before>import getFurnitureAlternatives from './get-furniture-alternatives.js'
import getSceneStructureFromAframeElements from '../scene/structure/from-aframe-elements.js'
import getAframeElementsFromSceneStructure from '../scene/structure/to-aframe-elements.js'
import normalizeSceneStructure from '../scene/structure/normalize.js'
import defaults from 'lodash/defaults'
import Promise from 'bluebird'

// consumes sceneStructure or DOM elements
// replaces furniture Ids and adjusts positioning
// outputs input type
export default function replaceFurniture(input, options) {
  options = options || {}
  var query = options.query
  // defaults to pick a random item from alternatives
  var random = options.random || true
  var furnitureIds

  // check for DOM element
  var isDomElement = isElement(input)
  if (isDomElement) {
    // convert to sceneStructure
    input = getSceneStructureFromAframeElements(input)
  }

  return normalizeSceneStructure(input)
    .then(function(sceneStructure) {
      furnitureIds = getIdsFromSceneStructure(sceneStructure)

      if (Object.keys(furnitureIds).length === 0)
        return Promise.reject('No valid furniture elements were found')

      var promises = []
      Object.keys(furnitureIds).forEach(function(id) {
        promises.push(getFurnitureAlternatives(id, options))
      })
      return Promise.all(promises)
    })
    .then(function(result) {
      var alternatives = {}
      Object.keys(furnitureIds).forEach(function(id, index) {
        alternatives[id] = result[index]
      })

      // replace params in furniture elements
      var sceneStructure = updateSceneStructureWithResult( input, alternatives, random)

      if (isDomElement) {
        return getAframeElementsFromSceneStructure(sceneStructure)
      } else return sceneStructure
    })
    .catch(function(error) {
      console.error(error)
      return Promise.reject(error)
    })
}

function getIdsFromSceneStructure(sceneStructure) {
  var isArray = Array.isArray(sceneStructure)
  sceneStructure = isArray ? sceneStructure : [sceneStructure]

  var collection = {}
  sceneStructure.forEach(function(element3d) {
    // get all furniture elements = type: 'interior'
    if (
      element3d.type === 'interior' &&
      element3d.src &&
      typeof element3d.src === 'string'
    )
      collection[element3d.src.substring(1)] = true
    // recursively search through scene structure
    if (element3d.children && element3d.children.length) {
      collection = defaults({}, collection, getIdsFromSceneStructure (element3d.children))
    }
  })
  return collection
}

// Returns true if it is a DOM element
// https://stackoverflow.com/a/384380/2835973
function isElement(o) {
  return typeof HTMLElement === 'object'
    ? o instanceof HTMLElement //DOM2
    : o &&
      typeof o === 'object' &&
      o !== null &&
      o.nodeType === 1 &&
      typeof o.nodeName === 'string'
}

function updateSceneStructureWithResult(input, alternatives, random) {
  var sceneStructure = input,
    replacement,
    index = 0

  Object.keys(alternatives).forEach(function(id) {
    if (!alternatives[id] || !alternatives[id].length) return
    // we the pick a random item or take the first one
    if (random) index = Math.floor(Math.random() * alternatives[id].length)

    replacement = alternatives[id][index]

    sceneStructure = updateElementsById(sceneStructure, id, replacement)
  })
  return sceneStructure
}

// search by furniture id and replace params
function updateElementsById(sceneStructure, id, replacement) {
  var isArray = Array.isArray(sceneStructure)
  sceneStructure = isArray ? sceneStructure : [sceneStructure]

  sceneStructure = sceneStructure.map(function(element3d) {
    // furniture id is stored in src param
    if (
      element3d.type === 'interior' &&
      element3d.src.substring(1) === id &&
      replacement.furniture
    ) {
      // apply new id
      element3d.src = '!' + replacement.furniture.id
      // compute new position for items that differ in size and mesh origin
      var newPosition = getNewPosition(element3d, replacement.offset)
      // apply new position
      element3d.x = newPosition.x
      element3d.y = newPosition.y
      element3d.z = newPosition.z
    }
    // recursivley search tree
    if (element3d.children && element3d.children.length) {
      element3d.children = updateElementsById(
        element3d.children,
        id,
        replacement
      )
    }
    return element3d
  })

  return isArray ? sceneStructure : sceneStructure[0]
}

// compute new position based on bounding boxes
function getNewPosition(element3d, offset) {
  var s = Math.sin(element3d.ry / 180 * Math.PI)
  var c = Math.cos(element3d.ry / 180 * Math.PI)
  var newPosition = {
    x: element3d.x + offset.x * c + offset.z * s,
    y: element3d.y + offset.y,
    z: element3d.z - offset.x * s + offset.z * c
  }
  return newPosition
}
<commit_msg>Remove auto-formatting<commit_after>import getFurnitureAlternatives from './get-furniture-alternatives.js'
import getSceneStructureFromAframeElements from '../scene/structure/from-aframe-elements.js'
import getAframeElementsFromSceneStructure from '../scene/structure/to-aframe-elements.js'
import normalizeSceneStructure from '../scene/structure/normalize.js'
import defaults from 'lodash/defaults'
import Promise from 'bluebird'

// consumes sceneStructure or DOM elements
// replaces furniture Ids and adjusts positioning
// outputs input type
export default function replaceFurniture (input, options) {

  options = options || {}
  var query = options.query
  // defaults to pick a random item from alternatives
  var random = options.random || true
  var furnitureIds

  // check for DOM element
  var isDomElement = isElement(input)
  if (isDomElement) {
    // convert to sceneStructure
    input = getSceneStructureFromAframeElements(input)
  }

  return normalizeSceneStructure(input)
    .then(function(sceneStructure) {
      furnitureIds = getIdsFromSceneStructure(sceneStructure)

      if (Object.keys(furnitureIds).length === 0) return Promise.reject('No valid furniture elements were found')

      var promises = []
      Object.keys(furnitureIds).forEach(function(id) {
        promises.push(getFurnitureAlternatives(id, options))
      })

      return Promise.all(promises)
    })
    .then(function(result) {
      var alternatives = {}
      Object.keys(furnitureIds).forEach(function(id, index) {
        alternatives[id] = result[index]
      })

      // replace params in furniture elements
      var sceneStructure = updateSceneStructureWithResult(input, alternatives, random)
      if (isDomElement) {
        return getAframeElementsFromSceneStructure(sceneStructure)
      } else return sceneStructure
    })
    .catch(function(error) {
      console.error(error)
      return Promise.reject(error)
    })
}

function getIdsFromSceneStructure(sceneStructure) {
  var isArray = Array.isArray(sceneStructure)
  sceneStructure = isArray ? sceneStructure : [sceneStructure]

  var collection = {}
  sceneStructure.forEach(function(element3d) {
    // get all furniture elements = type: 'interior'
    if (element3d.type === 'interior' && element3d.src && typeof element3d.src === 'string') collection[element3d.src.substring(1)] = true
    // recursively search through scene structure
    if (element3d.children && element3d.children.length) {
      collection = defaults({}, collection, getIdsFromSceneStructure (element3d.children))
    }
  })
  return collection
}

// Returns true if it is a DOM element
// https://stackoverflow.com/a/384380/2835973
function isElement(o){
  return (
    typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
      o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
  );
}

function updateSceneStructureWithResult(input, alternatives, random) {
  var
    sceneStructure = input,
    replacement,
    index = 0

  Object.keys(alternatives).forEach(function(id) {

    if (!alternatives[id] || !alternatives[id].length) return
    // we the pick a random item or take the first one
    if (random) index = Math.floor(Math.random() * alternatives[id].length)

    replacement = alternatives[id][index]

    sceneStructure = updateElementsById(sceneStructure, id, replacement)
  })
  return sceneStructure
}

// search by furniture id and replace params
function updateElementsById(sceneStructure, id, replacement) {
  var isArray = Array.isArray(sceneStructure)
  sceneStructure = isArray ? sceneStructure : [sceneStructure]

  sceneStructure = sceneStructure.map(function(element3d) {
    // furniture id is stored in src param
    if (element3d.type === 'interior' && element3d.src.substring(1) === id && replacement.furniture) {
      // apply new id
      element3d.src = '!' + replacement.furniture.id
      // compute new position for items that differ in size and mesh origin
      var newPosition = getNewPosition(element3d, replacement.offset)
      // apply new position
      element3d.x = newPosition.x
      element3d.y = newPosition.y
      element3d.z = newPosition.z
    }
    // recursivley search tree
    if (element3d.children && element3d.children.length) {
      element3d.children = updateElementsById(element3d.children, id, replacement)
    }
    return element3d
  })

  return isArray ? sceneStructure : sceneStructure[0]
}

// compute new position based on bounding boxes
function getNewPosition(element3d, offset) {

  var s = Math.sin(element3d.ry / 180 * Math.PI)
  var c = Math.cos(element3d.ry / 180 * Math.PI)
  var newPosition = {
    x: element3d.x + offset.x * c + offset.z * s,
    y: element3d.y + offset.y,
    z: element3d.z - offset.x * s + offset.z * c
  }
  return newPosition
}
<|endoftext|>


<commit_before>/*
 *
 * Plugin to use split.js to create a basic windowed ui
 *
 */
let splithandler_plugin = (function () {

    var num_splits = 0;
    var split_panes = {};
    var backout_list = [];

    var known_types = ['all', 'rest'];

    // Exported Functions

    //
    // function to assign "Text types to catch" to a pane
    var set_pane_types = function (splitpane, types) {
        split_panes[splitpane]['types'] = types;
    }

    //
    // Add buttons to the Evennia webcilent toolbar
    function addToolbarButtons () {
        var toolbar = $('#toolbar');
        toolbar.append( $('<button id="splitbutton" type="button">⇹</button>') );
        toolbar.append( $('<button id="panebutton" type="button">⚙</button>') );
        toolbar.append( $('<button id="undobutton" type="button">↶</button>') );
        $('#undobutton').hide();
    }

    function addSplitDialog () {
        plugins['popups'].createDialog('splitdialog', 'Split Pane', '');
    }

    function addPaneDialog () {
        plugins['popups'].createDialog('panedialog', 'Assign Pane Options', '');
    }

    //
    // Handle resizing the InputField after a client resize event so that the splits dont get too big.
    function resizeInputField () {
        var wrapper = $("#inputform")
        var input = $("#inputcontrol")
        var prompt = $("#prompt")

        input.height( wrapper.height() - (input.offset().top - wrapper.offset().top) );
    }

    //
    // Handle resizing of client
    function doWindowResize() {
        var resizable = $("[data-update-append]");
        var parents = resizable.closest(".split");

        resizeInputField();

        parents.animate({
            scrollTop: parents.prop("scrollHeight")
        }, 0);
    }

    //
    // create a new UI split
    var dynamic_split = function (splitpane, direction, pane_name1, pane_name2, update_method1, update_method2, sizes) {
        // find the sub-div of the pane we are being asked to split
        splitpanesub = splitpane + '-sub';

        // create the new div stack to replace the sub-div with.
        var first_div  = $( '
' )
        var first_sub  = $( '
' )
        var second_div = $( '
' )
        var second_sub = $( '
' )

        // check to see if this sub-pane contains anything
        contents = $('#'+splitpanesub).contents();
        if( contents ) {
            // it does, so move it to the first new div-sub (TODO -- selectable between first/second?)
            contents.appendTo(first_sub);
        }
        first_div.append( first_sub );
        second_div.append( second_sub );

        // update the split_panes array to remove this pane name, but store it for the backout stack
        var backout_settings = split_panes[splitpane];
        delete( split_panes[splitpane] );

        // now vaporize the current split_N-sub placeholder and create two new panes.
        $('#'+splitpane).append(first_div);
        $('#'+splitpane).append(second_div);
        $('#'+splitpane+'-sub').remove();

        // And split
        Split(['#'+pane_name1,'#'+pane_name2], {
            direction: direction,
            sizes: sizes,
            gutterSize: 4,
            minSize: [50,50],
        });

        // store our new split sub-divs for future splits/uses by the main UI.
        split_panes[pane_name1] = { 'types': [], 'update_method': update_method1 };
        split_panes[pane_name2] = { 'types': [], 'update_method': update_method2 };

        // add our new split to the backout stack
        backout_list.push( {'pane1': pane_name1, 'pane2': pane_name2, 'undo': backout_settings} );

        $('#undobutton').show();
    }

    //
    // Reverse the last UI split
    var undo_split = function () {
        // pop off the last split pair
        var back = backout_list.pop();
        if( !back ) {
            return;
        }

        if( backout_list.length === 0 ) {
            $('#undobutton').hide();
        }

        // Collect all the divs/subs in play
        var pane1 = back['pane1'];
        var pane2 = back['pane2'];
        var pane1_sub = $('#'+pane1+'-sub');
        var pane2_sub = $('#'+pane2+'-sub');
        var pane1_parent = $('#'+pane1).parent();
        var pane2_parent = $('#'+pane2).parent();

        if( pane1_parent.attr('id') != pane2_parent.attr('id') ) {
            // sanity check failed...somebody did something weird...bail out
            console.log( pane1 );
            console.log( pane2 );
            console.log( pane1_parent );
            console.log( pane2_parent );
            return;
        }

        // create a new sub-pane in the panes parent
        var parent_sub = $( '
' )

        // check to see if the special #messagewindow is in either of our sub-panes.
        var msgwindow = pane1_sub.find('#messagewindow')
        if( !msgwindow ) {
            //didn't find it in pane 1, try pane 2
            msgwindow = pane2_sub.find('#messagewindow')
        }
        if( msgwindow ) {
            // It is, so collect all contents into it instead of our parent_sub div
            // then move it to parent sub div, this allows future #messagewindow divs to flow properly
            msgwindow.append( pane1_sub.contents() );
            msgwindow.append( pane2_sub.contents() );
            parent_sub.append( msgwindow );
        } else {
            //didn't find it, so move the contents of the two panes' sub-panes into the new sub-pane
            parent_sub.append( pane1_sub.contents() );
            parent_sub.append( pane2_sub.contents() );
        }

        // clear the parent
        pane1_parent.empty();

        // add the new sub-pane back to the parent div
        pane1_parent.append(parent_sub);

        // pull the sub-div's from split_panes
        delete split_panes[pane1];
        delete split_panes[pane2];

        // add our parent pane back into the split_panes list for future splitting
        split_panes[pane1_parent.attr('id')] = back['undo'];
    }

    //
    // UI elements
    //

    //
    // Draw "Split Controls" Dialog
    var onSplitDialog = function () {
        var dialog = $("#splitdialogcontent");
        dialog.empty();

	var selection = '<select name="pane">';
        for ( var pane in split_panes ) {
	    selection = selection + '<option value="' + pane + '">' + pane + '</option>';
        }
	selection = "Pane to split: " + selection + "</select> ";
	dialog.append(selection);

        dialog.append('<input type="radio" name="direction" value="vertical" checked>top/bottom </>');
        dialog.append('<input type="radio" name="direction" value="horizontal">side-by-side
');

        dialog.append('Pane 1: <input type="text" name="new_pane1" value="" />');
        dialog.append('<input type="radio" name="flow1" value="linefeed" checked>newlines </>');
        dialog.append('<input type="radio" name="flow1" value="replace">replace </>');
        dialog.append('<input type="radio" name="flow1" value="append">append
');

        dialog.append('Pane 2: <input type="text" name="new_pane2" value="" />');
        dialog.append('<input type="radio" name="flow2" value="linefeed" checked>newlines </>');
        dialog.append('<input type="radio" name="flow2" value="replace">replace </>');
        dialog.append('<input type="radio" name="flow2" value="append">append
');

	dialog.append('
Split
');

        $("#splitclose").bind("click", onSplitDialogClose);

        plugins['popups'].togglePopup("#splitdialog");
    }

    //
    // Close "Split Controls" Dialog
    var onSplitDialogClose = function () {
        var pane      = $("select[name=pane]").val();
        var direction = $("input[name=direction]:checked").attr("value");
        var new_pane1 = $("input[name=new_pane1]").val();
        var new_pane2 = $("input[name=new_pane2]").val();
        var flow1     = $("input[name=flow1]:checked").attr("value");
        var flow2     = $("input[name=flow2]:checked").attr("value");

        if( new_pane1 == "" ) {
            new_pane1 = 'pane_'+num_splits;
            num_splits++;
        }

        if( new_pane2 == "" ) {
            new_pane2 = 'pane_'+num_splits;
            num_splits++;
        }

        if( document.getElementById(new_pane1) ) {
            alert('An element: "' + new_pane1 + '" already exists');
            return;
        }

        if( document.getElementById(new_pane2) ) {
            alert('An element: "' + new_pane2 + '" already exists');
            return;
        }

        dynamic_split( pane, direction, new_pane1, new_pane2, flow1, flow2, [50,50] );

        plugins['popups'].closePopup("#splitdialog");
    }

    //
    // Draw "Pane Controls" dialog
    var onPaneControlDialog = function () {
        var dialog = $("#panedialogcontent");
        dialog.empty();

	var selection = '<select name="assign-pane">';
        for ( var pane in split_panes ) {
	    selection = selection + '<option value="' + pane + '">' + pane + '</option>';
        }
	selection = "Assign to pane: " + selection + "</select>
";
	dialog.append(selection);

	var multiple = '<select multiple name="assign-type">';
        for ( var type in known_types ) {
	    multiple = multiple + '<option value="' + known_types[type] + '">' + known_types[type] + '</option>';
        }
	multiple = "Content types: " + multiple + "</select>
";
	dialog.append(multiple);

	dialog.append('
Assign
');

        $("#paneclose").bind("click", onPaneControlDialogClose);

        plugins['popups'].togglePopup("#panedialog");
    }

    //
    // Close "Pane Controls" dialog
    var onPaneControlDialogClose = function () {
        var pane = $("select[name=assign-pane]").val();
	var types = $("select[name=assign-type]").val();

	// var types = new Array;
        // $('#splitdialogcontent input[type=checkbox]:checked').each(function() {
        //     types.push( $(this).attr('value') );
        // });

        set_pane_types( pane, types );

        plugins['popups'].closePopup("#panedialog");
    }
    //
    // helper function sending text to a pane
    var txtToPane = function (panekey, txt) {
	var pane = split_panes[panekey];
	var text_div = $('#' + panekey + '-sub');

	if ( pane['update_method'] == 'replace' ) {
	    text_div.html(txt)
	} else if ( pane['update_method'] == 'append' ) {
	    text_div.append(txt);
	    var scrollHeight = text_div.parent().prop("scrollHeight");
	    text_div.parent().animate({ scrollTop: scrollHeight }, 0);
	} else {  // line feed
	    text_div.append("
" + txt + "
");
	    var scrollHeight = text_div.parent().prop("scrollHeight");
	    text_div.parent().animate({ scrollTop: scrollHeight }, 0);
	}

    }

    //
    // plugin functions
    //

    //
    // Accept plugin onText events
    var onText = function (args, kwargs) {

	// If the message is not itself tagged, we'll assume it
	// should go into any panes with 'all' or 'rest' set
        var msgtype = "rest";

        if ( kwargs && 'type' in kwargs ) {
	    msgtype = kwargs['type'];
            if ( ! known_types.includes(msgtype) ) {
                // this is a new output type that can be mapped to panes
                console.log('detected new output type: ' + msgtype)
                known_types.push(msgtype);
            }
	}
	var target_panes = [];
	var rest_panes = [];

	for (var key in split_panes) {
	    var pane = split_panes[key];
	    // is this message type mapped to this pane (or does the pane has an 'all' type)?
	    if (pane['types'].length > 0) {
		if (pane['types'].includes(msgtype) || pane['types'].includes('all')) {
		    target_panes.push(key);
		} else if (pane['types'].includes('rest')) {
		    // store rest-panes in case we have no explicit to send to
		    rest_panes.push(key);
		}
	    } else {
		// unassigned panes are assumed to be rest-panes too
		rest_panes.push(key);
	    }
	}
	var ntargets = target_panes.length;
	var nrests = rest_panes.length;
	if (ntargets > 0) {
	    // we have explicit target panes to send to
	    for (var i=0; i<ntargets; i++) {
		txtToPane(target_panes[i], args[0]);
	    }
	    return true;
	} else if (nrests > 0) {
	    // no targets, send remainder to rest-panes/unassigned
	    for (var i=0; i<nrests; i++) {
		txtToPane(rest_panes[i], args[0]);
	    }
	    return true;
	}
	// unhandled message
        return false;
    }

    //
    // Required plugin "init" function
    var init = function(settings) {
        known_types.push('help');

        Split(['#main','#input'], {
            direction: 'vertical',
            sizes: [90,10],
            gutterSize: 4,
            minSize: [50,50],
        });

        split_panes['main']  = { 'types': [], 'update_method': 'linefeed' };

        // Create our UI
        addToolbarButtons();
        addSplitDialog();
        addPaneDialog();

        // Register our utility button events
        $("#splitbutton").bind("click", onSplitDialog);
        $("#panebutton").bind("click", onPaneControlDialog);
        $("#undobutton").bind("click", undo_split);

        // Event when client window changes
        $(window).bind("resize", doWindowResize);

        $("[data-role-input]").bind("resize", doWindowResize)
                              .bind("paste", resizeInputField)
                              .bind("cut", resizeInputField);

        // Event when any key is pressed
        $(document).keyup(resizeInputField);

        console.log("Splithandler Plugin Initialized.");
    }

    return {
        init: init,
        onText: onText,
        dynamic_split: dynamic_split,
        undo_split: undo_split,
        set_pane_types: set_pane_types,
    }
})()
plugin_handler.add('splithandler', splithandler_plugin);
<commit_msg>whitespace cleanup<commit_after>/*
 *
 * Plugin to use split.js to create a basic windowed ui
 *
 */
let splithandler_plugin = (function () {

    var num_splits = 0;
    var split_panes = {};
    var backout_list = [];

    var known_types = ['all', 'rest'];

    // Exported Functions

    //
    // function to assign "Text types to catch" to a pane
    var set_pane_types = function (splitpane, types) {
        split_panes[splitpane]['types'] = types;
    }

    //
    // Add buttons to the Evennia webcilent toolbar
    function addToolbarButtons () {
        var toolbar = $('#toolbar');
        toolbar.append( $('<button id="splitbutton" type="button">⇹</button>') );
        toolbar.append( $('<button id="panebutton" type="button">⚙</button>') );
        toolbar.append( $('<button id="undobutton" type="button">↶</button>') );
        $('#undobutton').hide();
    }

    function addSplitDialog () {
        plugins['popups'].createDialog('splitdialog', 'Split Pane', '');
    }

    function addPaneDialog () {
        plugins['popups'].createDialog('panedialog', 'Assign Pane Options', '');
    }

    //
    // Handle resizing the InputField after a client resize event so that the splits dont get too big.
    function resizeInputField () {
        var wrapper = $("#inputform")
        var input = $("#inputcontrol")
        var prompt = $("#prompt")

        input.height( wrapper.height() - (input.offset().top - wrapper.offset().top) );
    }

    //
    // Handle resizing of client
    function doWindowResize() {
        var resizable = $("[data-update-append]");
        var parents = resizable.closest(".split");

        resizeInputField();

        parents.animate({
            scrollTop: parents.prop("scrollHeight")
        }, 0);
    }

    //
    // create a new UI split
    var dynamic_split = function (splitpane, direction, pane_name1, pane_name2, update_method1, update_method2, sizes) {
        // find the sub-div of the pane we are being asked to split
        splitpanesub = splitpane + '-sub';

        // create the new div stack to replace the sub-div with.
        var first_div  = $( '
' )
        var first_sub  = $( '
' )
        var second_div = $( '
' )
        var second_sub = $( '
' )

        // check to see if this sub-pane contains anything
        contents = $('#'+splitpanesub).contents();
        if( contents ) {
            // it does, so move it to the first new div-sub (TODO -- selectable between first/second?)
            contents.appendTo(first_sub);
        }
        first_div.append( first_sub );
        second_div.append( second_sub );

        // update the split_panes array to remove this pane name, but store it for the backout stack
        var backout_settings = split_panes[splitpane];
        delete( split_panes[splitpane] );

        // now vaporize the current split_N-sub placeholder and create two new panes.
        $('#'+splitpane).append(first_div);
        $('#'+splitpane).append(second_div);
        $('#'+splitpane+'-sub').remove();

        // And split
        Split(['#'+pane_name1,'#'+pane_name2], {
            direction: direction,
            sizes: sizes,
            gutterSize: 4,
            minSize: [50,50],
        });

        // store our new split sub-divs for future splits/uses by the main UI.
        split_panes[pane_name1] = { 'types': [], 'update_method': update_method1 };
        split_panes[pane_name2] = { 'types': [], 'update_method': update_method2 };

        // add our new split to the backout stack
        backout_list.push( {'pane1': pane_name1, 'pane2': pane_name2, 'undo': backout_settings} );

        $('#undobutton').show();
    }

    //
    // Reverse the last UI split
    var undo_split = function () {
        // pop off the last split pair
        var back = backout_list.pop();
        if( !back ) {
            return;
        }

        if( backout_list.length === 0 ) {
            $('#undobutton').hide();
        }

        // Collect all the divs/subs in play
        var pane1 = back['pane1'];
        var pane2 = back['pane2'];
        var pane1_sub = $('#'+pane1+'-sub');
        var pane2_sub = $('#'+pane2+'-sub');
        var pane1_parent = $('#'+pane1).parent();
        var pane2_parent = $('#'+pane2).parent();

        if( pane1_parent.attr('id') != pane2_parent.attr('id') ) {
            // sanity check failed...somebody did something weird...bail out
            console.log( pane1 );
            console.log( pane2 );
            console.log( pane1_parent );
            console.log( pane2_parent );
            return;
        }

        // create a new sub-pane in the panes parent
        var parent_sub = $( '
' )

        // check to see if the special #messagewindow is in either of our sub-panes.
        var msgwindow = pane1_sub.find('#messagewindow')
        if( !msgwindow ) {
            //didn't find it in pane 1, try pane 2
            msgwindow = pane2_sub.find('#messagewindow')
        }
        if( msgwindow ) {
            // It is, so collect all contents into it instead of our parent_sub div
            // then move it to parent sub div, this allows future #messagewindow divs to flow properly
            msgwindow.append( pane1_sub.contents() );
            msgwindow.append( pane2_sub.contents() );
            parent_sub.append( msgwindow );
        } else {
            //didn't find it, so move the contents of the two panes' sub-panes into the new sub-pane
            parent_sub.append( pane1_sub.contents() );
            parent_sub.append( pane2_sub.contents() );
        }

        // clear the parent
        pane1_parent.empty();

        // add the new sub-pane back to the parent div
        pane1_parent.append(parent_sub);

        // pull the sub-div's from split_panes
        delete split_panes[pane1];
        delete split_panes[pane2];

        // add our parent pane back into the split_panes list for future splitting
        split_panes[pane1_parent.attr('id')] = back['undo'];
    }

    //
    // UI elements
    //

    //
    // Draw "Split Controls" Dialog
    var onSplitDialog = function () {
        var dialog = $("#splitdialogcontent");
        dialog.empty();

        var selection = '<select name="pane">';
        for ( var pane in split_panes ) {
            selection = selection + '<option value="' + pane + '">' + pane + '</option>';
        }
        selection = "Pane to split: " + selection + "</select> ";
        dialog.append(selection);

        dialog.append('<input type="radio" name="direction" value="vertical" checked>top/bottom </>');
        dialog.append('<input type="radio" name="direction" value="horizontal">side-by-side
');

        dialog.append('Pane 1: <input type="text" name="new_pane1" value="" />');
        dialog.append('<input type="radio" name="flow1" value="linefeed" checked>newlines </>');
        dialog.append('<input type="radio" name="flow1" value="replace">replace </>');
        dialog.append('<input type="radio" name="flow1" value="append">append
');

        dialog.append('Pane 2: <input type="text" name="new_pane2" value="" />');
        dialog.append('<input type="radio" name="flow2" value="linefeed" checked>newlines </>');
        dialog.append('<input type="radio" name="flow2" value="replace">replace </>');
        dialog.append('<input type="radio" name="flow2" value="append">append
');

        dialog.append('
Split
');

        $("#splitclose").bind("click", onSplitDialogClose);

        plugins['popups'].togglePopup("#splitdialog");
    }

    //
    // Close "Split Controls" Dialog
    var onSplitDialogClose = function () {
        var pane      = $("select[name=pane]").val();
        var direction = $("input[name=direction]:checked").attr("value");
        var new_pane1 = $("input[name=new_pane1]").val();
        var new_pane2 = $("input[name=new_pane2]").val();
        var flow1     = $("input[name=flow1]:checked").attr("value");
        var flow2     = $("input[name=flow2]:checked").attr("value");

        if( new_pane1 == "" ) {
            new_pane1 = 'pane_'+num_splits;
            num_splits++;
        }

        if( new_pane2 == "" ) {
            new_pane2 = 'pane_'+num_splits;
            num_splits++;
        }

        if( document.getElementById(new_pane1) ) {
            alert('An element: "' + new_pane1 + '" already exists');
            return;
        }

        if( document.getElementById(new_pane2) ) {
            alert('An element: "' + new_pane2 + '" already exists');
            return;
        }

        dynamic_split( pane, direction, new_pane1, new_pane2, flow1, flow2, [50,50] );

        plugins['popups'].closePopup("#splitdialog");
    }

    //
    // Draw "Pane Controls" dialog
    var onPaneControlDialog = function () {
        var dialog = $("#panedialogcontent");
        dialog.empty();

        var selection = '<select name="assign-pane">';
        for ( var pane in split_panes ) {
            selection = selection + '<option value="' + pane + '">' + pane + '</option>';
        }
        selection = "Assign to pane: " + selection + "</select>
";
        dialog.append(selection);

        var multiple = '<select multiple name="assign-type">';
        for ( var type in known_types ) {
            multiple = multiple + '<option value="' + known_types[type] + '">' + known_types[type] + '</option>';
        }
        multiple = "Content types: " + multiple + "</select>
";
        dialog.append(multiple);

        dialog.append('
Assign
');

        $("#paneclose").bind("click", onPaneControlDialogClose);

        plugins['popups'].togglePopup("#panedialog");
    }

    //
    // Close "Pane Controls" dialog
    var onPaneControlDialogClose = function () {
        var pane = $("select[name=assign-pane]").val();
        var types = $("select[name=assign-type]").val();

        // var types = new Array;
        // $('#splitdialogcontent input[type=checkbox]:checked').each(function() {
        //     types.push( $(this).attr('value') );
        // });

        set_pane_types( pane, types );

        plugins['popups'].closePopup("#panedialog");
    }

    //
    // helper function sending text to a pane
    var txtToPane = function (panekey, txt) {
        var pane = split_panes[panekey];
        var text_div = $('#' + panekey + '-sub');

        if ( pane['update_method'] == 'replace' ) {
            text_div.html(txt)
        } else if ( pane['update_method'] == 'append' ) {
            text_div.append(txt);
            var scrollHeight = text_div.parent().prop("scrollHeight");
            text_div.parent().animate({ scrollTop: scrollHeight }, 0);
        } else {  // line feed
            text_div.append("
" + txt + "
");
            var scrollHeight = text_div.parent().prop("scrollHeight");
            text_div.parent().animate({ scrollTop: scrollHeight }, 0);
        }
    }

    //
    // plugin functions
    //

    //
    // Accept plugin onText events
    var onText = function (args, kwargs) {
        // If the message is not itself tagged, we'll assume it
        // should go into any panes with 'all' or 'rest' set
        var msgtype = "rest";

        if ( kwargs && 'type' in kwargs ) {
            msgtype = kwargs['type'];
            if ( ! known_types.includes(msgtype) ) {
                // this is a new output type that can be mapped to panes
                console.log('detected new output type: ' + msgtype)
                known_types.push(msgtype);
            }
        }
        var target_panes = [];
        var rest_panes = [];

        for (var key in split_panes) {
            var pane = split_panes[key];
            // is this message type mapped to this pane (or does the pane has an 'all' type)?
            if (pane['types'].length > 0) {
                if (pane['types'].includes(msgtype) || pane['types'].includes('all')) {
                    target_panes.push(key);
                } else if (pane['types'].includes('rest')) {
                    // store rest-panes in case we have no explicit to send to
                    rest_panes.push(key);
                }
            } else {
                // unassigned panes are assumed to be rest-panes too
                rest_panes.push(key);
            }
        }
        var ntargets = target_panes.length;
        var nrests = rest_panes.length;
        if (ntargets > 0) {
            // we have explicit target panes to send to
            for (var i=0; i<ntargets; i++) {
                txtToPane(target_panes[i], args[0]);
            }
            return true;
        } else if (nrests > 0) {
            // no targets, send remainder to rest-panes/unassigned
            for (var i=0; i<nrests; i++) {
                txtToPane(rest_panes[i], args[0]);
            }
            return true;
        }
        // unhandled message
        return false;
    }

    //
    // Required plugin "init" function
    var init = function(settings) {
        known_types.push('help');

        Split(['#main','#input'], {
            direction: 'vertical',
            sizes: [90,10],
            gutterSize: 4,
            minSize: [50,50],
        });

        split_panes['main']  = { 'types': [], 'update_method': 'linefeed' };

        // Create our UI
        addToolbarButtons();
        addSplitDialog();
        addPaneDialog();

        // Register our utility button events
        $("#splitbutton").bind("click", onSplitDialog);
        $("#panebutton").bind("click", onPaneControlDialog);
        $("#undobutton").bind("click", undo_split);

        // Event when client window changes
        $(window).bind("resize", doWindowResize);

        $("[data-role-input]").bind("resize", doWindowResize)
                              .bind("paste", resizeInputField)
                              .bind("cut", resizeInputField);

        // Event when any key is pressed
        $(document).keyup(resizeInputField);

        console.log("Splithandler Plugin Initialized.");
    }

    return {
        init: init,
        onText: onText,
        dynamic_split: dynamic_split,
        undo_split: undo_split,
        set_pane_types: set_pane_types,
    }
})()
plugin_handler.add('splithandler', splithandler_plugin);
<|endoftext|>