<commit_before>/* eslint-disable no-param-reassign */
/* eslint-disable class-methods-use-this */
const { Forbidden, GeneralError, BadRequest } = require('@feathersjs/errors');
const { authenticate } = require('@feathersjs/authentication').hooks;
const moment = require('moment');
const { v4: uuidv4 } = require('uuid');
const { Configuration } = require('@schul-cloud/commons');
const logger = require('../../../logger');
const { createMultiDocumentAggregation } = require('../utils/aggregations');
const { hasSchoolPermission, blockDisposableEmail } = require('../../../hooks');
const { equal: equalIds } = require('../../../helper/compare').ObjectId;
const { validateParams } = require('../hooks/adminUsers.hooks');
const { sendRegistrationLink } = require('../hooks/userService');
const { updateAccountUsername } = require('../hooks/userService');

const { userModel } = require('../model');

const getCurrentUserInfo = (id) => userModel.findById(id).select('schoolId').lean().exec();

const getCurrentYear = (ref, schoolId) =>
	ref.app
		.service('schools')
		.get(schoolId, {
			query: { $select: ['currentYear'] },
		})
		.then(({ currentYear }) => currentYear.toString());

class AdminUsers {
	constructor(roleName) {
		this.roleName = roleName;
		this.docs = {};
	}

	async find(params) {
		return this.getUsers(undefined, params);
	}

	async get(id, params) {
		return this.getUsers(id, params);
	}

	async getUsers(_id, params) {
		// integration test did not get the role in the setup
		// so here is a workaround set it at first call
		if (!this.role) {
			this.role = (
				await this.app.service('roles').find({
					query: { name: this.roleName },
				})
			).data[0];
		}

		try {
			const { query: clientQuery = {}, account } = params;
			const currentUserId = account.userId.toString();

			// fetch base data
			const { schoolId } = await getCurrentUserInfo(currentUserId);
			const schoolYearId = await getCurrentYear(this, schoolId);

			const query = {
				schoolId,
				roles: this.role._id,
				schoolYearId,
				sort: clientQuery.$sort || clientQuery.sort,
				select: [
					'consentStatus',
					'consent',
					'classes',
					'firstName',
					'lastName',
					'email',
					'createdAt',
					'importHash',
					'birthday',
					'preferences.registrationMailSend',
				],
				skip: clientQuery.$skip || clientQuery.skip,
				limit: clientQuery.$limit || clientQuery.limit,
			};
			if (_id) {
				query._id = _id;
			} else if (clientQuery.users) {
				query._id = clientQuery.users;
				// If the number of users exceeds 20, the underlying parsing library
				// will convert the array to an object with the index as the key.
				// To continue working with it, we convert it here back to the array form.
				// See the documentation for further infos: https://github.com/ljharb/qs#parsing-arrays
				if (typeof query._id === 'object') query._id = Object.values(query._id);
			}
			if (clientQuery.consentStatus) query.consentStatus = clientQuery.consentStatus;
			if (clientQuery.classes) query.classes = clientQuery.classes;
			if (clientQuery.firstName) query.firstName = clientQuery.firstName;
			if (clientQuery.lastName) query.lastName = clientQuery.lastName;
			if (clientQuery.usersForConsent) query._id = clientQuery.usersForConsent;
			if (clientQuery.searchQuery) {
				query.$or = [
					{ firstName: { $regex: clientQuery.searchQuery, $options: 'i' } },
					{ lastName: { $regex: clientQuery.searchQuery, $options: 'i' } },
					{ email: { $regex: clientQuery.searchQuery, $options: 'i' } },
				];
			}

			const dateQueries = ['createdAt'];
			for (const dateQuery of dateQueries) {
				if (clientQuery[dateQuery]) {
					if (typeof clientQuery[dateQuery] === 'object') {
						for (const [key, value] of Object.entries(clientQuery[dateQuery])) {
							if (['$gt', '$gte', '$lt', '$lte'].includes(key)) {
								clientQuery[dateQuery][key] = new Date(value);
							}
						}
						query[dateQuery] = clientQuery[dateQuery];
					} else {
						query[dateQuery] = new Date(clientQuery[dateQuery]);
					}
				}
			}

			return new Promise((resolve, reject) =>
				userModel
					.aggregate(createMultiDocumentAggregation(query))
					.option({
						collation: { locale: 'de', caseLevel: true },
					})
					.exec((err, res) => {
						if (err) reject(err);
						else resolve(res[0] || {});
					})
			);
		} catch (err) {
			if ((err || {}).code === 403) {
				throw new Forbidden('You have not the permission to execute this.', err);
			}
			if (err && err.code >= 500) {
				const uuid = uuidv4();
				logger.error(uuid, err);
				if (Configuration.get('NODE_ENV') !== 'production') {
					throw err;
				}
				throw new GeneralError(uuid);
			}
			throw err;
		}
	}

	async create(data, params) {
		const { account } = params;
		const currentUserId = account.userId.toString();

		// checks if school isExternal, throws forbidden if it is
		const { schoolId } = await getCurrentUserInfo(currentUserId);
		const { isExternal } = await this.app.service('schools').get(schoolId);
		if (isExternal) {
			throw new Forbidden('Creating new students or teachers is only possible in the source system.');
		}

		// checks for unique email accounts, throws bad request if it already exists
		const { email } = data;
		const userService = this.app.service('/users');
		const accounts = await userService.find({ query: { email: email.toLowerCase() } });
		if (accounts.total > 0) {
			throw new BadRequest('Email already exists.');
		}

		return this.app.service('usersModel').create(data);
	}

	async update(id, data, params) {
		return this.app.service('usersModel').update(id, data);
	}

	async patch(id, data, params) {
		return this.app.service('usersModel').patch(id, data);
	}

	async remove(id, params) {
		const { _ids } = params.query;
		const currentUser = await getCurrentUserInfo(params.account.userId);

		if (id) {
			const userToRemove = await getCurrentUserInfo(id);
			if (!equalIds(currentUser.schoolId, userToRemove.schoolId)) {
				throw new Forbidden('You cannot remove users from other schools.');
			}
			await this.app.service('accountModel').remove(null, { query: { userId: id } });
			return this.app.service('usersModel').remove(id);
		}

		const usersIds = await Promise.all(_ids.map((userId) => getCurrentUserInfo(userId)));
		if (usersIds.some((user) => !equalIds(currentUser.schoolId, user.schoolId))) {
			throw new Forbidden('You cannot remove users from other schools.');
		}

		await this.app.service('accountModel').remove(null, { query: { userId: { $in: _ids } } });
		return this.app.service('usersModel').remove(null, { query: { _id: { $in: _ids } } });
	}

	async setup(app) {
		this.app = app;
		this.role = (
			await this.app.service('roles').find({
				query: { name: this.roleName },
			})
		).data[0];
	}
}

const formatBirthdayOfUsers = ({ result: { data: users } }) => {
	users.forEach((user) => {
		if (user.birthday) user.birthday = moment(user.birthday).format('DD.MM.YYYY');
		if (user.birthday) {
			user.birthday = moment(user.birthday).format('DD.MM.YYYY');
		}
	});
};

const adminHookGenerator = (kind) => ({
	before: {
		all: [authenticate('jwt')],
		find: [hasSchoolPermission(`${kind}_LIST`)],
		get: [hasSchoolPermission(`${kind}_LIST`)],
		create: [hasSchoolPermission(`${kind}_CREATE`), blockDisposableEmail('email')],
		update: [hasSchoolPermission(`${kind}_EDIT`)],
		patch: [hasSchoolPermission(`${kind}_EDIT`)],
		remove: [hasSchoolPermission(`${kind}_DELETE`), validateParams],
	},
	after: {
		find: [formatBirthdayOfUsers],
		create: [sendRegistrationLink],
		patch: [updateAccountUsername],
	},
});

module.exports = {
	AdminUsers,
	adminHookGenerator,
};
<commit_msg>fix invalid date<commit_after>/* eslint-disable no-param-reassign */
/* eslint-disable class-methods-use-this */
const { Forbidden, GeneralError, BadRequest } = require('@feathersjs/errors');
const { authenticate } = require('@feathersjs/authentication').hooks;
const moment = require('moment');
const { v4: uuidv4 } = require('uuid');
const { Configuration } = require('@schul-cloud/commons');
const logger = require('../../../logger');
const { createMultiDocumentAggregation } = require('../utils/aggregations');
const { hasSchoolPermission, blockDisposableEmail } = require('../../../hooks');
const { equal: equalIds } = require('../../../helper/compare').ObjectId;
const { validateParams } = require('../hooks/adminUsers.hooks');
const { sendRegistrationLink } = require('../hooks/userService');
const { updateAccountUsername } = require('../hooks/userService');

const { userModel } = require('../model');

const getCurrentUserInfo = (id) => userModel.findById(id).select('schoolId').lean().exec();

const getCurrentYear = (ref, schoolId) =>
	ref.app
		.service('schools')
		.get(schoolId, {
			query: { $select: ['currentYear'] },
		})
		.then(({ currentYear }) => currentYear.toString());

class AdminUsers {
	constructor(roleName) {
		this.roleName = roleName;
		this.docs = {};
	}

	async find(params) {
		return this.getUsers(undefined, params);
	}

	async get(id, params) {
		return this.getUsers(id, params);
	}

	async getUsers(_id, params) {
		// integration test did not get the role in the setup
		// so here is a workaround set it at first call
		if (!this.role) {
			this.role = (
				await this.app.service('roles').find({
					query: { name: this.roleName },
				})
			).data[0];
		}

		try {
			const { query: clientQuery = {}, account } = params;
			const currentUserId = account.userId.toString();

			// fetch base data
			const { schoolId } = await getCurrentUserInfo(currentUserId);
			const schoolYearId = await getCurrentYear(this, schoolId);

			const query = {
				schoolId,
				roles: this.role._id,
				schoolYearId,
				sort: clientQuery.$sort || clientQuery.sort,
				select: [
					'consentStatus',
					'consent',
					'classes',
					'firstName',
					'lastName',
					'email',
					'createdAt',
					'importHash',
					'birthday',
					'preferences.registrationMailSend',
				],
				skip: clientQuery.$skip || clientQuery.skip,
				limit: clientQuery.$limit || clientQuery.limit,
			};
			if (_id) {
				query._id = _id;
			} else if (clientQuery.users) {
				query._id = clientQuery.users;
				// If the number of users exceeds 20, the underlying parsing library
				// will convert the array to an object with the index as the key.
				// To continue working with it, we convert it here back to the array form.
				// See the documentation for further infos: https://github.com/ljharb/qs#parsing-arrays
				if (typeof query._id === 'object') query._id = Object.values(query._id);
			}
			if (clientQuery.consentStatus) query.consentStatus = clientQuery.consentStatus;
			if (clientQuery.classes) query.classes = clientQuery.classes;
			if (clientQuery.firstName) query.firstName = clientQuery.firstName;
			if (clientQuery.lastName) query.lastName = clientQuery.lastName;
			if (clientQuery.usersForConsent) query._id = clientQuery.usersForConsent;
			if (clientQuery.searchQuery) {
				query.$or = [
					{ firstName: { $regex: clientQuery.searchQuery, $options: 'i' } },
					{ lastName: { $regex: clientQuery.searchQuery, $options: 'i' } },
					{ email: { $regex: clientQuery.searchQuery, $options: 'i' } },
				];
			}

			const dateQueries = ['createdAt'];
			for (const dateQuery of dateQueries) {
				if (clientQuery[dateQuery]) {
					if (typeof clientQuery[dateQuery] === 'object') {
						for (const [key, value] of Object.entries(clientQuery[dateQuery])) {
							if (['$gt', '$gte', '$lt', '$lte'].includes(key)) {
								clientQuery[dateQuery][key] = new Date(value);
							}
						}
						query[dateQuery] = clientQuery[dateQuery];
					} else {
						query[dateQuery] = new Date(clientQuery[dateQuery]);
					}
				}
			}

			return new Promise((resolve, reject) =>
				userModel
					.aggregate(createMultiDocumentAggregation(query))
					.option({
						collation: { locale: 'de', caseLevel: true },
					})
					.exec((err, res) => {
						if (err) reject(err);
						else resolve(res[0] || {});
					})
			);
		} catch (err) {
			if ((err || {}).code === 403) {
				throw new Forbidden('You have not the permission to execute this.', err);
			}
			if (err && err.code >= 500) {
				const uuid = uuidv4();
				logger.error(uuid, err);
				if (Configuration.get('NODE_ENV') !== 'production') {
					throw err;
				}
				throw new GeneralError(uuid);
			}
			throw err;
		}
	}

	async create(data, params) {
		const { account } = params;
		const currentUserId = account.userId.toString();

		// checks if school isExternal, throws forbidden if it is
		const { schoolId } = await getCurrentUserInfo(currentUserId);
		const { isExternal } = await this.app.service('schools').get(schoolId);
		if (isExternal) {
			throw new Forbidden('Creating new students or teachers is only possible in the source system.');
		}

		// checks for unique email accounts, throws bad request if it already exists
		const { email } = data;
		const userService = this.app.service('/users');
		const accounts = await userService.find({ query: { email: email.toLowerCase() } });
		if (accounts.total > 0) {
			throw new BadRequest('Email already exists.');
		}

		return this.app.service('usersModel').create(data);
	}

	async update(id, data, params) {
		return this.app.service('usersModel').update(id, data);
	}

	async patch(id, data, params) {
		return this.app.service('usersModel').patch(id, data);
	}

	async remove(id, params) {
		const { _ids } = params.query;
		const currentUser = await getCurrentUserInfo(params.account.userId);

		if (id) {
			const userToRemove = await getCurrentUserInfo(id);
			if (!equalIds(currentUser.schoolId, userToRemove.schoolId)) {
				throw new Forbidden('You cannot remove users from other schools.');
			}
			await this.app.service('accountModel').remove(null, { query: { userId: id } });
			return this.app.service('usersModel').remove(id);
		}

		const usersIds = await Promise.all(_ids.map((userId) => getCurrentUserInfo(userId)));
		if (usersIds.some((user) => !equalIds(currentUser.schoolId, user.schoolId))) {
			throw new Forbidden('You cannot remove users from other schools.');
		}

		await this.app.service('accountModel').remove(null, { query: { userId: { $in: _ids } } });
		return this.app.service('usersModel').remove(null, { query: { _id: { $in: _ids } } });
	}

	async setup(app) {
		this.app = app;
		this.role = (
			await this.app.service('roles').find({
				query: { name: this.roleName },
			})
		).data[0];
	}
}

const formatBirthdayOfUsers = ({ result: { data: users } }) => {
	users.forEach((user) => {
		if (user.birthday) {
			user.birthday = moment(user.birthday).format('DD.MM.YYYY');
		}
	});
};

const adminHookGenerator = (kind) => ({
	before: {
		all: [authenticate('jwt')],
		find: [hasSchoolPermission(`${kind}_LIST`)],
		get: [hasSchoolPermission(`${kind}_LIST`)],
		create: [hasSchoolPermission(`${kind}_CREATE`), blockDisposableEmail('email')],
		update: [hasSchoolPermission(`${kind}_EDIT`)],
		patch: [hasSchoolPermission(`${kind}_EDIT`)],
		remove: [hasSchoolPermission(`${kind}_DELETE`), validateParams],
	},
	after: {
		find: [formatBirthdayOfUsers],
		create: [sendRegistrationLink],
		patch: [updateAccountUsername],
	},
});

module.exports = {
	AdminUsers,
	adminHookGenerator,
};
<|endoftext|>


<commit_before>/**
  @module ember-flexberry-gis
*/

import Ember from 'ember';
import BaseModeComponent from 'ember-flexberry-gis/components/layers-dialogs/edit-modes/base';
import layout from '../../../templates/components/layers-dialogs/edit-modes/test-mode';
import { Query } from 'ember-flexberry-data';
/*import {
  translationMacro as t
} from 'ember-i18n';
*/
/**
  Component's CSS-classes names.
  JSON-object containing string constants with CSS-classes names related to component's .hbs markup elements.
  @property {Object} flexberryClassNames
  @property {String} flexberryClassNames.prefix Component's CSS-class names prefix ('flexberry-edit-mode-csw').
  @property {String} flexberryClassNames.wrapper Component's wrapping
 CSS-class name (null, because component is tagless).
  @readonly
  @static
  @for CswModeComponent
*/
const flexberryClassNamesPrefix = 'flexberry-edit-mode-csw';
const flexberryClassNames = {
  prefix: flexberryClassNamesPrefix,
  wrapper: null
};

/**
  @class CswModeComponent
  @extends BaseModeComponent
*/
let CswModeComponent = BaseModeComponent.extend({

  layout,

  /**
    Array of property names that will be bound from parentView.
    @property bindingProperties
    @type String[]
    @default ['cswConnections', 'leafletMap']
  */
  bindingProperties: ['cswConnections', 'leafletMap'],

  /**
    Loaded CSW connections.
    @property cswConnections
    @type Object[]
    @default null
  */
  cswConnections: null,

  /**
    Leaflet map.
    @property leafletMap
    @type L.Map
    @default null
  */
  leafletMap: null,

  store: Ember.inject.service(),
  metadataTestRecordCount:5,
  _metadataRecords:[],
  _metadataRecordsName:[],
  _metadataRecordsName:'',

  init: function() {
    this._super();
    let metadataArray=[];
    let metadataArrayNames=[];
    /*
    for (var i = 0; i < this.metadataTestRecordCount; i++) {
      let metadata = this.createTestMetadata(i);
      metadataArray.push(metadata);
      metadataArrayNames.push(metadata.get('name'));
    }
    */
    let store = this.get('store');
    let loadedMetadata = store.peekAll('new-platform-flexberry-g-i-s-layer-metadata');
    loadedMetadata.forEach((item, index) => {
      metadataArray.push(item);
      metadataArrayNames.push(item.get('name'));
    });
    Ember.set(this, '_metadataRecords',metadataArray);
    Ember.set(this, '_metadataRecordsNames',metadataArrayNames);
    console.log(this._metadataRecordsNames);
   },

  createTestMetadata(number){
    let store = this.get('store');
    let testMapLayer = store.createRecord('new-platform-flexberry-g-i-s-layer-metadata', {
      syncDownTime: undefined,
      readOnly: undefined,
      createTime: undefined,
      creator: undefined,
      editTime: undefined,
      editor: undefined,
      name: 'TestName - '+ number,
      description: 'Descr Test',
      keyWords: 'Keyword Test',
      anyText: 'Anytext test',
      type: 'wms-wfs',
      settings:'{"opacity":1,"bounds": [["43","39"],["44","41"]],"wgs84bbox":[["43","39"],["44","41"]],"bbox":[[null,null],[null,null]],"searchSettings":{"canBeSearched":false,"canBeContextSearched":false,"contextSearchFields":null,"searchFields":null}}',
      scale:0,
      coordinateReferenceSystem:'{"code":"EPSG:3857","definition":null}',
      boundingBox:null
    });
    testMapLayer.get('linkMetadata').pushObject(this.createTestLinkMetadata(number));
    console.log('Metadata Create');
    return testMapLayer;
  },

  createTestLinkMetadata(number){
    let store = this.get('store');
    let testLinkLayer = store.createRecord('new-platform-flexberry-g-i-s-link-metadata', {
      allowShow: true,
      mapObjectSetting: undefined,
    });
    testLinkLayer.get('parameters').pushObject(this.createTestParametersMetadata(number));
    console.log('LinkMetadata Create');
    return testLinkLayer;
  },

  createTestParametersMetadata(number){
    let store = this.get('store');
    let testParamLayer = store.createRecord('new-platform-flexberry-g-i-s-parameter-metadata', {
      objectField: undefined,
      layerField: undefined,
      expression: 'test params - '+number,
      queryKey: undefined,
      linkField: undefined
    });
    console.log('Param Create');
    return testParamLayer;
  },

  loadTestMetadata(){
    console.log('Record loaded');
    return this.get('store').peekRecord('new-platform-flexberry-g-i-s-layer-metadata',9384);
  },

  getLayerFromMetadata(metadata){
    let mapLayer = {
      name: metadata.get('name'),
      description: metadata.get('description'),
      keyWords: metadata.get('keyWords'),
      type: metadata.get('type'),
      settings: metadata.get('settings'),
      scale:metadata.get('scale'),
      coordinateReferenceSystem:metadata.get('coordinateReferenceSystem'),
      boundingBox:metadata.get('boundingBox'),
      layerLink: []
    };
    this.addLinkMetadata(mapLayer, metadata.get('linkMetadata'));
    console.log('Metadata Convert');
    return mapLayer;
  },

  addLinkMetadata(layerModel, linkMetadata) {
      console.log('addLinkMetadata start');
      linkMetadata.forEach((item) => {
        let newLayerLink = {
          allowShow: item.get('allowShow'),
          mapObjectSetting: item.get('mapObjectSetting'),
          parameters:[{name:'test'}]

      };
      this.addLinkParametersMetadata(newLayerLink, item.get('parameters'));
      layerModel.layerLink.push(newLayerLink);
    });
  },

  addLinkParametersMetadata(layerLinkModel, parameters) {
    console.log('addParameters start');
    parameters.forEach((item) => {
      let newLinkParameter = {
        objectField: item.get('objectField'),
        layerField: item.get('layerField'),
        expression: item.get('expression'),
        queryKey: item.get('queryKey'),
        linkField: item.get('linkField')
      };
      layerLinkModel.parameters.push(newLinkParameter);
    });
  },

  actions: {

    createMetadataRecords(){
      let metadataArray=[{}];
      let metadataArrayNames=[];
      for (var i = 0; i < this.metadataTestRecordCount; i++) {
        let metadata = this.createTestMetadata(i);
        metadataArray.push(metadata);
        metadataArrayNames.push(metadata.get('name'));
      }
      Ember.set(this, '_metadataRecords',metadataArray);
      Ember.set(this, '_metadataRecordsNames',metadataArrayNames);

    },

    metadataSelect(){
      Ember.set(this, '_selectedRecordNumber',this._metadataRecordsNames.indexOf(this._metadataRecordsName));
    },

    createLayerFromMetadata(){
    //  let layer = this.getLayerFromMetadata(this._metadataRecords[this._selectedRecordNumber]);
      let layer = this.getLayerFromMetadata(
        this._metadataRecords[this._metadataRecordsNames.indexOf(this._metadataRecordsName)]
      );
      this.sendAction('editingFinished', layer);
    }

  }

});

// Add component's CSS-class names as component's class static constants
// to make them available outside of the component instance.
  CswModeComponent.reopenClass({
  flexberryClassNames,
  layout
});

export default CswModeComponent;
<commit_msg>Fix some validation errors<commit_after>/**
  @module ember-flexberry-gis
*/
import Ember from 'ember';
import BaseModeComponent from 'ember-flexberry-gis/components/layers-dialogs/edit-modes/base';
import layout from '../../../templates/components/layers-dialogs/edit-modes/test-mode';

/**
  Component's CSS-classes names.
  JSON-object containing string constants with CSS-classes names related to component's .hbs markup elements.
  @property {Object} flexberryClassNames
  @property {String} flexberryClassNames.prefix Component's CSS-class names prefix ('flexberry-edit-mode-csw').
  @property {String} flexberryClassNames.wrapper Component's wrapping
 CSS-class name (null, because component is tagless).
  @readonly
  @static
  @for CswModeComponent
*/
const flexberryClassNamesPrefix = 'flexberry-edit-mode-csw';
const flexberryClassNames = {
  prefix: flexberryClassNamesPrefix,
  wrapper: null
};

/**
  @class CswModeComponent
  @extends BaseModeComponent
*/
let CswModeComponent = BaseModeComponent.extend({

  layout,
  /**
    Array of property names that will be bound from parentView.
    @property bindingProperties
    @type String[]
    @default ['cswConnections', 'leafletMap']
  */
  bindingProperties: ['cswConnections', 'leafletMap'],

  /**
    Loaded CSW connections.
    @property cswConnections
    @type Object[]
    @default null
  */
  cswConnections: null,

  /**
    Leaflet map.
    @property leafletMap
    @type L.Map
    @default null
  */
  leafletMap: null,

  store: Ember.inject.service(),
  metadataTestRecordCount:5,
  _metadataRecords:[],
  _metadataRecordsName:[],

  init: function() {
    this._super();
    let metadataArray = [];
    let metadataArrayNames = [];
    let store = this.get('store');
    let loadedMetadata = store.peekAll('new-platform-flexberry-g-i-s-layer-metadata');
    loadedMetadata.forEach((item, index) => {
      metadataArray.push(item);
      metadataArrayNames.push(item.get('name'));
    });
    Ember.set(this, '_metadataRecords', metadataArray);
    Ember.set(this, '_metadataRecordsNames', metadataArrayNames);
  },

  getLayerFromMetadata(metadata) {
    let mapLayer = {
      name: metadata.get('name'),
      description: metadata.get('description'),
      keyWords: metadata.get('keyWords'),
      type: metadata.get('type'),
      settings: metadata.get('settings'),
      scale:metadata.get('scale'),
      coordinateReferenceSystem:metadata.get('coordinateReferenceSystem'),
      boundingBox:metadata.get('boundingBox'),
      layerLink: []
    };
    this.addLinkMetadata(mapLayer, metadata.get('linkMetadata'));
    console.log('Metadata Convert');
    return mapLayer;
  },

  addLinkMetadata(layerModel, linkMetadata) {
    console.log('addLinkMetadata start');
    linkMetadata.forEach((item) => {
      let newLayerLink = {
        allowShow: item.get('allowShow'),
        mapObjectSetting: item.get('mapObjectSetting'),
        parameters:[{ name: 'test' }]
      };
      this.addLinkParametersMetadata(newLayerLink, item.get('parameters'));
      layerModel.layerLink.push(newLayerLink);
    });
  },

  addLinkParametersMetadata(layerLinkModel, parameters) {
    console.log('addParameters start');
    parameters.forEach((item) => {
      let newLinkParameter = {
        objectField: item.get('objectField'),
        layerField: item.get('layerField'),
        expression: item.get('expression'),
        queryKey: item.get('queryKey'),
        linkField: item.get('linkField')
      };
      layerLinkModel.parameters.push(newLinkParameter);
    });
  },

  actions: {

    createLayerFromMetadata() {
      let layer = this.getLayerFromMetadata(
        this._metadataRecords[this._metadataRecordsNames.indexOf(this._metadataRecordsName)]
      );
      this.sendAction('editingFinished', layer);
    }
  }

});

// Add component's CSS-class names as component's class static constants
// to make them available outside of the component instance.
CswModeComponent.reopenClass({
  flexberryClassNames,
  layout
});

export default CswModeComponent;
<|endoftext|>


<commit_before>import Ember from 'ember';
import layout from './template';

export default Ember.Component.extend({
  layout: layout,
  tagName: 'button',
  classNames: ['dropdown-toggle'],
  classNameBindings: ['computedStyle'],
  attributeBindings: [
    'type',
    'data-toggle',
    'haspopup',
    'aria-expanded',
    'data-test-id'
  ],

  //Defaults
  type: 'button',
  style: null,
  size: null,

  //Constructors
  classPrefix: 'btn',
  styles: ['primary', 'secondary', 'success', 'warning', 'danger', 'info', 'link'],

  // Data Attrs, required by bootstrap
  'data-toggle': 'dropdown',

  // Accessibility
  haspopup: 'true',
  'aria-expanded': 'false',

  // Computed
  computedStyle: Ember.computed('style', function(){
    // Builds size button class
    // Should not have any btn class by default to allow for custom classing

    if (!this.get('style')) {
      return false;
    }

    var styles = this.get('styles');
    var findStyle = this.get('style');
    var resolvedStyle = findStyle;

    if (styles.indexOf(findStyle) === -1) {
      resolvedStyle = 'secondary';
      Ember.Logger.warn('rui-dropdown-trigger: You specified and unsupported \'style\' property so we\'ve defaulted to the secondary style: choose from \'primary secondary success warning danger info link.\'');
    }

    return 'btn ' + this.get('classPrefix') + '-' + resolvedStyle;
  }),
});
<commit_msg>Adds the `aria-` back to has popup<commit_after>import Ember from 'ember';
import layout from './template';

export default Ember.Component.extend({
  layout: layout,
  tagName: 'button',
  classNames: ['dropdown-toggle'],
  classNameBindings: ['computedStyle'],
  attributeBindings: [
    'type',
    'data-toggle',
    'aria-haspopup',
    'aria-expanded',
    'data-test-id'
  ],

  //Defaults
  type: 'button',
  style: null,
  size: null,

  //Constructors
  classPrefix: 'btn',
  styles: ['primary', 'secondary', 'success', 'warning', 'danger', 'info', 'link'],

  // Data Attrs, required by bootstrap
  'data-toggle': 'dropdown',

  // Accessibility
  'aria-haspopup': 'true',
  'aria-expanded': 'false',

  // Computed
  computedStyle: Ember.computed('style', function(){
    // Builds size button class
    // Should not have any btn class by default to allow for custom classing

    if (!this.get('style')) {
      return false;
    }

    var styles = this.get('styles');
    var findStyle = this.get('style');
    var resolvedStyle = findStyle;

    if (styles.indexOf(findStyle) === -1) {
      resolvedStyle = 'secondary';
      Ember.Logger.warn('rui-dropdown-trigger: You specified and unsupported \'style\' property so we\'ve defaulted to the secondary style: choose from \'primary secondary success warning danger info link.\'');
    }

    return 'btn ' + this.get('classPrefix') + '-' + resolvedStyle;
  }),
});
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var WLError = require('./WLError');
var WLUsageError = require('./WLUsageError');
var util = require('util');
var _ = require('lodash');

/**
 * WLValidationError
 *
 * @extends WLError
 */
function WLValidationError (properties) {

  // Call superclass
  WLValidationError.super_.call(this, properties);

  // Ensure valid usage
  if ( typeof this.invalidAttributes !== 'object' ) {
    return new WLUsageError({
      reason: 'An `invalidAttributes` object must be passed into the constructor for `WLValidationError`'
    });
  }

  // Customize the `reason` based on the # of invalid attributes
  // (`reason` may not be overridden)
  var isSingular = this.length === 1;
  this.reason = util.format('%d attribute%s %s invalid',
    this.length,
    isSingular ? '' : 's',
    isSingular ? 'is' : 'are');

  // Always apply the 'E_VALIDATION' error code, even if it was overridden.
  this.code = 'E_VALIDATION';

  // Status may be overridden.
  this.status = properties.status || 400;
}
util.inherits(WLValidationError, WLError);

/**
 * `rules`
 *
 * @return {Object[Array[String]]} dictionary of validation rule ids, indexed by attribute
 */
WLValidationError.prototype.__defineGetter__('rules', function(){
  return _.mapValues(this.invalidAttributes, function (rules, attrName) {
    return _.pluck(rules, 'rule');
  });
});

/**
 * `messages` (aka `errors`)
 *
 * @return {Object[Array[String]]} dictionary of validation messages, indexed by attribute
 */
WLValidationError.prototype.__defineGetter__('messages', function(){
  return _.mapValues(this.invalidAttributes, function (rules, attrName) {
    return _.pluck(rules, 'message');
  });
});
WLValidationError.prototype.__defineGetter__('errors', function(){
  return this.messages;
});

/**
 * `attributes` (aka `keys`)
 *
 * @return {Array[String]} of invalid attribute names
 */
WLValidationError.prototype.__defineGetter__('attributes', function(){
  return _.keys(this.invalidAttributes);
});
WLValidationError.prototype.__defineGetter__('keys', function(){
  return this.attributes;
});

/**
 * `.length`
 *
 * @return {Integer} number of invalid attributes
 */
WLValidationError.prototype.__defineGetter__('length', function(){
  return this.attributes.length;
});

/**
 * `.ValidationError`
 * (backwards-compatibility)
 *
 * @return {Object[Array[Object]]} number of invalid attributes
 */
WLValidationError.prototype.__defineGetter__('ValidationError', function(){
  //
  // TODO:
  // Down the road- emit deprecation event here--
  // (will log information about new error handling options)
  //
  return this.invalidAttributes;
});

/**
 * [toJSON description]
 * @type {[type]}
 */
WLValidationError.prototype.toJSON =
WLValidationError.prototype.toPOJO =
function () {
  return {
    error: this.code,
    summary: this.reason,
    status: this.status,
    invalidAttributes: this.messages
  };
};

/**
 * @return {String} a detailed explanation of this error
 */
WLValidationError.prototype.explain = function () {

  return _.reduce(this.messages, function (memo, messages, attrName) {
    memo += attrName + ':\n';
    memo += _.reduce(messages, function (memo, message) {
      memo += '  ' + message + '\n';
      return memo;
    }, '');
    return memo;
  }, '');
};

module.exports = WLValidationError;
<commit_msg>Expand toPOJO to include rule/value as well as message (better to start with more and trim it back later).  Special accessors still exist (keys/attributes/messages/rules)<commit_after>/**
 * Module dependencies
 */

var WLError = require('./WLError');
var WLUsageError = require('./WLUsageError');
var util = require('util');
var _ = require('lodash');

/**
 * WLValidationError
 *
 * @extends WLError
 */
function WLValidationError (properties) {

  // Call superclass
  WLValidationError.super_.call(this, properties);

  // Ensure valid usage
  if ( typeof this.invalidAttributes !== 'object' ) {
    return new WLUsageError({
      reason: 'An `invalidAttributes` object must be passed into the constructor for `WLValidationError`'
    });
  }

  // Customize the `reason` based on the # of invalid attributes
  // (`reason` may not be overridden)
  var isSingular = this.length === 1;
  this.reason = util.format('%d attribute%s %s invalid',
    this.length,
    isSingular ? '' : 's',
    isSingular ? 'is' : 'are');

  // Always apply the 'E_VALIDATION' error code, even if it was overridden.
  this.code = 'E_VALIDATION';

  // Status may be overridden.
  this.status = properties.status || 400;

  // Ensure messages exist for each invalidAttribute
  this.invalidAttributes = _.mapValues(this.invalidAttributes, function (rules, attrName) {
    return _.map(rules, function (rule) {
      if (!rule.message) {
        rule.message = util.format('A record with that `%s` already exists (`%s`).', attrName, rule.value);
      }
      return rule;
    });
  });

}
util.inherits(WLValidationError, WLError);

/**
 * `rules`
 *
 * @return {Object[Array[String]]} dictionary of validation rule ids, indexed by attribute
 */
WLValidationError.prototype.__defineGetter__('rules', function(){
  return _.mapValues(this.invalidAttributes, function (rules, attrName) {
    return _.pluck(rules, 'rule');
  });
});

/**
 * `messages` (aka `errors`)
 *
 * @return {Object[Array[String]]} dictionary of validation messages, indexed by attribute
 */
WLValidationError.prototype.__defineGetter__('messages', function(){
  return _.mapValues(this.invalidAttributes, function (rules, attrName) {
    return _.pluck(rules, 'message');
  });
});
WLValidationError.prototype.__defineGetter__('errors', function(){
  return this.messages;
});

/**
 * `attributes` (aka `keys`)
 *
 * @return {Array[String]} of invalid attribute names
 */
WLValidationError.prototype.__defineGetter__('attributes', function(){
  return _.keys(this.invalidAttributes);
});
WLValidationError.prototype.__defineGetter__('keys', function(){
  return this.attributes;
});

/**
 * `.length`
 *
 * @return {Integer} number of invalid attributes
 */
WLValidationError.prototype.__defineGetter__('length', function(){
  return this.attributes.length;
});

/**
 * `.ValidationError`
 * (backwards-compatibility)
 *
 * @return {Object[Array[Object]]} number of invalid attributes
 */
WLValidationError.prototype.__defineGetter__('ValidationError', function(){
  //
  // TODO:
  // Down the road- emit deprecation event here--
  // (will log information about new error handling options)
  //
  return this.invalidAttributes;
});

/**
 * [toJSON description]
 * @type {[type]}
 */
WLValidationError.prototype.toJSON =
WLValidationError.prototype.toPOJO =
function () {
  return {
    error: this.code,
    summary: this.reason,
    status: this.status,
    invalidAttributes: this.invalidAttributes
  };
};

/**
 * @return {String} a detailed explanation of this error
 */
WLValidationError.prototype.explain = function () {

  return _.reduce(this.messages, function (memo, messages, attrName) {
    memo += attrName + ':\n';
    memo += _.reduce(messages, function (memo, message) {
      memo += '  ' + message + '\n';
      return memo;
    }, '');
    return memo;
  }, '');
};

module.exports = WLValidationError;
<|endoftext|>


<commit_before>(function () {
    'use strict';

    /**
     * @param {User} user
     * @param {ModalManager} modalManager
     * @param {Waves} waves
     * @param {Router} router
     * @param {$rootScope.Scope} $rootScope
     * @param {app.utils} utils
     * @return {ModalRouter}
     */
    const factory = function (user, modalManager, waves, router, $rootScope, utils) {

        class ModalRouter {

            constructor() {
                /**
                 * @type {boolean}
                 * @private
                 */
                this._sleep = false;
                /**
                 * @type {{url: string, search: string}}
                 * @private
                 */
                this._firstUrl = ModalRouter._getUrlData();
                router.registerRouteHash(this._wrapClose(this._getRoutes()));
            }

            initialize() {
                window.addEventListener('hashchange', () => {
                    if (!this._sleep) {
                        this._apply(ModalRouter._getUrlData());
                    }
                }, false);

                waves.node.assets.userBalances().then(() => {
                    this._apply(this._firstUrl);
                });

                if (WavesApp.isDesktop()) {
                    window.listenMainProcessEvent((eventType, urlString) => {
                        const { hash } = utils.parseElectronUrl(urlString);
                        if (hash) {
                            this._apply(ModalRouter._getUrlData(hash.replace('#', '')));
                        }
                    });
                }
            }

            /**
             * @private
             */
            _apply({ url, search }) {
                return router.apply(url, utils.parseSearchParams(search));
            }

            /**
             * @return {Object.<function>}
             * @private
             */
            _getRoutes() {
                return {
                    '/send': () => modalManager.showSendAsset(),
                    '/send/:assetId': ({ assetId }, search) => {
                        return modalManager.showSendAsset({ ...search, assetId });
                    },
                    '/asset/:assetId': ({ assetId }) => {
                        return waves.node.assets.getAsset(assetId).then((asset) => {
                            return modalManager.showAssetInfo(asset);
                        });
                    },
                    // '/receive': () => modalManager.showReceiveAsset(user), // TODO : decide on that
                    '/account': () => modalManager.showAccountInfo(),
                    '/gateway/auth': (params, search) => modalManager.showGatewaySign(search)
                };
            }

            /**
             * @param hash
             * @returns {*}
             * @private
             */
            _wrapClose(hash) {
                Object.keys(hash).forEach((key) => {
                    const handler = hash[key];
                    hash[key] = (...args) => {
                        this._sleep = true;
                        return user.onLogin().then(() => {
                            const result = handler(...args);
                            if (!result || !result.then || typeof result.then !== 'function') {
                                throw new Error('Modal result mast be a promise!');
                            }
                            const cb = () => {
                                this._sleep = false;
                                location.hash = '';
                            };
                            result.then(cb, cb);
                            return result;
                        });
                    };
                });
                return hash;
            }

            /**
             * @param {string} [fromUrl]
             * @return {{url: string, search: string}}
             * @private
             */
            static _getUrlData(fromUrl) {
                const fullUrl = `/${decodeURIComponent(fromUrl || ModalRouter._getLocation())}`;
                const [url, search] = fullUrl.split('?');
                return { url, search };
            }

            static _getLocation() {
                if (WavesApp.isDesktop()) {
                    const lastIndex = location.hash.lastIndexOf('#');
                    const firstIndex = location.hash.indexOf('#');
                    return lastIndex > firstIndex ? location.hash.slice(lastIndex + 1) : '';
                } else {
                    return location.hash.replace('#', '');
                }
            }

        }

        return ModalRouter;
    };

    factory.$inject = ['user', 'modalManager', 'waves', 'router', '$rootScope', 'utils'];

    angular.module('app.ui').factory('ModalRouter', factory);
})();
<commit_msg>CLIENT-887: add empty line for build with jenkins<commit_after>(function () {
    'use strict';

    /**
     * @param {User} user
     * @param {ModalManager} modalManager
     * @param {Waves} waves
     * @param {Router} router
     * @param {$rootScope.Scope} $rootScope
     * @param {app.utils} utils
     * @return {ModalRouter}
     */
    const factory = function (user, modalManager, waves, router, $rootScope, utils) {

        class ModalRouter {

            constructor() {
                /**
                 * @type {boolean}
                 * @private
                 */
                this._sleep = false;
                /**
                 * @type {{url: string, search: string}}
                 * @private
                 */
                this._firstUrl = ModalRouter._getUrlData();
                router.registerRouteHash(this._wrapClose(this._getRoutes()));
            }

            initialize() {
                window.addEventListener('hashchange', () => {
                    if (!this._sleep) {
                        this._apply(ModalRouter._getUrlData());
                    }
                }, false);

                waves.node.assets.userBalances().then(() => {
                    this._apply(this._firstUrl);
                });

                if (WavesApp.isDesktop()) {
                    window.listenMainProcessEvent((eventType, urlString) => {
                        const { hash } = utils.parseElectronUrl(urlString);
                        if (hash) {
                            this._apply(ModalRouter._getUrlData(hash.replace('#', '')));
                        }
                    });
                }
            }

            /**
             * @private
             */
            _apply({ url, search }) {
                return router.apply(url, utils.parseSearchParams(search));
            }

            /**
             * @return {Object.<function>}
             * @private
             */
            _getRoutes() {
                return {
                    '/send': () => modalManager.showSendAsset(),
                    '/send/:assetId': ({ assetId }, search) => {
                        return modalManager.showSendAsset({ ...search, assetId });
                    },
                    '/asset/:assetId': ({ assetId }) => {
                        return waves.node.assets.getAsset(assetId).then((asset) => {
                            return modalManager.showAssetInfo(asset);
                        });
                    },
                    // '/receive': () => modalManager.showReceiveAsset(user), // TODO : decide on that
                    '/account': () => modalManager.showAccountInfo(),
                    '/gateway/auth': (params, search) => modalManager.showGatewaySign(search)
                };
            }

            /**
             * @param hash
             * @returns {*}
             * @private
             */
            _wrapClose(hash) {
                Object.keys(hash).forEach((key) => {
                    const handler = hash[key];
                    hash[key] = (...args) => {
                        this._sleep = true;
                        return user.onLogin().then(() => {
                            const result = handler(...args);
                            if (!result || !result.then || typeof result.then !== 'function') {
                                throw new Error('Modal result mast be a promise!');
                            }
                            const cb = () => {
                                this._sleep = false;
                                location.hash = '';
                            };
                            result.then(cb, cb);
                            return result;
                        });
                    };
                });
                return hash;
            }

            /**
             * @param {string} [fromUrl]
             * @return {{url: string, search: string}}
             * @private
             */
            static _getUrlData(fromUrl) {
                const fullUrl = `/${decodeURIComponent(fromUrl || ModalRouter._getLocation())}`;
                const [url, search] = fullUrl.split('?');
                return { url, search };
            }

            static _getLocation() {
                if (WavesApp.isDesktop()) {
                    const lastIndex = location.hash.lastIndexOf('#');
                    const firstIndex = location.hash.indexOf('#');
                    return lastIndex > firstIndex ? location.hash.slice(lastIndex + 1) : '';
                } else {
                    return location.hash.replace('#', '');
                }
            }

        }

        return ModalRouter;
    };

    factory.$inject = ['user', 'modalManager', 'waves', 'router', '$rootScope', 'utils'];

    angular.module('app.ui').factory('ModalRouter', factory);
})();
<|endoftext|>


<commit_before>/**
 *
 */
 window.addEvent("load",function(){
	    	  var content=$('pushmenu');

	    	    $('pushmenu-btn').addEvent('click',function(){

	    	    	size=content.getSize();
	    	    	PushBoxWindow.open(content, {handler: "append", size: size, anchor:this, push:true, classWindow:'pushmenu'}).addEvent('close',function(){
	    	    		//content.parentNode.removeChild(content);
	    	    	    $('pushmenu-parent').appendChild(content);
		    	    });

		    	});

		 });<commit_msg>added global method window.openLoginWindow so it can be integrated into other items<commit_after>/**
 *
 */
window.openLoginWindow=function(){
	var content=$('pushmenu');
	size=content.getSize();
	PushBoxWindow.open(content, {handler: "append", size: size, anchor:this, push:true, classWindow:'pushmenu'}).addEvent('close',function(){
		//content.parentNode.removeChild(content);
		$('pushmenu-parent').appendChild(content);
	});

};
window.addEvent("load",function(){

	$('pushmenu-btn').addEvent('click', window.openLoginWindow);

});<|endoftext|>


<commit_before>(function() {
    var global = this;
    var $ = global.$;
    var console = global.console || {log: function() {}};

    var AjaxUploadWidget = global.AjaxUploadWidget = function(element, options) {
        this.options = {
            changeButtonText: 'Change',
            removeButtonText: 'Remove',
            previewAreaClass: 'ajax-upload-preview-area',
            previewFilenameLength: 30,
            onUpload: null, // right before uploading to the server
            onComplete: null,
            onError: null,
            onRemove: null
        };
        $.extend(this.options, options);
        this.$element = $(element);
        this.initialize();
    };

    AjaxUploadWidget.prototype.DjangoAjaxUploadError = function(message) {
        this.name = 'DjangoAjaxUploadError';
        this.message = message;
    };
    AjaxUploadWidget.prototype.DjangoAjaxUploadError.prototype = new Error();
    AjaxUploadWidget.prototype.DjangoAjaxUploadError.prototype.constructor = AjaxUploadWidget.prototype.DjangoAjaxUploadError;

    AjaxUploadWidget.prototype.initialize = function() {
        var self = this;
        this.name = this.$element.attr('name');

        // Create a hidden field to contain our uploaded file name
        this.$hiddenElement = $('<input type="hidden"/>')
            .attr('name', this.name)
            .val(this.$element.data('filename'));
        this.$element.attr('name', ''); // because we don't want to conflict with our hidden field
        this.$element.after(this.$hiddenElement);

        // Initialize preview area and action buttons
        this.$previewArea = $('

');
        this.$element.before(this.$previewArea);

        // Listen for when a file is selected, and perform upload
        this.$element.on('change', function(evt) {
            self.upload();
        });
        this.$changeButton = $('<button type="button" class="btn-change"></button>')
            .text(this.options.changeButtonText)
            .on('click', function(evt) {
                self.$element.show();
                $(this).hide();
            });
        this.$element.after(this.$changeButton);

        this.$removeButton = $('<button type="button" class="btn-remove"></button>')
            .text(this.options.removeButtonText)
            .on('click', function(evt) {
                self.$hiddenElement.val('');
                self.displaySelection();
                if(self.options.onRemove) self.options.onRemove.call(self);
            });
        this.$changeButton.after(this.$removeButton);

        this.displaySelection();
    };

    AjaxUploadWidget.prototype.upload = function() {
        var self = this;
        if(!this.$element.val()) return;
        if(this.options.onUpload) this.options.onUpload.call(this);
        this.$element.attr('name', 'file');
        $.ajax(this.$element.data('upload-url'), {
            iframe: true,
            files: this.$element,
            processData: false,
            type: 'POST',
            dataType: 'json',
            success: function(data) { self.uploadDone(data); },
            error: function(data) { self.uploadFail(data); }
        });
    };

    AjaxUploadWidget.prototype.uploadDone = function(data) {
        // This handles errors as well because iframe transport does not
        // distinguish between 200 response and other errors
        if(data.errors) {
            if(this.options.onError) {
                this.options.onError.call(this, data);
            } else {
                console.log('Upload failed:');
                console.log(data);
            }
        } else {
            this.$hiddenElement.val(data.path);
            var tmp = this.$element;
            this.$element = this.$element.clone(true).val('');
            tmp.replaceWith(this.$element);
            this.displaySelection();
            if(this.options.onComplete) this.options.onComplete.call(this, data.path);
        }
    };

    AjaxUploadWidget.prototype.uploadFail = function(xhr) {
        if(this.options.onError) {
            this.options.onError.call(this);
        } else {
            console.log('Upload failed:');
            console.log(xhr);
        }
    };

    AjaxUploadWidget.prototype.displaySelection = function() {
        var filename = this.$hiddenElement.val();

        if(filename !== '') {
            this.$previewArea.empty();
            this.$previewArea.append(this.generateFilePreview(filename));

            this.$previewArea.show();
            this.$changeButton.show();
            if(this.$element.data('required') === 'True') {
                this.$removeButton.hide();
            } else {
                this.$removeButton.show();
            }
            this.$element.hide();
        } else {
            this.$previewArea.hide();
            this.$changeButton.hide();
            this.$removeButton.hide();
            this.$element.show();
        }
    };

    AjaxUploadWidget.prototype.generateFilePreview = function(filename) {
        // Returns the html output for displaying the given uploaded filename to the user.
        var prettyFilename = this.prettifyFilename(filename);
        var output = ''+prettyFilename+'';
        $.each(['jpg', 'jpeg', 'png', 'gif'], function(i, ext) {
            if(filename.slice(-3) == ext) {
                output += '';
                return false;
            }
        });
        output += '';
        return output;
    };

    AjaxUploadWidget.prototype.prettifyFilename = function(filename) {
        // Get rid of the folder names
        var cleaned = filename.slice(filename.lastIndexOf('/')+1);

        // Strip the random hex in the filename inserted by the backend (if present)
        var re = /^[a-f0-9]{32}\-/i;
        cleaned = cleaned.replace(re, '');

        // Truncate the filename
        var maxChars = this.options.previewFilenameLength;
        var elipsis = '...';
        if(cleaned.length > maxChars) {
            cleaned = elipsis + cleaned.slice((-1 * maxChars) + elipsis.length);
        }
        return cleaned;
    };

    AjaxUploadWidget.autoDiscover = function(options) {
        $('input[type="file"].ajax-upload').each(function(index, element) {
            new AjaxUploadWidget(element, options);
        });
    };
}).call(this);
<commit_msg>Moved onRemove callback to occur before removable and made remove and upload cancel if the callbacks return false<commit_after>(function() {
    var global = this;
    var $ = global.$;
    var console = global.console || {log: function() {}};

    var AjaxUploadWidget = global.AjaxUploadWidget = function(element, options) {
        this.options = {
            changeButtonText: 'Change',
            removeButtonText: 'Remove',
            previewAreaClass: 'ajax-upload-preview-area',
            previewFilenameLength: 30,
            onUpload: null, // right before uploading to the server
            onComplete: null,
            onError: null,
            onRemove: null
        };
        $.extend(this.options, options);
        this.$element = $(element);
        this.initialize();
    };

    AjaxUploadWidget.prototype.DjangoAjaxUploadError = function(message) {
        this.name = 'DjangoAjaxUploadError';
        this.message = message;
    };
    AjaxUploadWidget.prototype.DjangoAjaxUploadError.prototype = new Error();
    AjaxUploadWidget.prototype.DjangoAjaxUploadError.prototype.constructor = AjaxUploadWidget.prototype.DjangoAjaxUploadError;

    AjaxUploadWidget.prototype.initialize = function() {
        var self = this;
        this.name = this.$element.attr('name');

        // Create a hidden field to contain our uploaded file name
        this.$hiddenElement = $('<input type="hidden"/>')
            .attr('name', this.name)
            .val(this.$element.data('filename'));
        this.$element.attr('name', ''); // because we don't want to conflict with our hidden field
        this.$element.after(this.$hiddenElement);

        // Initialize preview area and action buttons
        this.$previewArea = $('

');
        this.$element.before(this.$previewArea);

        // Listen for when a file is selected, and perform upload
        this.$element.on('change', function(evt) {
            self.upload();
        });
        this.$changeButton = $('<button type="button" class="btn-change"></button>')
            .text(this.options.changeButtonText)
            .on('click', function(evt) {
                self.$element.show();
                $(this).hide();
            });
        this.$element.after(this.$changeButton);

        this.$removeButton = $('<button type="button" class="btn-remove"></button>')
            .text(this.options.removeButtonText)
            .on('click', function(evt) {
                if(self.options.onRemove) {
                    var result = self.options.onRemove.call(self);
                    if(result === false) return;
                }
                self.$hiddenElement.val('');
                self.displaySelection();
            });
        this.$changeButton.after(this.$removeButton);

        this.displaySelection();
    };

    AjaxUploadWidget.prototype.upload = function() {
        var self = this;
        if(!this.$element.val()) return;
        if(this.options.onUpload) {
            var result = this.options.onUpload.call(this);
            if(result === false) return;
        }
        this.$element.attr('name', 'file');
        $.ajax(this.$element.data('upload-url'), {
            iframe: true,
            files: this.$element,
            processData: false,
            type: 'POST',
            dataType: 'json',
            success: function(data) { self.uploadDone(data); },
            error: function(data) { self.uploadFail(data); }
        });
    };

    AjaxUploadWidget.prototype.uploadDone = function(data) {
        // This handles errors as well because iframe transport does not
        // distinguish between 200 response and other errors
        if(data.errors) {
            if(this.options.onError) {
                this.options.onError.call(this, data);
            } else {
                console.log('Upload failed:');
                console.log(data);
            }
        } else {
            this.$hiddenElement.val(data.path);
            var tmp = this.$element;
            this.$element = this.$element.clone(true).val('');
            tmp.replaceWith(this.$element);
            this.displaySelection();
            if(this.options.onComplete) this.options.onComplete.call(this, data.path);
        }
    };

    AjaxUploadWidget.prototype.uploadFail = function(xhr) {
        if(this.options.onError) {
            this.options.onError.call(this);
        } else {
            console.log('Upload failed:');
            console.log(xhr);
        }
    };

    AjaxUploadWidget.prototype.displaySelection = function() {
        var filename = this.$hiddenElement.val();

        if(filename !== '') {
            this.$previewArea.empty();
            this.$previewArea.append(this.generateFilePreview(filename));

            this.$previewArea.show();
            this.$changeButton.show();
            if(this.$element.data('required') === 'True') {
                this.$removeButton.hide();
            } else {
                this.$removeButton.show();
            }
            this.$element.hide();
        } else {
            this.$previewArea.hide();
            this.$changeButton.hide();
            this.$removeButton.hide();
            this.$element.show();
        }
    };

    AjaxUploadWidget.prototype.generateFilePreview = function(filename) {
        // Returns the html output for displaying the given uploaded filename to the user.
        var prettyFilename = this.prettifyFilename(filename);
        var output = ''+prettyFilename+'';
        $.each(['jpg', 'jpeg', 'png', 'gif'], function(i, ext) {
            if(filename.slice(-3) == ext) {
                output += '';
                return false;
            }
        });
        output += '';
        return output;
    };

    AjaxUploadWidget.prototype.prettifyFilename = function(filename) {
        // Get rid of the folder names
        var cleaned = filename.slice(filename.lastIndexOf('/')+1);

        // Strip the random hex in the filename inserted by the backend (if present)
        var re = /^[a-f0-9]{32}\-/i;
        cleaned = cleaned.replace(re, '');

        // Truncate the filename
        var maxChars = this.options.previewFilenameLength;
        var elipsis = '...';
        if(cleaned.length > maxChars) {
            cleaned = elipsis + cleaned.slice((-1 * maxChars) + elipsis.length);
        }
        return cleaned;
    };

    AjaxUploadWidget.autoDiscover = function(options) {
        $('input[type="file"].ajax-upload').each(function(index, element) {
            new AjaxUploadWidget(element, options);
        });
    };
}).call(this);
<|endoftext|>


<commit_before>var assets = require('./common').paths.assets;

/**
 * Style Building
 * Configuration
 * Object
 *
 * @type {{}}
 */
module.exports = {
	paths: {
		watch: assets.src  + '/scss/**/*.scss',
		src:   [assets.src + '/scss/*.scss', '!' + assets.src + '/scss/**/_*'],
		dest:  assets.dest + '/css',
		clean: assets.dest + '/css/**/*.{css,map}'
	},

	options: {
		sass: {},
		autoprefixer: {
			browsers: [
				'last 2 version',
				'safari >= 5',
				'ie >= 7',
				'opera >= 12.1',
				'ios >= 6',
				'android >= 4'
			]
		}
	}
};<commit_msg>change gulp styles config paths in order to exclude the '_tmp' files that the scss-linter creates<commit_after>var assets = require('./common').paths.assets;

/**
 * Style Building
 * Configuration
 * Object
 *
 * @type {{}}
 */
module.exports = {
	paths: {
		watch: [assets.src + '/scss/**/*.scss', '!' + assets.src + '/scss/**/*_tmp\\d+.scss'],
		src:   [assets.src + '/scss/*.scss', '!' + assets.src + '/scss/**/_*'],
		dest:  assets.dest + '/css',
		clean: assets.dest + '/css/**/*.{css,map}'
	},

	options: {
		sass: {},
		autoprefixer: {
			browsers: [
				'last 2 version',
				'safari >= 5',
				'ie >= 7',
				'opera >= 12.1',
				'ios >= 6',
				'android >= 4'
			]
		}
	}
};<|endoftext|>


<commit_before>'use strict';

// Necessary Plugins
var gulp     = require('gulp')
    ,paths   = require('../paths')
    ,stylint = require('gulp-stylint');

// Call Stylus Linter
module.exports = gulp.task('stylint', function () {
  return gulp.src('paths.source.styl')
      .pipe(stylint({config: '.stylintrc'}))
});
<commit_msg>Delete "stylint" task<commit_after><|endoftext|>


<commit_before>/* eslint class-methods-use-this: 0 */
// @flow
import React from 'react';
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';

import {
  executeCell,
  removeCell,
  toggleStickyCell,
  clearOutputs,
  changeOutputVisibility,
  changeInputVisibility,
  changeCellType,
  toggleOutputExpansion,
} from '../../actions';

type Props = {
  cell: any,
  id: string,
  type: string,
}

export default class Toolbar extends React.PureComponent {
  removeCell: () => void;
  executeCell: () => void;
  clearOutputs: () => void;
  toggleStickyCell: () => void;
  changeInputVisibility: () => void;
  changeOutputVisibility: () => void;
  changeCellType: () => void;
  dropdown: Dropdown;
  toggleOutputExpansion: () => void;

  static contextTypes = {
    store: React.PropTypes.object,
  };

  constructor(props: Props): void {
    super(props);
    this.removeCell = this.removeCell.bind(this);
    this.executeCell = this.executeCell.bind(this);
    this.clearOutputs = this.clearOutputs.bind(this);
    this.toggleStickyCell = this.toggleStickyCell.bind(this);
    this.changeInputVisibility = this.changeInputVisibility.bind(this);
    this.changeOutputVisibility = this.changeOutputVisibility.bind(this);
    this.changeCellType = this.changeCellType.bind(this);
    this.toggleOutputExpansion = this.toggleOutputExpansion.bind(this);
  }

  toggleStickyCell(): void {
    this.context.store.dispatch(toggleStickyCell(this.props.id));
  }

  removeCell(): void {
    this.context.store.dispatch(removeCell(this.props.id));
  }

  executeCell(): void {
    this.context.store.dispatch(executeCell(
                                      this.props.id,
                                      this.props.cell.get('source')));
  }

  clearOutputs(): void {
    this.dropdown.hide();
    this.context.store.dispatch(clearOutputs(this.props.id));
  }

  changeInputVisibility(): void {
    this.dropdown.hide();
    this.context.store.dispatch(changeInputVisibility(this.props.id));
  }

  changeOutputVisibility(): void {
    this.dropdown.hide();
    this.context.store.dispatch(changeOutputVisibility(this.props.id));
  }

  changeCellType(): void {
    this.dropdown.hide();
    const to = this.props.type === 'markdown' ? 'code' : 'markdown';
    this.context.store.dispatch(changeCellType(this.props.id, to));
  }

  toggleOutputExpansion(): void {
    this.context.store.dispatch(toggleOutputExpansion(this.props.id));
  }

  render(): ?React.Element<any> {
    const showPlay = this.props.type !== 'markdown';
    return (

          {showPlay &&

            <button
              onClick={this.executeCell}
              title= "execute cell"
              className="executeButton"
            >

            </button>
          }
          <button
            onClick={this.toggleStickyCell}
            title="pin cell"
            className="stickyButton"
          >

          </button>
          <button
            onClick={this.removeCell}
            title="delete cell"
            className="deleteButton"
          >

          </button>
          <Dropdown ref={(dropdown) => { this.dropdown = dropdown; }}>
            <DropdownTrigger>
              <button title="show additional actions">

              </button>
            </DropdownTrigger>
            <DropdownContent>
              {
              (this.props.type === 'code') ?

                    Clear Cell Output

                    Toggle Input Visibility

                    Toggle Output Visibility

                    Toggle Expanded Output

 : null
              }

                    Convert to {this.props.type === 'markdown' ? 'Code' : 'Markdown'} Cell

            </DropdownContent>
          </Dropdown>

    );
  }
}
<commit_msg>chore(toolbar): Satisfy linter.<commit_after>/* eslint class-methods-use-this: 0 */
// @flow
import React from 'react';
import Dropdown, { DropdownTrigger, DropdownContent } from 'react-simple-dropdown';

import {
  executeCell,
  removeCell,
  toggleStickyCell,
  clearOutputs,
  changeOutputVisibility,
  changeInputVisibility,
  changeCellType,
  toggleOutputExpansion,
} from '../../actions';

type Props = {
  cell: any,
  id: string,
  type: string,
}

export default class Toolbar extends React.PureComponent {
  removeCell: () => void;
  executeCell: () => void;
  clearOutputs: () => void;
  toggleStickyCell: () => void;
  changeInputVisibility: () => void;
  changeOutputVisibility: () => void;
  changeCellType: () => void;
  dropdown: Dropdown;
  toggleOutputExpansion: () => void;

  static contextTypes = {
    store: React.PropTypes.object,
  };

  constructor(props: Props): void {
    super(props);
    this.removeCell = this.removeCell.bind(this);
    this.executeCell = this.executeCell.bind(this);
    this.clearOutputs = this.clearOutputs.bind(this);
    this.toggleStickyCell = this.toggleStickyCell.bind(this);
    this.changeInputVisibility = this.changeInputVisibility.bind(this);
    this.changeOutputVisibility = this.changeOutputVisibility.bind(this);
    this.changeCellType = this.changeCellType.bind(this);
    this.toggleOutputExpansion = this.toggleOutputExpansion.bind(this);
  }

  toggleStickyCell(): void {
    this.context.store.dispatch(toggleStickyCell(this.props.id));
  }

  removeCell(): void {
    this.context.store.dispatch(removeCell(this.props.id));
  }

  executeCell(): void {
    this.context.store.dispatch(executeCell(
                                      this.props.id,
                                      this.props.cell.get('source')));
  }

  clearOutputs(): void {
    this.dropdown.hide();
    this.context.store.dispatch(clearOutputs(this.props.id));
  }

  changeInputVisibility(): void {
    this.dropdown.hide();
    this.context.store.dispatch(changeInputVisibility(this.props.id));
  }

  changeOutputVisibility(): void {
    this.dropdown.hide();
    this.context.store.dispatch(changeOutputVisibility(this.props.id));
  }

  changeCellType(): void {
    this.dropdown.hide();
    const to = this.props.type === 'markdown' ? 'code' : 'markdown';
    this.context.store.dispatch(changeCellType(this.props.id, to));
  }

  toggleOutputExpansion(): void {
    this.context.store.dispatch(toggleOutputExpansion(this.props.id));
  }

  render(): ?React.Element<any> {
    const showPlay = this.props.type !== 'markdown';
    return (

          {showPlay &&

            <button
              onClick={this.executeCell}
              title="execute cell"
              className="executeButton"
            >

            </button>
          }
          <button
            onClick={this.toggleStickyCell}
            title="pin cell"
            className="stickyButton"
          >

          </button>
          <button
            onClick={this.removeCell}
            title="delete cell"
            className="deleteButton"
          >

          </button>
          <Dropdown ref={(dropdown) => { this.dropdown = dropdown; }}>
            <DropdownTrigger>
              <button title="show additional actions">

              </button>
            </DropdownTrigger>
            <DropdownContent>
              {
              (this.props.type === 'code') ?

                    Clear Cell Output

                    Toggle Input Visibility

                    Toggle Output Visibility

                    Toggle Expanded Output

 : null
              }

                    Convert to {this.props.type === 'markdown' ? 'Code' : 'Markdown'} Cell

            </DropdownContent>
          </Dropdown>

    );
  }
}
<|endoftext|>


<commit_before><commit_msg>Delete Test<commit_after><|endoftext|>


<commit_before>/**
 * @license Copyright 2021 The Lighthouse 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.
 */
'use strict';

/**
 * @fileoverview
 * This gatherer collects all network and page devtools protocol traffic during the timespan/navigation.
 * This protocol log can be used to recreate the network records using lib/network-recorder.js.
 */

const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
const TraceProcessor = require('../../lib/tracehouse/trace-processor.js');

class Trace extends FRGatherer {
  /** @type {LH.Trace} */
  _trace = {traceEvents: []};

  static getDefaultTraceCategories() {
    return [
      // Exclude default categories. We'll be selective to minimize trace size
      '-*',

      // Used instead of 'toplevel' in Chrome 71+
      'disabled-by-default-lighthouse',

      // Used for Cumulative Layout Shift metric
      'loading',

      // All compile/execute events are captured by parent events in devtools.timeline..
      // But the v8 category provides some nice context for only <0.5% of the trace size
      'v8',
      // Same situation here. This category is there for RunMicrotasks only, but with other teams
      // accidentally excluding microtasks, we don't want to assume a parent event will always exist
      'v8.execute',

      // For extracting UserTiming marks/measures
      'blink.user_timing',

      // Not mandatory but not used much
      'blink.console',

      // Most of the events we need are from these two categories
      'devtools.timeline',
      'disabled-by-default-devtools.timeline',

      // Up to 450 (https://goo.gl/rBfhn4) JPGs added to the trace
      'disabled-by-default-devtools.screenshot',

      // This doesn't add its own events, but adds a `stackTrace` property to devtools.timeline events
      'disabled-by-default-devtools.timeline.stack',

      // Additional categories used by devtools. Not used by Lighthouse, but included to facilitate
      // loading traces from Lighthouse into the Performance panel.
      'disabled-by-default-devtools.timeline.frame',
      'latencyInfo',

      // A bug introduced in M92 causes these categories to crash targets on Linux.
      // See https://github.com/GoogleChrome/lighthouse/issues/12835 for full investigation.
      // 'disabled-by-default-v8.cpu_profiler',
      // 'disabled-by-default-v8.cpu_profiler.hires',
    ];
  }

  /**
   * @param {LH.Gatherer.FRProtocolSession} session
   * @return {Promise<LH.Trace>}
   */
  static async endTraceAndCollectEvents(session) {
    /** @type {Array<LH.TraceEvent>} */
    const traceEvents = [];

    /**
     * Listener for when dataCollected events fire for each trace chunk
     * @param {LH.Crdp.Tracing.DataCollectedEvent} data
     */
    const dataListener = function(data) {
      traceEvents.push(...data.value);
    };
    session.on('Tracing.dataCollected', dataListener);

    return new Promise((resolve, reject) => {
      session.once('Tracing.tracingComplete', _ => {
        session.off('Tracing.dataCollected', dataListener);
        resolve({traceEvents});
      });

      session.sendCommand('Tracing.end').catch(reject);
    });
  }

  static symbol = Symbol('Trace');

  /** @type {LH.Gatherer.GathererMeta} */
  meta = {
    symbol: Trace.symbol,
    supportedModes: ['timespan', 'navigation'],
  };

  /**
   * @param {LH.Gatherer.FRTransitionalContext} passContext
   */
  async startSensitiveInstrumentation({driver, gatherMode, settings}) {
    const traceCategories = Trace.getDefaultTraceCategories()
      .concat(settings.additionalTraceCategories || []);
    await driver.defaultSession.sendCommand('Page.enable');
    await driver.defaultSession.sendCommand('Tracing.start', {
      categories: traceCategories.join(','),
      options: 'sampling-frequency=10000', // 1000 is default and too slow.
    });

    if (gatherMode === 'timespan') {
      await driver.defaultSession.sendCommand('Tracing.recordClockSyncMarker',
        {syncId: TraceProcessor.TIMESPAN_MARKER_ID});
    }
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} passContext
   */
  async stopSensitiveInstrumentation({driver}) {
    this._trace = await Trace.endTraceAndCollectEvents(driver.defaultSession);
  }

  getArtifact() {
    return this._trace;
  }
}

module.exports = Trace;
<commit_msg>core(tracing): remove cpu_profiler.hires (#13056)<commit_after>/**
 * @license Copyright 2021 The Lighthouse 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.
 */
'use strict';

/**
 * @fileoverview
 * This gatherer collects all network and page devtools protocol traffic during the timespan/navigation.
 * This protocol log can be used to recreate the network records using lib/network-recorder.js.
 */

const FRGatherer = require('../../fraggle-rock/gather/base-gatherer.js');
const TraceProcessor = require('../../lib/tracehouse/trace-processor.js');

class Trace extends FRGatherer {
  /** @type {LH.Trace} */
  _trace = {traceEvents: []};

  static getDefaultTraceCategories() {
    return [
      // Exclude default categories. We'll be selective to minimize trace size
      '-*',

      // Used instead of 'toplevel' in Chrome 71+
      'disabled-by-default-lighthouse',

      // Used for Cumulative Layout Shift metric
      'loading',

      // All compile/execute events are captured by parent events in devtools.timeline..
      // But the v8 category provides some nice context for only <0.5% of the trace size
      'v8',
      // Same situation here. This category is there for RunMicrotasks only, but with other teams
      // accidentally excluding microtasks, we don't want to assume a parent event will always exist
      'v8.execute',

      // For extracting UserTiming marks/measures
      'blink.user_timing',

      // Not mandatory but not used much
      'blink.console',

      // Most of the events we need are from these two categories
      'devtools.timeline',
      'disabled-by-default-devtools.timeline',

      // Up to 450 (https://goo.gl/rBfhn4) JPGs added to the trace
      'disabled-by-default-devtools.screenshot',

      // This doesn't add its own events, but adds a `stackTrace` property to devtools.timeline events
      'disabled-by-default-devtools.timeline.stack',

      // Additional categories used by devtools. Not used by Lighthouse, but included to facilitate
      // loading traces from Lighthouse into the Performance panel.
      'disabled-by-default-devtools.timeline.frame',
      'latencyInfo',

      // A bug introduced in M92 causes these categories to crash targets on Linux.
      // See https://github.com/GoogleChrome/lighthouse/issues/12835 for full investigation.
      // 'disabled-by-default-v8.cpu_profiler',
    ];
  }

  /**
   * @param {LH.Gatherer.FRProtocolSession} session
   * @return {Promise<LH.Trace>}
   */
  static async endTraceAndCollectEvents(session) {
    /** @type {Array<LH.TraceEvent>} */
    const traceEvents = [];

    /**
     * Listener for when dataCollected events fire for each trace chunk
     * @param {LH.Crdp.Tracing.DataCollectedEvent} data
     */
    const dataListener = function(data) {
      traceEvents.push(...data.value);
    };
    session.on('Tracing.dataCollected', dataListener);

    return new Promise((resolve, reject) => {
      session.once('Tracing.tracingComplete', _ => {
        session.off('Tracing.dataCollected', dataListener);
        resolve({traceEvents});
      });

      session.sendCommand('Tracing.end').catch(reject);
    });
  }

  static symbol = Symbol('Trace');

  /** @type {LH.Gatherer.GathererMeta} */
  meta = {
    symbol: Trace.symbol,
    supportedModes: ['timespan', 'navigation'],
  };

  /**
   * @param {LH.Gatherer.FRTransitionalContext} passContext
   */
  async startSensitiveInstrumentation({driver, gatherMode, settings}) {
    const traceCategories = Trace.getDefaultTraceCategories()
      .concat(settings.additionalTraceCategories || []);
    await driver.defaultSession.sendCommand('Page.enable');
    await driver.defaultSession.sendCommand('Tracing.start', {
      categories: traceCategories.join(','),
      options: 'sampling-frequency=10000', // 1000 is default and too slow.
    });

    if (gatherMode === 'timespan') {
      await driver.defaultSession.sendCommand('Tracing.recordClockSyncMarker',
        {syncId: TraceProcessor.TIMESPAN_MARKER_ID});
    }
  }

  /**
   * @param {LH.Gatherer.FRTransitionalContext} passContext
   */
  async stopSensitiveInstrumentation({driver}) {
    this._trace = await Trace.endTraceAndCollectEvents(driver.defaultSession);
  }

  getArtifact() {
    return this._trace;
  }
}

module.exports = Trace;
<|endoftext|>


<commit_before>'use strict';

// Kernel "scheduler" (analogous to OS), manages processes
// Maintains multiple independent execution contexts (processes) for JavaScript, using sandboxed iframes

let nextPid = 1;
let processes = new Map();
let sources = new Map();

const {createDraggableIframe} = require('./windowing');

class Process {
  // Create a new "userland" sandboxed execution context (= process)
  constructor() {
    this.state = 'new'; // see https://en.wikipedia.org/wiki/Process_state
    this.pid = nextPid;
    nextPid += 1;

    Process.broadcast({cmd: 'nextPid', nextPid});

    let {iframe, container, titleText, closeButton} = createDraggableIframe(this.pid);

    this.iframe = iframe;
    this.container = container;
    this.titleText = titleText;
    this.closeButton = closeButton;

    this.closeButton.addEventListener('click', (event) => {
      this.terminate(-1); // TODO: instead, send SIGTERM, let gracefully exit?
    });

    processes.set(this.pid, this);

    const containers = document.getElementById('userland-processes');
    containers.appendChild(container);

    console.log(`new Process pid=${this.pid}`);
    // process is created in frozen state, can be started with exec()
  }

  // Execute code with arguments and environment, like Unix fork/exec or posix_spawn/system
  exec(argv, env) {
    console.log(`Process exec ${this.pid}, argv ${JSON.stringify(argv)}, env ${JSON.stringify(env)}`);

    if (!env) env = global.ENV; // inherit from kernel TODO: per-process inheritance, forking

    // save own independent copy
    this.argv = JSON.parse(JSON.stringify(argv));
    this.env = JSON.parse(JSON.stringify(env));

    this.state = 'waiting'; // awaiting execution
    this.iframe.setAttribute('src', '/userland/userland.html');
    this.iframe.addEventListener('load', (event) => {
      console.log('sandbox frame load',this.pid);

      sources.set(this.iframe.contentWindow, this);

      this.iframe.contentWindow.postMessage({cmd: '_start', pid: this.pid, argv: this.argv, env: this.env}, '*');
      this.state = 'ready'; // ready until process confirms it started by posting reply back to _start: 'started'
    });
    // TODO: add path, to require and/or readFile to execute and run
  }

  // IPC
  postMessage(msg) {
    if (!this.iframe.contentWindow) throw new Error(`unable to send to process, no iframe content: ${this.pid}`);

    this.iframe.contentWindow.postMessage(msg, '*');
  }

  static broadcast(msg) {
    for (let thisProcess of processes.values()) {
      thisProcess.postMessage(msg);
    }
  }

  terminate(code) {
    this.container.parentNode.removeChild(this.container);
    processes.delete(this.pid);
    console.log(`Terminated ${this.pid}`);
    this.state = 'terminated';
    this.exitCode = code;
    // TODO: zombies? when exit, may want to keep process around for examination, until the zombie is 'reaped'
  }

  set title(title) {
    this.titleText.textContent = title;
  }

  get title() {
    return this.titleText.textContent;
  }

  markDead(code) { // TODO: move to window
    this.state = 'dead';
    this.exitCode = code;

    const notice = document.createElement('p');
    notice.textContent = `DEAD PROCESS: ${code}`;
    this.container.insertBefore(notice, this.iframe);
  }

  static getFromPid(pid) {
    return processes.get(pid);
  }

  static getFromSource(source) {
    return sources.get(source);
  }
}

window.addEventListener('message', (event) => {
  if (event.data.cmd === 'started') {
    const sourceProcess = Process.getFromSource(event.source);
    if (!sourceProcess) throw new Error(`started process not found: ${event.data}`);

    sourceProcess.state = 'running';
  } else if (event.data.cmd === 'kill') {
    const pid = event.data.pid;
    const signal = event.data.signal;

    const sourceProcess = Process.getFromSource(event.source);

    const targetProcess = Process.getFromPid(pid);
    if (!targetProcess) {
      // TODO: deliver ESRCH error back to source.. but have to do it async, even though
      // the API is sync! What to do here? Deliver it another signal? Or does it matter?
      //sourceProcess.postMessage({cmd: 'signal',
      console.log(`kill(${pid}, ${signal}) from ${sourceProcess.pid}, but ${pid} does not exist`);
      return;
    }

    targetProcess.postMessage({cmd: 'signal', fromPid: sourceProcess.pid, signal: signal});
  } else if (event.data.cmd === 'exit') {
    const sourceProcess = Process.getFromSource(event.source);

    const code = event.data.code;

    // Give some time to inspect the process before it cleans up
    // TODO: zombie reaping model instead, mark dead but allow getting exit code, visual, but no IPC, etc.
    // and have /bin/init, the parent of all processes, 'reap' the zombies after they are done or whatever
    const terminateDelaySeconds = 5;
    console.log(`Process ${sourceProcess.pid} exited (${code}), terminating in ${terminateDelaySeconds}s`);

    sourceProcess.markDead(code);

    window.setTimeout(() => {
      sourceProcess.terminate(code);
    }, terminateDelaySeconds * 1000);
  } else if (event.data.cmd === 'spawn') {
    const command = event.data.command;
    const args = event.data.args;

    const argv = [command].concat(event.data.args);
    const env = event.data.env;

    const newProcess = new Process();
    if (event.data.nextPid && newProcess.pid !== event.data.nextPid) {
      throw new Error(`spawn() expected pid ${event.data.nextPid} but got ${newProcess.pid}, race condition detected?`);
    }

    newProcess.exec(argv, env);
  } else if (event.data.cmd === 'setproctitle') {
    const sourceProcess = Process.getFromSource(event.source);

    sourceProcess.title = event.data.title;
  }
});

// Send a message to the sandboxed iframe, aka the userland
function postUserland(pid, msg) {
  const process = processes.get(pid);
  if (!process) throw new Error(`no such process: ${pid}`);

  process.postMessage(msg);
}

// Evaluate code within a given sandbox
function evalin(pid, code) {
  postUserland(pid, {cmd: 'eval', code: code});
}

module.exports = {
  Process,
  evalin,
  postUserland,
};
<commit_msg>Show process exit code in title, and leave unterminated for now<commit_after>'use strict';

// Kernel "scheduler" (analogous to OS), manages processes
// Maintains multiple independent execution contexts (processes) for JavaScript, using sandboxed iframes

let nextPid = 1;
let processes = new Map();
let sources = new Map();

const {createDraggableIframe} = require('./windowing');

class Process {
  // Create a new "userland" sandboxed execution context (= process)
  constructor() {
    this.state = 'new'; // see https://en.wikipedia.org/wiki/Process_state
    this.pid = nextPid;
    nextPid += 1;

    Process.broadcast({cmd: 'nextPid', nextPid});

    let {iframe, container, titleText, closeButton} = createDraggableIframe(this.pid);

    this.iframe = iframe;
    this.container = container;
    this.titleText = titleText;
    this.closeButton = closeButton;

    this.closeButton.addEventListener('click', (event) => {
      this.terminate(-1); // TODO: instead, send SIGTERM, let gracefully exit?
    });

    processes.set(this.pid, this);

    const containers = document.getElementById('userland-processes');
    containers.appendChild(container);

    console.log(`new Process pid=${this.pid}`);
    // process is created in frozen state, can be started with exec()
  }

  // Execute code with arguments and environment, like Unix fork/exec or posix_spawn/system
  exec(argv, env) {
    console.log(`Process exec ${this.pid}, argv ${JSON.stringify(argv)}, env ${JSON.stringify(env)}`);

    if (!env) env = global.ENV; // inherit from kernel TODO: per-process inheritance, forking

    // save own independent copy
    this.argv = JSON.parse(JSON.stringify(argv));
    this.env = JSON.parse(JSON.stringify(env));

    this.state = 'waiting'; // awaiting execution
    this.iframe.setAttribute('src', '/userland/userland.html');
    this.iframe.addEventListener('load', (event) => {
      console.log('sandbox frame load',this.pid);

      sources.set(this.iframe.contentWindow, this);

      this.iframe.contentWindow.postMessage({cmd: '_start', pid: this.pid, argv: this.argv, env: this.env}, '*');
      this.state = 'ready'; // ready until process confirms it started by posting reply back to _start: 'started'
    });
    // TODO: add path, to require and/or readFile to execute and run
  }

  // IPC
  postMessage(msg) {
    if (!this.iframe.contentWindow) throw new Error(`unable to send to process, no iframe content: ${this.pid}`);

    this.iframe.contentWindow.postMessage(msg, '*');
  }

  static broadcast(msg) {
    for (let thisProcess of processes.values()) {
      thisProcess.postMessage(msg);
    }
  }

  terminate(code) {
    this.container.parentNode.removeChild(this.container);
    processes.delete(this.pid);
    console.log(`Terminated ${this.pid}`);
    this.state = 'terminated';
    this.exitCode = code;
    // TODO: zombies? when exit, may want to keep process around for examination, until the zombie is 'reaped'
  }

  set title(title) {
    this.titleText.textContent = title;
  }

  get title() {
    return this.titleText.textContent;
  }

  markDead(code) {
    this.state = 'dead';
    this.exitCode = code;

    this.title += ` (exited with code ${code})`;
  }

  static getFromPid(pid) {
    return processes.get(pid);
  }

  static getFromSource(source) {
    return sources.get(source);
  }
}

window.addEventListener('message', (event) => {
  if (event.data.cmd === 'started') {
    const sourceProcess = Process.getFromSource(event.source);
    if (!sourceProcess) throw new Error(`started process not found: ${event.data}`);

    sourceProcess.state = 'running';
  } else if (event.data.cmd === 'kill') {
    const pid = event.data.pid;
    const signal = event.data.signal;

    const sourceProcess = Process.getFromSource(event.source);

    const targetProcess = Process.getFromPid(pid);
    if (!targetProcess) {
      // TODO: deliver ESRCH error back to source.. but have to do it async, even though
      // the API is sync! What to do here? Deliver it another signal? Or does it matter?
      //sourceProcess.postMessage({cmd: 'signal',
      console.log(`kill(${pid}, ${signal}) from ${sourceProcess.pid}, but ${pid} does not exist`);
      return;
    }

    targetProcess.postMessage({cmd: 'signal', fromPid: sourceProcess.pid, signal: signal});
  } else if (event.data.cmd === 'exit') {
    const sourceProcess = Process.getFromSource(event.source);

    const code = event.data.code;

    // Give some time to inspect the process before it cleans up
    // TODO: zombie reaping model instead, mark dead but allow getting exit code, visual, but no IPC, etc.
    // and have /bin/init, the parent of all processes, 'reap' the zombies after they are done or whatever
    const terminateDelaySeconds = 5;
    console.log(`Process ${sourceProcess.pid} exited (${code}), terminating in ${terminateDelaySeconds}s`);

    sourceProcess.markDead(code);

    // TODO: when should this exit? leaving up for now to see terminated process output
    //sourceProcess.terminate(code);
  } else if (event.data.cmd === 'spawn') {
    const command = event.data.command;
    const args = event.data.args;

    const argv = [command].concat(event.data.args);
    const env = event.data.env;

    const newProcess = new Process();
    if (event.data.nextPid && newProcess.pid !== event.data.nextPid) {
      throw new Error(`spawn() expected pid ${event.data.nextPid} but got ${newProcess.pid}, race condition detected?`);
    }

    newProcess.exec(argv, env);
  } else if (event.data.cmd === 'setproctitle') {
    const sourceProcess = Process.getFromSource(event.source);

    sourceProcess.title = event.data.title;
  }
});

// Send a message to the sandboxed iframe, aka the userland
function postUserland(pid, msg) {
  const process = processes.get(pid);
  if (!process) throw new Error(`no such process: ${pid}`);

  process.postMessage(msg);
}

// Evaluate code within a given sandbox
function evalin(pid, code) {
  postUserland(pid, {cmd: 'eval', code: code});
}

module.exports = {
  Process,
  evalin,
  postUserland,
};
<|endoftext|>


<commit_before>/**
 * @file directMessageHandler
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

/**
 * Handles direct messages sent to Bastion
 * @param {Message} message Discord.js message object
 * @returns {void}
 */
module.exports = message => {
  let prefix = message.client.config.prefix;

  if (message.content.startsWith(prefix)) {
    let args = message.content.split(' ');
    let command = args.shift().slice(prefix.length).toLowerCase();

    if (command === 'help' || command === 'h') {
      return message.channel.send({
        embed: {
          color: message.client.colors.BLUE,
          title: 'The Bastion Bot',
          url: 'https://bastionbot.org',
          description: 'Join [**Bastion HQ**](https://discord.gg/fzx8fkt) to test Bastion and it\'s commands, for giveaway events, for chatting and for a lot of fun!',
          fields: [
            {
              name: 'Bastion HQ Invite Link',
              value: 'https://discord.gg/fzx8fkt'
            },
            {
              name: 'Bastion Bot Invite Link',
              value: `https://discordapp.com/oauth2/authorize?client_id=${message.client.user.id}&scope=bot&permissions=2146958463`
            }
          ],
          thumbnail: {
            url: message.client.user.displayAvatarURL
          },
          footer: {
            text: '</> with ❤ by Sankarsan Kampa (a.k.a. k3rn31p4nic)'
          }
        }
      }).catch(e => {
        message.client.log.error(e);
      });
    }
  }
};
<commit_msg>DM commands will work only with the 1st prefix<commit_after>/**
 * @file directMessageHandler
 * @author Sankarsan Kampa (a.k.a k3rn31p4nic)
 * @license MIT
 */

/**
 * Handles direct messages sent to Bastion
 * @param {Message} message Discord.js message object
 * @returns {void}
 */
module.exports = message => {
  let prefix = message.client.config.prefix[0];

  if (message.content.startsWith(prefix)) {
    let args = message.content.split(' ');
    let command = args.shift().slice(prefix.length).toLowerCase();

    if (command === 'help' || command === 'h') {
      return message.channel.send({
        embed: {
          color: message.client.colors.BLUE,
          title: 'The Bastion Bot',
          url: 'https://bastionbot.org',
          description: 'Join [**Bastion HQ**](https://discord.gg/fzx8fkt) to test Bastion and it\'s commands, for giveaway events, for chatting and for a lot of fun!',
          fields: [
            {
              name: 'Bastion HQ Invite Link',
              value: 'https://discord.gg/fzx8fkt'
            },
            {
              name: 'Bastion Bot Invite Link',
              value: `https://discordapp.com/oauth2/authorize?client_id=${message.client.user.id}&scope=bot&permissions=2146958463`
            }
          ],
          thumbnail: {
            url: message.client.user.displayAvatarURL
          },
          footer: {
            text: '</> with ❤ by Sankarsan Kampa (a.k.a. k3rn31p4nic)'
          }
        }
      }).catch(e => {
        message.client.log.error(e);
      });
    }
  }
};
<|endoftext|>