<commit_before>/**
 * Copyright 2015 Alistair Stead
 *
 * 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.
 */
define([], function() {

	var PageController = ['$scope', 'auth', 'api', '$window', '$rootScope', '$sce', '$timeout', function($scope, auth, api, $window, $rootScope, $sce, $timeout) {
		$scope.isAdminUser = $rootScope.user.role == 'ADMIN';
	    $scope.statistics = null;
	    $scope.setLoading(true);
	    $scope.apiVersion = api.contentVersion.get();
	    $scope.emailTypes = null;

	    $scope.lastContentIDSuccessfullyPreviewed = "";

	    $scope.htmlPreview = "";
	    $scope.plainTextPreview = "";
	    $scope.subjectPreview = "";

	    $scope.emailToSend = {
	    	emailType : -1,
	    	contentObjectId : "",
	    	users: {
		    	ADMIN : false,
		    	EVENT_MANAGER : false,
		    	CONTENT_EDITOR : false,
		    	TEACHER : false,
		    	TESTER : false,
		    	STAFF : false,
		    	STUDENT : false,
			}
	    };

	    $scope.queueSize = "?";
	    var checkTimes = 0;

	    var queueSizeChecker = function(){
	    	api.email.getQueueSize({}).$promise.then(function(result){
	    		var d = new Date();
    			$scope.queueSize = "" + result[0] + " at " + d.toLocaleTimeString();
				$timeout(queueSizeChecker, 5000);
    		}).catch(function(e){
				$scope.showToast($scope.toastTypes.Failure, "Email Queue Size", "Could not get email queue size (" + e.status + ") " + e.statusText);
				clearInterval(timerId);
			});
    	}

	    queueSizeChecker();

		var getUserIdArrayFromTextArea = function(){
			var userids = $scope.csvuseridlist.split(/[\s,]+/).map(function(e) {return parseInt(e);});;
			return userids;
		}

	    $scope.getTotalUsers = function(){
	    	if($scope.userSelectionType == 'csvuseridlist') {
				var userIdList = getUserIdArrayFromTextArea();
				return userIdList.length;
	    	}

	    	var total = 0;
			if($scope.emailToSend.users.ADMIN) {
	    		total += parseInt($scope.statistics.role.ADMIN);
	    	}

	    	if($scope.emailToSend.users.EVENT_MANAGER) {
	    		total += parseInt($scope.statistics.role.EVENT_MANAGER);
	    	}

	    	if($scope.emailToSend.users.CONTENT_EDITOR) {
	    		total += parseInt($scope.statistics.role.CONTENT_EDITOR);
	    	}

	    	if($scope.emailToSend.users.TEACHER) {
	    		total += parseInt($scope.statistics.role.TEACHER);
	    	}

	    	if($scope.emailToSend.users.TESTER) {
	    		total += parseInt($scope.statistics.role.TESTER);
	    	}

	    	if($scope.emailToSend.users.STAFF) {
	    		total += parseInt($scope.statistics.role.STAFF);
	    	}

	    	if($scope.emailToSend.users.STUDENT) {
	    		total += parseInt($scope.statistics.role.STUDENT);
	    	}
	    	return total;
	    }

	    api.statisticsEndpoint.get().$promise.then(function(result){
	        $scope.statistics = result;
	        $scope.setLoading(false);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Statistics load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    api.email.getPreferences().$promise.then(function(result){
	    	$scope.emailTypes = result;
	    	console.log(result);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Preferences load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    $scope.loadContentTemplate = function(contentId){
	    	$scope.setLoading(true);
	    	api.email.get({"id" : $scope.emailToSend.contentObjectId}).$promise.then(function(content){
	        	$scope.subjectPreview = content.subject;
	        	$scope.htmlPreview = $sce.trustAsResourceUrl("data:text/html," + encodeURIComponent(content.html));
	        	$scope.plainTextPreview = $sce.trustAsResourceUrl("data:text," + content.plainText);
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = $scope.emailToSend.contentObjectId;
	    	}).catch(function(e){
    			$scope.showToast($scope.toastTypes.Failure, "Email template failed", "With error message (" + e.status + ") " + e.statusText);
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = "";
	    	});
	    };

        $scope.emailTypeChanged = function(idOfSelectedEmailType){
			$scope.emailToSend.emailType = idOfSelectedEmailType;
		};

	    $scope.validateAndSendEmails = function(){

	    	if(!$scope.emailToSend.emailType || $scope.emailToSend.emailType < 0){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select a type of email");
				return;
	    	}

	    	if($scope.lastContentIDSuccessfullyPreviewed == "" || $scope.emailToSend.contentObjectId != $scope.lastContentIDSuccessfullyPreviewed){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must preview the email before sending");
				return;
	    	}

			if($scope.userSelectionType == 'userfilter') {
		    	var usersSelected = false;
		    	for(var key in $scope.emailToSend.users) {
	    			if($scope.emailToSend.users[key]){
	    				usersSelected = true;
	    			}
		    	}

		    	if(!usersSelected){
					$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select users to send the email to");
					return;
				}
			}
			else if($scope.userSelectionType == 'csvuseridlist') {
				var userids = getUserIdArrayFromTextArea($scope.csvuseridlist);

				// Check for NaNs
				for(var i = 0; i < userids.length; i++){
					if(isNaN(userids[i])){
						$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "One of the userIds given evaluates to NaN");
						return;
					}
				}
			}
			else {
				$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "No user selection type selected");
				return;
			}

        	var confirmation = $window.confirm('You are about to send ' + $scope.getTotalUsers() + ' email(s). Are you sure?');
        	if(confirmation && $scope.userSelectionType == 'userfilter'){
				$scope.setLoading(true);
				api.email.sendEmail({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType,
				}, $scope.emailToSend.users).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
			else if(confirmation && $scope.userSelectionType == 'csvuseridlist'){
				$scope.setLoading(true);
				api.email.sendEmailWithIds({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType
				}, getUserIdArrayFromTextArea($scope.csvuseridlist)).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
	    };

    }]
	return {
		PageController: PageController,
	};
})<commit_msg>Added some extra validation to avoid things that look like numbers being accepted as user ids.<commit_after>/**
 * Copyright 2015 Alistair Stead
 *
 * 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.
 */
define([], function() {

	var PageController = ['$scope', 'auth', 'api', '$window', '$rootScope', '$sce', '$timeout', function($scope, auth, api, $window, $rootScope, $sce, $timeout) {
		$scope.isAdminUser = $rootScope.user.role == 'ADMIN';
	    $scope.statistics = null;
	    $scope.setLoading(true);
	    $scope.apiVersion = api.contentVersion.get();
	    $scope.emailTypes = null;

	    $scope.lastContentIDSuccessfullyPreviewed = "";

	    $scope.htmlPreview = "";
	    $scope.plainTextPreview = "";
	    $scope.subjectPreview = "";

	    $scope.emailToSend = {
	    	emailType : -1,
	    	contentObjectId : "",
	    	users: {
		    	ADMIN : false,
		    	EVENT_MANAGER : false,
		    	CONTENT_EDITOR : false,
		    	TEACHER : false,
		    	TESTER : false,
		    	STAFF : false,
		    	STUDENT : false,
			}
	    };

	    $scope.queueSize = "?";
	    var checkTimes = 0;

	    var queueSizeChecker = function(){
	    	api.email.getQueueSize({}).$promise.then(function(result){
	    		var d = new Date();
    			$scope.queueSize = "" + result[0] + " at " + d.toLocaleTimeString();
				$timeout(queueSizeChecker, 5000);
    		}).catch(function(e){
				$scope.showToast($scope.toastTypes.Failure, "Email Queue Size", "Could not get email queue size (" + e.status + ") " + e.statusText);
				clearInterval(timerId);
			});
    	}

	    queueSizeChecker();

		var getUserIdArrayFromTextArea = function(){
			var userids = $scope.csvuseridlist.split(/[\s,]+/).map(function(e) {return parseInt(e);});;
			return userids;
		}

	    $scope.getTotalUsers = function(){
	    	if($scope.userSelectionType == 'csvuseridlist') {
				var userIdList = getUserIdArrayFromTextArea();
				return userIdList.length;
	    	}

	    	var total = 0;
			if($scope.emailToSend.users.ADMIN) {
	    		total += parseInt($scope.statistics.role.ADMIN);
	    	}

	    	if($scope.emailToSend.users.EVENT_MANAGER) {
	    		total += parseInt($scope.statistics.role.EVENT_MANAGER);
	    	}

	    	if($scope.emailToSend.users.CONTENT_EDITOR) {
	    		total += parseInt($scope.statistics.role.CONTENT_EDITOR);
	    	}

	    	if($scope.emailToSend.users.TEACHER) {
	    		total += parseInt($scope.statistics.role.TEACHER);
	    	}

	    	if($scope.emailToSend.users.TESTER) {
	    		total += parseInt($scope.statistics.role.TESTER);
	    	}

	    	if($scope.emailToSend.users.STAFF) {
	    		total += parseInt($scope.statistics.role.STAFF);
	    	}

	    	if($scope.emailToSend.users.STUDENT) {
	    		total += parseInt($scope.statistics.role.STUDENT);
	    	}
	    	return total;
	    }

	    api.statisticsEndpoint.get().$promise.then(function(result){
	        $scope.statistics = result;
	        $scope.setLoading(false);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Statistics load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    api.email.getPreferences().$promise.then(function(result){
	    	$scope.emailTypes = result;
	    	console.log(result);
	    }).catch(function(e){
			$scope.showToast($scope.toastTypes.Failure, "Preferences load failed", "With error message (" + e.status + ") " + e.statusText);
	    });

	    $scope.loadContentTemplate = function(contentId){
	    	$scope.setLoading(true);
	    	api.email.get({"id" : $scope.emailToSend.contentObjectId}).$promise.then(function(content){
	        	$scope.subjectPreview = content.subject;
	        	$scope.htmlPreview = $sce.trustAsResourceUrl("data:text/html," + encodeURIComponent(content.html));
	        	$scope.plainTextPreview = $sce.trustAsResourceUrl("data:text," + content.plainText);
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = $scope.emailToSend.contentObjectId;
	    	}).catch(function(e){
    			$scope.showToast($scope.toastTypes.Failure, "Email template failed", "With error message (" + e.status + ") " + e.statusText);
	        	$scope.setLoading(false);
	        	$scope.lastContentIDSuccessfullyPreviewed = "";
	    	});
	    };

        $scope.emailTypeChanged = function(idOfSelectedEmailType){
			$scope.emailToSend.emailType = idOfSelectedEmailType;
		};

	    $scope.validateAndSendEmails = function(){

	    	if(!$scope.emailToSend.emailType || $scope.emailToSend.emailType < 0){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select a type of email");
				return;
	    	}

	    	if($scope.lastContentIDSuccessfullyPreviewed == "" || $scope.emailToSend.contentObjectId != $scope.lastContentIDSuccessfullyPreviewed){
    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must preview the email before sending");
				return;
	    	}

			if($scope.userSelectionType == 'userfilter') {
		    	var usersSelected = false;
		    	for(var key in $scope.emailToSend.users) {
	    			if($scope.emailToSend.users[key]){
	    				usersSelected = true;
	    			}
		    	}

		    	if(!usersSelected){
					$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "You must select users to send the email to");
					return;
				}
			}
			else if($scope.userSelectionType == 'csvuseridlist') {
				var letters = /[^,0-9]+/;

				if(letters.test($scope.csvuseridlist)){
					$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "Userids field contains invalid characters");
					return;
				}

				var userids = getUserIdArrayFromTextArea($scope.csvuseridlist);

				// Check for NaNs and invalid characters
				for(var i = 0; i < userids.length; i++){
					if(isNaN(userids[i])){
						$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "One of the userIds given evaluates to NaN");
						return;
					}
				}
			}
			else {
				$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "No user selection type selected");
				return;
			}

        	var confirmation = $window.confirm('You are about to send ' + $scope.getTotalUsers() + ' email(s). Are you sure?');
        	if(confirmation && $scope.userSelectionType == 'userfilter'){
				$scope.setLoading(true);
				api.email.sendEmail({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType,
				}, $scope.emailToSend.users).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
			else if(confirmation && $scope.userSelectionType == 'csvuseridlist'){
				$scope.setLoading(true);
				api.email.sendEmailWithIds({
					contentid : $scope.emailToSend.contentObjectId,
					emailtype: $scope.emailToSend.emailType
				}, getUserIdArrayFromTextArea($scope.csvuseridlist)).$promise.then(function(){
					$scope.setLoading(false);
	    			$scope.showToast($scope.toastTypes.Success, "Success!", "Email has been sent (and filtered) successfully!");
				}).catch(function(e){
	    			$scope.showToast($scope.toastTypes.Failure, "Email sending failed", "With error message (" + e.status + ") " + e.statusText);
		        	$scope.setLoading(false);
				});
			}
	    };

    }]
	return {
		PageController: PageController,
	};
})<|endoftext|>


<commit_before>'use strict';

const lo_find = require('lodash.find');
const lo_assign = require('lodash.assign');
const lo_reject = require('lodash.reject');
const lo_orderBy = require('lodash.orderby');

const co = require('co');
const path = require('path');
const semver = require('semver');

const Packman = require('./packman');
const searchClient = require('./search-client');
const util = require('./util');

const COMMANDS_RE = / (install|update|uninstall|list)(\s+([^\s]+))?/i;
const NAME = 'hain-package-manager (experimental)';
const PREFIX = '/hpm';

const COMMANDS = [`${PREFIX} install `, `${PREFIX} update `, `${PREFIX} uninstall `, `${PREFIX} list `];
const CACHE_DURATION_SEC = 5 * 60; // 5 mins

module.exports = (context) => {
  const packmanOpts = {
    mainRepo: context.MAIN_PLUGIN_REPO,
    internalRepo: context.INTERNAL_PLUGIN_REPO,
    tempDir: path.resolve('./_temp'),
    installDir: context.__PLUGIN_PREINSTALL_DIR,
    uninstallListFile: context.__PLUGIN_UNINSTALL_LIST_FILE,
    updateListFile: context.__PLUGIN_UPDATE_LIST_FILE
  };
  const pm = new Packman(packmanOpts);
  const toast = context.toast;
  const logger = context.logger;
  const shell = context.shell;
  const matchutil = context.matchutil;
  const app = context.app;

  let isSearching = false;
  let currentStatus = null;
  let progressTimer = 0;
  let lastUpdatedTime = 0;
  let availablePackages = [];

  function getBackendUrl() {
    return context.preferences.get('backendUrl') || 'http://npmsearch.com';
  }

  function checkAvailablePackages() {
    const elapsed = (Date.now() - lastUpdatedTime) / 1000;
    if (elapsed <= CACHE_DURATION_SEC || isSearching)
      return;

    isSearching = true;
    searchClient.findCompatiblePackagesWithDownloads(getBackendUrl(), context.COMPATIBLE_API_VERSIONS).then(ret => {
      availablePackages = ret || [];
      availablePackages = lo_orderBy(availablePackages, 'downloads', ['desc']);
      lastUpdatedTime = Date.now();
      isSearching = false;
    }, (err) => {
      isSearching = false;
    });
  }

  function getPackageInfo(packageName) {
    return lo_find(pm.listPackages(), (x) => x.name === packageName);
  }

  function startup() {
    pm.readPackages();
    checkAvailablePackages();
  }

  function search(query, res) {
    if (currentStatus === null)
      checkAvailablePackages();

    clearTimeout(progressTimer);
    if (currentStatus) {
      res.add({
        id: '**',
        title: currentStatus,
        desc: NAME,
        icon: '#fa fa-spinner fa-spin'
      });
      progressTimer = setInterval(() => {
        if (!currentStatus) {
          res.remove('**');
          res.add(parseCommands(query));
          return clearTimeout(progressTimer);
        }
      }, 500);
      return;
    }
    res.add(parseCommands(query));
  }

  function _toSearchResult(cmdType, pkgInfo, customName, group) {
    const result = {
      id: pkgInfo.name,
      payload: cmdType,
      title: `${customName || pkgInfo.name} ` +
             ` <span style='font-size: 9pt'>${pkgInfo.internal ? 'internal' : pkgInfo.version}` +
             `${!pkgInfo.internal ? ` by ${util.parseAuthor(pkgInfo.author)}` : ''}` +
             `</span>`,
      desc: `${pkgInfo.desc}`,
      group
    };
    if (pkgInfo.downloads !== undefined && pkgInfo.modified !== undefined)
      result.desc = `${pkgInfo.downloads} Downloads / ${pkgInfo.desc}`;
    return result;
  }

  function _toSearchResults(cmdType, packages, group) {
    return packages.map(x => _toSearchResult(cmdType, x, undefined, group));
  }

  function _fuzzy(cmdType, packages, keyword) {
    if (keyword.length <= 0)
      return packages.map(x => _toSearchResult(cmdType, x));
    return matchutil.fuzzy(packages, keyword.trim(), x => x.name).map(x => {
      const m = matchutil.makeStringBoldHtml(x.elem.name, x.matches);
      return _toSearchResult(cmdType, x.elem, m);
    });
  }

  function parseCommands(query) {
    const parsed = COMMANDS_RE.exec(query.toLowerCase());
    if (!parsed) {
      return _makeCommandsHelp(query);
    }
    const command = parsed[1];
    const arg = parsed[2] || '';
    if (command === 'install') {
      if (availablePackages.length <= 0) {
        return {
          title: 'Please wait, fetching available packages...',
          desc: NAME,
          icon: '#fa fa-spinner fa-spin'
        };
      }
      const packages = availablePackages.filter(x => {
        return !pm.hasPackage(x.name);
      });

      // if there is a query, then return fuzzy matched results
      if (arg.length > 0)
        return _fuzzy('install', packages, arg);

      // split packages into newest and popular
      const newestPackages = lo_orderBy(packages, '__modified', ['desc']).slice(0, 3);
      const newestPkgNames = newestPackages.map(x => x.name);
      const popularPackages = lo_reject(packages, x => newestPkgNames.indexOf(x.name) >= 0);

      const popularResults = _toSearchResults('install', popularPackages, 'Popular (Monthly)');
      const newestResults = _toSearchResults('install', newestPackages, 'Newest');
      return newestResults.concat(popularResults);
    }
    if (command === 'update') {
      const packages = availablePackages.filter(x => {
        const installedPackage = pm.getPackage(x.name);
        if (installedPackage === undefined)
          return false;
        return semver.gt(x.version, installedPackage.version);
      }).map(x => {
        const org = pm.getPackage(x.name);
        const _x = lo_assign({}, x); // clone
        _x.version = `${org.version} => ${_x.version}`;
        return _x;
      });
      if (packages.length <= 0) {
        return {
          title: 'Everthing is up-to-date',
          desc: NAME
        };
      }
      return _fuzzy('update', packages, arg);
    }
    if (command === 'uninstall') {
      const packages = pm.listPackages();
      return _fuzzy('uninstall', packages, arg);
    }
    if (command === 'list') {
      const packages = pm.listPackages();
      const internalPackages = pm.listInternalPackages();
      return packages.map((x) => _toSearchResult('list', x))
        .concat(internalPackages.map((x) => _toSearchResult('list', x, null, 'Internal packages')));
    }
    return _makeCommandsHelp(query);
  }

  function _makeCommandsHelp(query) {
    const ret = matchutil.head(COMMANDS, `${PREFIX}${query}`).map((x) => {
      return {
        id: x.elem,
        payload: 'redirect',
        redirect: x.elem,
        title: matchutil.makeStringBoldHtml(x.elem, x.matches),
        desc: NAME
      };
    });
    return ret;
  }

  function resetQuery() {
    app.setQuery(`${PREFIX} `);
  }

  function execute(id, payload) {
    const funcs = {
      'install': () => {
        installPackage(id);
        resetQuery();
      },
      'update': () => {
        updatePackage(id);
        resetQuery();
      },
      'uninstall': () => {
        uninstallPackage(id);
        resetQuery();
      },
      'list': () => {
        const pkgInfo = getPackageInfo(id);
        if (pkgInfo.homepage)
          shell.openExternal(pkgInfo.homepage);
      },
      'redirect': () => app.setQuery(id)
    };
    const func = funcs[payload];
    func();
  }

  function uninstallPackage(packageName) {
    try {
      pm.uninstallPackage(packageName);
      toast.enqueue(`${packageName} has uninstalled, Reload plugins to take effect`, 3000);
    } catch (e) {
      toast.enqueue(e.toString());
    }
  }

  function installPackage(packageName) {
    co(function* () {
      logger.log(`Installing ${packageName}`);
      currentStatus = `Installing <b>${packageName}</b>`;
      try {
        yield pm.installPackage(packageName, 'latest');
        toast.enqueue(`${packageName} has installed, Reload plugins to take effect`, 3000);
        logger.log(`${packageName} has pre-installed`);
      } catch (e) {
        toast.enqueue(e.toString());
        logger.log(`${packageName} ${e}`);
        throw e;
      } finally {
        currentStatus = null;
      }
    });
  }

  function updatePackage(packageName) {
    co(function* () {
      logger.log(`Updating ${packageName}`);
      currentStatus = `Updating <b>${packageName}</b>`;
      try {
        pm.uninstallPackageForUpdate(packageName);
        yield pm.installPackage(packageName, 'latest');
        toast.enqueue(`${packageName} has updated, Reload plugins to take effect`, 3000);
        logger.log(`${packageName} has pre-installed (for update)`);
      } catch (e) {
        toast.enqueue(e.toString());
        logger.log(`${packageName} ${e}`);
        throw e;
      } finally {
        currentStatus = null;
      }
    });
  }

  return { startup, search, execute };
};
<commit_msg>We might want to change this too<commit_after>'use strict';

const lo_find = require('lodash.find');
const lo_assign = require('lodash.assign');
const lo_reject = require('lodash.reject');
const lo_orderBy = require('lodash.orderby');

const co = require('co');
const path = require('path');
const semver = require('semver');

const Packman = require('./packman');
const searchClient = require('./search-client');
const util = require('./util');

const COMMANDS_RE = / (install|update|uninstall|list)(\s+([^\s]+))?/i;
const NAME = 'hain-package-manager (experimental)';
const PREFIX = '/hpm';

const COMMANDS = [`${PREFIX} install `, `${PREFIX} update `, `${PREFIX} uninstall `, `${PREFIX} list `];
const CACHE_DURATION_SEC = 5 * 60; // 5 mins

module.exports = (context) => {
  const packmanOpts = {
    mainRepo: context.MAIN_PLUGIN_REPO,
    internalRepo: context.INTERNAL_PLUGIN_REPO,
    tempDir: path.resolve('./_temp'),
    installDir: context.__PLUGIN_PREINSTALL_DIR,
    uninstallListFile: context.__PLUGIN_UNINSTALL_LIST_FILE,
    updateListFile: context.__PLUGIN_UPDATE_LIST_FILE
  };
  const pm = new Packman(packmanOpts);
  const toast = context.toast;
  const logger = context.logger;
  const shell = context.shell;
  const matchutil = context.matchutil;
  const app = context.app;

  let isSearching = false;
  let currentStatus = null;
  let progressTimer = 0;
  let lastUpdatedTime = 0;
  let availablePackages = [];

  function getBackendUrl() {
    return context.preferences.get('backendUrl') || 'http://npmsearch.com';
  }

  function checkAvailablePackages() {
    const elapsed = (Date.now() - lastUpdatedTime) / 1000;
    if (elapsed <= CACHE_DURATION_SEC || isSearching)
      return;

    isSearching = true;
    searchClient.findCompatiblePackagesWithDownloads(getBackendUrl(), context.COMPATIBLE_API_VERSIONS).then(ret => {
      availablePackages = ret || [];
      availablePackages = lo_orderBy(availablePackages, 'downloads', ['desc']);
      lastUpdatedTime = Date.now();
      isSearching = false;
    }, (err) => {
      isSearching = false;
    });
  }

  function getPackageInfo(packageName) {
    return lo_find(pm.listPackages(), (x) => x.name === packageName);
  }

  function startup() {
    pm.readPackages();
    checkAvailablePackages();
  }

  function search(query, res) {
    if (currentStatus === null)
      checkAvailablePackages();

    clearTimeout(progressTimer);
    if (currentStatus) {
      res.add({
        id: '**',
        title: currentStatus,
        desc: NAME,
        icon: '#fa fa-spinner fa-spin'
      });
      progressTimer = setInterval(() => {
        if (!currentStatus) {
          res.remove('**');
          res.add(parseCommands(query));
          return clearTimeout(progressTimer);
        }
      }, 500);
      return;
    }
    res.add(parseCommands(query));
  }

  function _toSearchResult(cmdType, pkgInfo, customName, group) {
    const result = {
      id: pkgInfo.name,
      payload: cmdType,
      title: `${customName || pkgInfo.name} ` +
             ` <span style='font-size: 9pt'>${pkgInfo.internal ? 'internal' : pkgInfo.version}` +
             `${!pkgInfo.internal ? ` by ${util.parseAuthor(pkgInfo.author)}` : ''}` +
             `</span>`,
      desc: `${pkgInfo.desc}`,
      group
    };
    if (pkgInfo.downloads !== undefined && pkgInfo.modified !== undefined)
      result.desc = `${pkgInfo.downloads} Downloads / ${pkgInfo.desc}`;
    return result;
  }

  function _toSearchResults(cmdType, packages, group) {
    return packages.map(x => _toSearchResult(cmdType, x, undefined, group));
  }

  function _fuzzy(cmdType, packages, keyword) {
    if (keyword.length <= 0)
      return packages.map(x => _toSearchResult(cmdType, x));
    return matchutil.fuzzy(packages, keyword.trim(), x => x.name).map(x => {
      const m = matchutil.makeStringBoldHtml(x.elem.name, x.matches);
      return _toSearchResult(cmdType, x.elem, m);
    });
  }

  function parseCommands(query) {
    const parsed = COMMANDS_RE.exec(query.toLowerCase());
    if (!parsed) {
      return _makeCommandsHelp(query);
    }
    const command = parsed[1];
    const arg = parsed[2] || '';
    if (command === 'install') {
      if (availablePackages.length <= 0) {
        return {
          title: 'Please wait, fetching available packages...',
          desc: NAME,
          icon: '#fa fa-spinner fa-spin'
        };
      }
      const packages = availablePackages.filter(x => {
        return !pm.hasPackage(x.name);
      });

      // if there is a query, then return fuzzy matched results
      if (arg.length > 0)
        return _fuzzy('install', packages, arg);

      // split packages into newest and popular
      const newestPackages = lo_orderBy(packages, '__modified', ['desc']).slice(0, 3);
      const newestPkgNames = newestPackages.map(x => x.name);
      const popularPackages = lo_reject(packages, x => newestPkgNames.indexOf(x.name) >= 0);

      const popularResults = _toSearchResults('install', popularPackages, 'Popular');
      const newestResults = _toSearchResults('install', newestPackages, 'Newest');
      return newestResults.concat(popularResults);
    }
    if (command === 'update') {
      const packages = availablePackages.filter(x => {
        const installedPackage = pm.getPackage(x.name);
        if (installedPackage === undefined)
          return false;
        return semver.gt(x.version, installedPackage.version);
      }).map(x => {
        const org = pm.getPackage(x.name);
        const _x = lo_assign({}, x); // clone
        _x.version = `${org.version} => ${_x.version}`;
        return _x;
      });
      if (packages.length <= 0) {
        return {
          title: 'Everthing is up-to-date',
          desc: NAME
        };
      }
      return _fuzzy('update', packages, arg);
    }
    if (command === 'uninstall') {
      const packages = pm.listPackages();
      return _fuzzy('uninstall', packages, arg);
    }
    if (command === 'list') {
      const packages = pm.listPackages();
      const internalPackages = pm.listInternalPackages();
      return packages.map((x) => _toSearchResult('list', x))
        .concat(internalPackages.map((x) => _toSearchResult('list', x, null, 'Internal packages')));
    }
    return _makeCommandsHelp(query);
  }

  function _makeCommandsHelp(query) {
    const ret = matchutil.head(COMMANDS, `${PREFIX}${query}`).map((x) => {
      return {
        id: x.elem,
        payload: 'redirect',
        redirect: x.elem,
        title: matchutil.makeStringBoldHtml(x.elem, x.matches),
        desc: NAME
      };
    });
    return ret;
  }

  function resetQuery() {
    app.setQuery(`${PREFIX} `);
  }

  function execute(id, payload) {
    const funcs = {
      'install': () => {
        installPackage(id);
        resetQuery();
      },
      'update': () => {
        updatePackage(id);
        resetQuery();
      },
      'uninstall': () => {
        uninstallPackage(id);
        resetQuery();
      },
      'list': () => {
        const pkgInfo = getPackageInfo(id);
        if (pkgInfo.homepage)
          shell.openExternal(pkgInfo.homepage);
      },
      'redirect': () => app.setQuery(id)
    };
    const func = funcs[payload];
    func();
  }

  function uninstallPackage(packageName) {
    try {
      pm.uninstallPackage(packageName);
      toast.enqueue(`${packageName} has uninstalled, Reload plugins to take effect`, 3000);
    } catch (e) {
      toast.enqueue(e.toString());
    }
  }

  function installPackage(packageName) {
    co(function* () {
      logger.log(`Installing ${packageName}`);
      currentStatus = `Installing <b>${packageName}</b>`;
      try {
        yield pm.installPackage(packageName, 'latest');
        toast.enqueue(`${packageName} has installed, Reload plugins to take effect`, 3000);
        logger.log(`${packageName} has pre-installed`);
      } catch (e) {
        toast.enqueue(e.toString());
        logger.log(`${packageName} ${e}`);
        throw e;
      } finally {
        currentStatus = null;
      }
    });
  }

  function updatePackage(packageName) {
    co(function* () {
      logger.log(`Updating ${packageName}`);
      currentStatus = `Updating <b>${packageName}</b>`;
      try {
        pm.uninstallPackageForUpdate(packageName);
        yield pm.installPackage(packageName, 'latest');
        toast.enqueue(`${packageName} has updated, Reload plugins to take effect`, 3000);
        logger.log(`${packageName} has pre-installed (for update)`);
      } catch (e) {
        toast.enqueue(e.toString());
        logger.log(`${packageName} ${e}`);
        throw e;
      } finally {
        currentStatus = null;
      }
    });
  }

  return { startup, search, execute };
};
<|endoftext|>


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

  angular.module('dimApp')
    .directive('dimSearchFilter', SearchFilter);

  SearchFilter.$inject = [];

  function SearchFilter() {
    return {
      controller: SearchFilterCtrl,
      controllerAs: 'vm',
      bindToController: true,
      restrict: 'A',
      template: [
        '<input placeholder="filter items or is:arc" type="search" name="filter" ng-model="vm.search" ng-model-options="{ debounce: 500 }" ng-trim="true" ng-change="vm.filter()">'
      ].join('')
    };
  }

  SearchFilterCtrl.$inject = ['$scope', 'dimStoreService', '$timeout'];

  function SearchFilterCtrl($scope, dimStoreService, $timeout) {
    var vm = this;

    $scope.$on('dim-stores-updated', function (arg) {
      vm.filter();
    });

    vm.filter = function () {

      var filterValue = (vm.search) ? vm.search.toLowerCase() : '';
      var filterResults;
      var filterResult = '';
      var filterFn;
      var tempFns = [];
      var special = filterValue.indexOf('is:') >= 0;

      if (special) {
        filterResults = filterValue.split('is:');

        _.each(filterResults, function (filterResult) {
          filterResult = filterResult.trim();

          if (filterResult !== '') {
            if (['arc', 'solar', 'void', 'kinetic'].indexOf(filterResult) >= 0) {
              special = 'elemental';
            } else if (['primary', 'special', 'heavy', 'helmet',' legs', 'gauntlets', 'chest', 'class', 'classitem'].indexOf(filterResult) >= 0) {
              special = 'type';
            } else if (['basic', 'common', 'uncommon', 'rare', 'legendary', 'exotic'].indexOf(filterResult) >= 0) {
              special = 'tier';
            } else if (['incomplete'].indexOf(filterResult) >= 0) {
              special = 'incomplete';
            } else if (['complete'].indexOf(filterResult) >= 0) {
              special = 'complete';
            } else if (['xpincomplete'].indexOf(filterResult) >= 0) {
              special = 'xpincomplete';
            } else if (['xpcomplete'].indexOf(filterResult) >= 0) {
              special = 'xpcomplete';
            }

            tempFns.push(filterGenerator(filterResult, special));
          }
        });
      } else {
        tempFns.push(filterGenerator(filterValue, ''));
      }

      filterFn = function (item) {
        return (_.reduce(tempFns, function (memo, fn) {
          return memo || fn(item);
        }, false));
      };

      _.each(dimStoreService.getStores(), function (store) {
        _.chain(store.items)
          .each(function (item) {
            item.visible = true; // resets the visiblity
          })
          .filter(filterFn)
          .each(function (item) {
            item.visible = false; // hides it if it passes
          });
      });

      $timeout(dimStoreService.setHeights, 32);
    };

    var filterGenerator = function (predicate, switchParam) {
      var result = function (predicate, item) {
        return true;
      };

      switch (switchParam) {
      case 'elemental':
        {
          result = function (p, item) {
            return (item.dmg !== p);
          };
          break;
        }
      case 'type':
        {
          result = function (p, item) {
            return (item.type.toLowerCase() !== p);
          };
          break;
        }
      case 'tier':
        {
          result = function (p, item) {
            return (item.tier.toLowerCase() !== p);
          };
          break;
        }
      case 'incomplete':
        {
          result = function (p, item) {
            return item.complete === true || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class');
          };
          break;
        }
      case 'complete':
        {
          result = function (p, item) {
            return (item.complete === false) || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class');
          };
          break;
        }
      case 'xpincomplete':
        {
          result = function (p, item) {
            return (item.xpComplete && item.hasXP) || (!item.hasXP);
          };
          break;
        }
      case 'xpcomplete':
        {
          result = function (p, item) {
            return (!item.xpComplete && item.hasXP) || (!item.hasXP);
          };
          break;
        }
      default:
        {
          result = function (p, item) {
            return (item.name.toLowerCase()
              .indexOf(p) === -1);
          };
        }
      }

      return result.bind(null, predicate);
    };

    // function outerHeight(el) {
    //   var height = el.offsetHeight;
    //   var style = getComputedStyle(el);
    //
    //   height += parseInt(style.marginTop) + parseInt(style.marginBottom);
    //   return height;
    // }
    //
    // function outerWidth(el) {
    //   var width = el.offsetWidth;
    //   var style = getComputedStyle(el);
    //
    //   width += parseInt(style.marginLeft) + parseInt(style.marginRight);
    //   return width;
    // }
    //
    // function cleanUI() {
    //   var weapons = document.querySelectorAll('.weapons');
    //   var armor = document.querySelectorAll('.armor');
    //
    //   var wHeight = _.reduce(weapons, function (memo, section) {
    //     var childHeight = 0;
    //     _.each(section.children, function(child) {
    //       childHeight += outerHeight(child);
    //     });
    //
    //
    //     if (childHeight > memo) {
    //       memo = childHeight;
    //     }
    //
    //     return memo;
    //   }, 0);
    //
    //   var aHeight = _.reduce(armor, function (memo, section) {
    //     var childHeight = 0;
    //     _.each(section.children, function(child) {
    //       childHeight += outerHeight(child);
    //     });
    //
    //
    //     if (childHeight > memo) {
    //       memo = childHeight;
    //     }
    //
    //     return memo;
    //   }, 0);
    //
    //   var style = document.createElement('style');
    //   style.type = 'text/css';
    //   style.innerHTML = '.armor { min-height: ' + (aHeight) + 'px; } .weapons { min-height: ' + (wHeight) + 'px; }';
    //   document.getElementsByTagName('head')[0].appendChild(style);
    // }
  }
})();
<commit_msg>Added is:upgraded and changed is:incomplete to be both XP and Mat based.<commit_after>(function () {
  'use strict';

  angular.module('dimApp')
    .directive('dimSearchFilter', SearchFilter);

  SearchFilter.$inject = [];

  function SearchFilter() {
    return {
      controller: SearchFilterCtrl,
      controllerAs: 'vm',
      bindToController: true,
      restrict: 'A',
      template: [
        '<input placeholder="filter items or is:arc" type="search" name="filter" ng-model="vm.search" ng-model-options="{ debounce: 500 }" ng-trim="true" ng-change="vm.filter()">'
      ].join('')
    };
  }

  SearchFilterCtrl.$inject = ['$scope', 'dimStoreService', '$timeout'];

  function SearchFilterCtrl($scope, dimStoreService, $timeout) {
    var vm = this;

    $scope.$on('dim-stores-updated', function (arg) {
      vm.filter();
    });

    vm.filter = function () {

      var filterValue = (vm.search) ? vm.search.toLowerCase() : '';
      var filterResults;
      var filterResult = '';
      var filterFn;
      var tempFns = [];
      var special = filterValue.indexOf('is:') >= 0;

      if (special) {
        filterResults = filterValue.split('is:');

        _.each(filterResults, function (filterResult) {
          filterResult = filterResult.trim();

          if (filterResult !== '') {
            if (['arc', 'solar', 'void', 'kinetic'].indexOf(filterResult) >= 0) {
              special = 'elemental';
            } else if (['primary', 'special', 'heavy', 'helmet',' legs', 'gauntlets', 'chest', 'class', 'classitem'].indexOf(filterResult) >= 0) {
              special = 'type';
            } else if (['basic', 'common', 'uncommon', 'rare', 'legendary', 'exotic'].indexOf(filterResult) >= 0) {
              special = 'tier';
            } else if (['incomplete'].indexOf(filterResult) >= 0) {
              special = 'incomplete';
            } else if (['complete'].indexOf(filterResult) >= 0) {
              special = 'complete';
            } else if (['xpincomplete'].indexOf(filterResult) >= 0) {
              special = 'xpincomplete';
            } else if (['xpcomplete'].indexOf(filterResult) >= 0) {
              special = 'xpcomplete';
            } else if (['upgraded'].indexOf(filterResult) >= 0) {
              special = 'upgraded';
            }

            tempFns.push(filterGenerator(filterResult, special));
          }
        });
      } else {
        tempFns.push(filterGenerator(filterValue, ''));
      }

      filterFn = function (item) {
        return (_.reduce(tempFns, function (memo, fn) {
          return memo || fn(item);
        }, false));
      };

      _.each(dimStoreService.getStores(), function (store) {
        _.chain(store.items)
          .each(function (item) {
            item.visible = true; // resets the visiblity
          })
          .filter(filterFn)
          .each(function (item) {
            item.visible = false; // hides it if it passes
          });
      });

      $timeout(dimStoreService.setHeights, 32);
    };

    var filterGenerator = function (predicate, switchParam) {
      var result = function (predicate, item) {
        return true;
      };

      switch (switchParam) {
      case 'elemental':
        {
          result = function (p, item) {
            return (item.dmg !== p);
          };
          break;
        }
      case 'type':
        {
          result = function (p, item) {
            return (item.type.toLowerCase() !== p);
          };
          break;
        }
      case 'tier':
        {
          result = function (p, item) {
            return (item.tier.toLowerCase() !== p);
          };
          break;
        }
      case 'incomplete':
        {
          result = function (p, item) {
            return ((item.complete === true || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class')) || ( (item.xpComplete && item.hasXP) || (!item.hasXP)));
          };
          break;
        }
      case 'complete':
        {
          result = function (p, item) {
            return (item.complete === false) || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class');
          };
          break;
        }
      case 'xpincomplete':
        {
          result = function (p, item) {
            return (item.xpComplete && item.hasXP) || (!item.hasXP);
          };
          break;
        }
      case 'xpcomplete':
        {
          result = function (p, item) {
            return (!item.xpComplete && item.hasXP) || (!item.hasXP);
          };
          break;
        }
      case 'upgraded': {
        result = function (p, item) {
          return ((item.complete === true || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class')) || ((!item.xpComplete && item.hasXP) || (!item.hasXP)));
        };
        break;
      }
      default:
        {
          result = function (p, item) {
            return (item.name.toLowerCase()
              .indexOf(p) === -1);
          };
        }
      }

      return result.bind(null, predicate);
    };

    // function outerHeight(el) {
    //   var height = el.offsetHeight;
    //   var style = getComputedStyle(el);
    //
    //   height += parseInt(style.marginTop) + parseInt(style.marginBottom);
    //   return height;
    // }
    //
    // function outerWidth(el) {
    //   var width = el.offsetWidth;
    //   var style = getComputedStyle(el);
    //
    //   width += parseInt(style.marginLeft) + parseInt(style.marginRight);
    //   return width;
    // }
    //
    // function cleanUI() {
    //   var weapons = document.querySelectorAll('.weapons');
    //   var armor = document.querySelectorAll('.armor');
    //
    //   var wHeight = _.reduce(weapons, function (memo, section) {
    //     var childHeight = 0;
    //     _.each(section.children, function(child) {
    //       childHeight += outerHeight(child);
    //     });
    //
    //
    //     if (childHeight > memo) {
    //       memo = childHeight;
    //     }
    //
    //     return memo;
    //   }, 0);
    //
    //   var aHeight = _.reduce(armor, function (memo, section) {
    //     var childHeight = 0;
    //     _.each(section.children, function(child) {
    //       childHeight += outerHeight(child);
    //     });
    //
    //
    //     if (childHeight > memo) {
    //       memo = childHeight;
    //     }
    //
    //     return memo;
    //   }, 0);
    //
    //   var style = document.createElement('style');
    //   style.type = 'text/css';
    //   style.innerHTML = '.armor { min-height: ' + (aHeight) + 'px; } .weapons { min-height: ' + (wHeight) + 'px; }';
    //   document.getElementsByTagName('head')[0].appendChild(style);
    // }
  }
})();
<|endoftext|>


<commit_before>/*jshint -W027*/

(function() {
  'use strict';

  angular.module('dimApp')
    .directive('dimStoreHeading', StoreHeading);

  StoreHeading.$inject = ['ngDialog'];

  function StoreHeading(ngDialog) {
    return {
      controller: StoreHeadingCtrl,
      controllerAs: 'vm',
      bindToController: true,
      scope: {
        'store': '=storeData'
      },
      link: Link,
      template: [
        '
',
        '

',
        '
{{:: vm.store.class || "Vault" }}
',
        '
{{:: vm.store.race }} {{:: vm.store.gender }}
',
        '
Level {{ vm.store.level }}
',
        '
{{ vm.store.powerLevel }}
',
        '
 {{ vm.store.glimmer }}
',
        '
 {{ vm.store.legendaryMarks }}
',
        '
',
        '

',
        '
',
        '

',
        '
',
        '

',
        '<dim-stats stats="vm.store.stats" ng-if="!vm.store.isVault"></dim-stats>',
        '
',
        '
',
        '
{{sort.substring(0,1)}}
',
        '
',
        '

',
        '
',
        '
',
        '
'
      ].join('')
    };

    function Link(scope, element) {
      var vm = scope.vm;
      $(document).ready(function() {
        element.scrollToFixed({
          marginTop: 61,
          fixed: function() {
            $(document.body).addClass('something-is-sticky');
            $(this).addClass('fixed-header');
          },
          unfixed: function() {
            $(document.body).removeClass('something-is-sticky');
            $(this).removeClass('fixed-header');
          }
        });
      });
    }
  }

  StoreHeadingCtrl.$inject = ['$scope', 'ngDialog'];

  function StoreHeadingCtrl($scope, ngDialog) {
    var vm = this;
    var dialogResult = null;

    vm.sortSize = {};
    if (vm.store.isVault) {
      $scope.$watchCollection('vm.store.items', function() {
        ['Weapons', 'Armor', 'General'].forEach(function(sort) {
          vm.sortSize[sort] = count(vm.store.items, {sort: sort});
        });
      });
    }

    vm.getLevelBar = function getLevelBar() {
        if (vm.store.percentToNextLevel) {
          return { width: vm.store.percentToNextLevel + '%' };
        }
        if (vm.store.progression && vm.store.progression.progressions) {
          var prestige = _.findWhere(vm.store.progression.progressions, {
            progressionHash: 2030054750
          });
          vm.xpTillMote = 'Prestige level: ' + prestige.level + '\n' +
            (25000 - prestige.progressToNextLevel) +
            'xp until 5 motes of light';
          return { width: (prestige.progressToNextLevel)/250 + '%' };
        }
        return '';
      };

    vm.openLoadoutPopup = function openLoadoutPopup(e) {
      e.stopPropagation();

      if (!_.isNull(dialogResult)) {
        dialogResult.close();
      } else {
        ngDialog.closeAll();

        dialogResult = ngDialog.open({
          template: '

',
          plain: true,
          appendTo: 'div[loadout-id="' + vm.store.id + '"]',
          overlay: false,
          className: 'loadout-popup',
          showClose: false,
          scope: $scope
        });

        dialogResult.closePromise.then(function(data) {
          dialogResult = null;
        });
      }
      };
  }
})();
<commit_msg>Remove hardcoded numbers from prestige rank<commit_after>/*jshint -W027*/

(function() {
  'use strict';

  angular.module('dimApp')
    .directive('dimStoreHeading', StoreHeading);

  StoreHeading.$inject = ['ngDialog'];

  function StoreHeading(ngDialog) {
    return {
      controller: StoreHeadingCtrl,
      controllerAs: 'vm',
      bindToController: true,
      scope: {
        'store': '=storeData'
      },
      link: Link,
      template: [
        '
',
        '

',
        '
{{:: vm.store.class || "Vault" }}
',
        '
{{:: vm.store.race }} {{:: vm.store.gender }}
',
        '
Level {{ vm.store.level }}
',
        '
{{ vm.store.powerLevel }}
',
        '
 {{ vm.store.glimmer }}
',
        '
 {{ vm.store.legendaryMarks }}
',
        '
',
        '

',
        '
',
        '

',
        '
',
        '

',
        '<dim-stats stats="vm.store.stats" ng-if="!vm.store.isVault"></dim-stats>',
        '
',
        '
',
        '
{{sort.substring(0,1)}}
',
        '
',
        '

',
        '
',
        '
',
        '
'
      ].join('')
    };

    function Link(scope, element) {
      var vm = scope.vm;
      $(document).ready(function() {
        element.scrollToFixed({
          marginTop: 61,
          fixed: function() {
            $(document.body).addClass('something-is-sticky');
            $(this).addClass('fixed-header');
          },
          unfixed: function() {
            $(document.body).removeClass('something-is-sticky');
            $(this).removeClass('fixed-header');
          }
        });
      });
    }
  }

  StoreHeadingCtrl.$inject = ['$scope', 'ngDialog'];

  function StoreHeadingCtrl($scope, ngDialog) {
    var vm = this;
    var dialogResult = null;

    vm.sortSize = {};
    if (vm.store.isVault) {
      $scope.$watchCollection('vm.store.items', function() {
        ['Weapons', 'Armor', 'General'].forEach(function(sort) {
          vm.sortSize[sort] = count(vm.store.items, {sort: sort});
        });
      });
    }

    vm.getLevelBar = function getLevelBar() {
        if (vm.store.percentToNextLevel) {
          return { width: vm.store.percentToNextLevel + '%' };
        }
        if (vm.store.progression && vm.store.progression.progressions) {
          var prestige = _.findWhere(vm.store.progression.progressions, {
            progressionHash: 2030054750
          });
          vm.xpTillMote = 'Prestige level: ' + prestige.level + '\n' +
            (prestige.nextLevelAt - prestige.progressToNextLevel) +
            'xp until 5 motes of light';
          return { width: 100 * (prestige.progressToNextLevel / prestige.nextLevelAt) + '%' };
        }
        return '';
      };

    vm.openLoadoutPopup = function openLoadoutPopup(e) {
      e.stopPropagation();

      if (!_.isNull(dialogResult)) {
        dialogResult.close();
      } else {
        ngDialog.closeAll();

        dialogResult = ngDialog.open({
          template: '

',
          plain: true,
          appendTo: 'div[loadout-id="' + vm.store.id + '"]',
          overlay: false,
          className: 'loadout-popup',
          showClose: false,
          scope: $scope
        });

        dialogResult.closePromise.then(function(data) {
          dialogResult = null;
        });
      }
      };
  }
})();
<|endoftext|>


<commit_before>const _ = require('lodash');
const BaseModule = require('./baseModule');

/**
 * NOTE
 * microbit getGesture 결과 목록표
 * MICROBIT_ACCELEROMETER_EVT_TILT_UP	1
 * MICROBIT_ACCELEROMETER_EVT_TILT_DOWN	2
 * MICROBIT_ACCELEROMETER_EVT_TILT_LEFT	3
 * MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT	4
 * MICROBIT_ACCELEROMETER_EVT_FACE_UP	5
 * MICROBIT_ACCELEROMETER_EVT_FACE_DOWN	6
 * MICROBIT_ACCELEROMETER_EVT_FREEFALL	7
 * MICROBIT_ACCELEROMETER_EVT_3G	8
 * MICROBIT_ACCELEROMETER_EVT_6G	9
 * MICROBIT_ACCELEROMETER_EVT_8G	10
 * MICROBIT_ACCELEROMETER_EVT_SHAKE	11
 *
 */

const MICROBIT_BUFFER_SIZE = 16;
const functionKeys = {
    TEST_MESSAGE: 0xfa,
    RESET: 0xfe,
    CHECK_READY: 0xff,
    SET_LED: 0x01,
    SET_STRING: 0x02,
    SET_IMAGE: 0x03,
    SET_DIGITAL: 0x07,
    SET_ANALOG: 0x08,
    RESET_SCREEN: 0x09,
    SET_ANALOG_PERIOD: 0x10,
    SET_SERVO: 0x11,
    SET_SERVO_PERIOD: 0x12,
    SET_TONE: 0x13,
    SET_TEMPO: 0x14,
    SET_CUSTOM_IMAGE: 0x15,

    GET_LED: 0x31,
    GET_ANALOG: 0x32,
    GET_DIGITAL: 0x33,
    GET_BUTTON: 0x34,
    GET_LIGHT_LEVEL: 0x35,
    GET_TEMPERATURE: 0x36,
    GET_COMPASS_HEADING: 0x37,
    GET_ACCELEROMETER: 0x38,
    GET_PITCH: 0x39,
    GET_ROLL: 0x40,
    GET_GESTURE: 0x41,
    PLAY_NOTE: 0x04,
    CHANGE_BPM: 0x05,
    SET_BPM: 0x06,
    ACC_REGULAR: 0xaa,
    SENSOR_REGULAR: 0xab,
};

class Microbit2 extends BaseModule {
    constructor() {
        super();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.startChecksum = [0xff, 0x01];
        this.endChecksum = [0x0d, 0x0a];

        this.resetMicrobitStatusMap();
        this.commandQueue = [];
    }

    resetMicrobitStatusMap() {
        this.microbitStatusMap = {
            payload: undefined,
            sensorData: {
                digital: {},
                analog: {},
                led: {
                    0: [0, 0, 0, 0, 0],
                    1: [0, 0, 0, 0, 0],
                    2: [0, 0, 0, 0, 0],
                    3: [0, 0, 0, 0, 0],
                    4: [0, 0, 0, 0, 0],
                },
                button: false,
                lightLevel: 0,
                temperature: 0,
                compassHeading: 0,
                accelerometer: {
                    x: 0,
                    y: 0,
                    z: 0,
                    strength: 0,
                },
            },
        };
    }
    getStatusMapCodes() {
        return [170, 171, 172];
    }
    /**
     * payload 에 앞, 뒤 고정 버퍼를 추가한다.
     * @param {number} key
     * @param {number[]?} payload
     * @returns {Buffer}
     */
    makeBuffer(key, payload) {
        const payloadLength =
            MICROBIT_BUFFER_SIZE -
            this.startChecksum.length -
            this.endChecksum.length -
            1; // key length

        // payload 는 최대 payloadLength 까지. 이보다 적은 경우 0 으로 fill.
        // payload 가 없는 경우는 빈 배열로 대체한다.
        const slicedPayload = _.chain(_.fill(Array(payloadLength), 0))
            .zipWith(payload || [], (original, input) => input || 0)
            .slice(0, payloadLength)
            .value();

        return Buffer.from(
            this.startChecksum
                .concat([key])
                .concat(...slicedPayload)
                .concat(this.endChecksum)
        );
    }

    // socketReconnection() {
    //     this.socket.send(this.handler.encode());
    // }

    requestInitialData() {
        const aa = this.makeBuffer(functionKeys.CHECK_READY);
        console.log('send to : ', aa);
        return aa;
    }

    // validateLocalData(data) {
    //     return data[0] === this.BUFFER_END_ACK_FLAG;
    // }

    setSerialPort(sp) {
        this.sp = sp;
    }

    setSocket(socket) {
        this.socket = socket;
    }

    /**
     * 정상 handshake response 는 [ff 11 22 33 ~ 0d 0a] 이다.
     * @param {Buffer} data
     * @param config
     * @returns {boolean}
     */
    checkInitialData(data, config) {
        // data[1~4] 는 commandId 로, 체크하지 않는다.
        console.log('checkInitialData : ', data);
        // return true;
        return (
            data[0] === functionKeys.CHECK_READY &&
            data[1] === 0x11 &&
            data[2] === 0x22 &&
            data[3] === 0x33
        );
    }

    requestRemoteData(handler) {
        handler.write('payload', this.microbitStatusMap);
    }

    lostController(connector, stateCallback) {
        // 아무일도 안하지만, 해당 함수가 선언되면 lostTimer 가 선언되지 않음.
    }

    handleRemoteData(handler) {
        const type = handler.read('type') || undefined;
        const payload = handler.read('payload') || {};

        // 리퀘스트 목록이 마지막으로 확인한 버전과 다르기 때문에, 업데이트한다.
        // 업데이트는 중복되지 않는 id 의 커맨드만 뒤에 추가한다.
        this.commandQueue.push({
            type,
            payload,
        });
    }

    requestLocalData() {
        if (this.commandQueue.length !== 0 && !this.pending) {
            this.pending = true;
            const { type, payload } = this.commandQueue.shift();
            switch (type) {
                case functionKeys.SET_LED: {
                    const { x, y, value } = payload;
                    const valueType = {
                        on: 1,
                        off: 0,
                        toggle: 2,
                    };
                    // 임시로 statusMap 을 업데이트 한다.
                    // 실제 값은 getLED 시 다시 업데이트 된다.
                    let dummyCacheLedValue = 0;
                    if (value === 'toggle') {
                        dummyCacheLedValue =
                            _.get(this.microbitStatusMap, [
                                'sensorData',
                                'led',
                                x,
                                y,
                            ]) === 0
                                ? 1
                                : 0;
                    } else {
                        dummyCacheLedValue = valueType[value];
                    }
                    _.set(
                        this.microbitStatusMap,
                        ['sensorData', 'led', x, y],
                        dummyCacheLedValue
                    );
                    return this.makeBuffer(functionKeys.SET_LED, [
                        x,
                        y,
                        valueType[value],
                    ]);
                }

                case functionKeys.SET_CUSTOM_IMAGE: {
                    const { value } = payload;
                    let dataToSend = [];
                    let temp = 0;
                    for (let i = 0; i < 25; i++) {
                        let x = parseInt(i / 5);
                        let y = i % 5;
                        if (value[x][y] == 1) {
                            temp += value[x][y] * Math.pow(2, 24 - i);
                        }
                    }
                    while (temp > 0) {
                        let remainder = temp % 256;
                        temp = parseInt(temp / 256);
                        dataToSend.unshift(remainder);
                    }
                    return this.makeBuffer(
                        functionKeys.SET_CUSTOM_IMAGE,
                        dataToSend
                    );
                }

                case functionKeys.GET_LED: {
                    const { x, y } = payload;
                    return this.makeBuffer(functionKeys.GET_LED, [x, y]);
                }
                case functionKeys.RESET:
                    this.resetMicrobitStatusMap();
                    return this.makeBuffer(functionKeys.RESET);
                case functionKeys.SET_STRING:
                    return this.makeBuffer(
                        functionKeys.SET_STRING,
                        Buffer.from(payload).toJSON().data
                    );
                case functionKeys.SET_DIGITAL: {
                    const { pinNumber, value } = payload;
                    return this.makeBuffer(functionKeys.SET_DIGITAL, [
                        pinNumber,
                        value,
                    ]);
                }
                // 전달값이 uint8_t 이상인 경우
                case functionKeys.SET_ANALOG:
                case functionKeys.SET_SERVO_PERIOD:
                case functionKeys.SET_ANALOG_PERIOD: {
                    const { pinNumber, value } = payload;
                    const uInt8Value = [];
                    let targetValue = value;
                    while (targetValue) {
                        uInt8Value.push(targetValue & 0xff);
                        targetValue >>= 8;
                    }
                    return this.makeBuffer(type, [pinNumber, ...uInt8Value]);
                }
                case functionKeys.SET_TONE: {
                    // const { noteValue, beatValue } = payload; console.
                    const noteValue = payload.noteValue;
                    const beatValue = payload.beatValue;
                    const noteValue1 = noteValue / 256;
                    const noteValue2 = noteValue % 256;
                    const beatValue1 = beatValue / (256 * 256 * 256);
                    const beatValue2 = beatValue / (256 * 256);
                    const beatValue3 = beatValue / 256;
                    const beatValue4 = beatValue % 256;

                    return this.makeBuffer(type, [
                        noteValue1,
                        noteValue2,
                        beatValue1,
                        beatValue2,
                        beatValue3,
                        beatValue4,
                    ]);
                }
                case functionKeys.SET_TEMPO: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }
                // 필요한 값이 value property 하나인 경우 전부
                case functionKeys.SET_SERVO:
                case functionKeys.SET_IMAGE: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }

                case functionKeys.GET_ANALOG:
                case functionKeys.GET_DIGITAL: {
                    const value = payload[0];
                    return this.makeBuffer(type, [value]);
                }

                case functionKeys.GET_LIGHT_LEVEL:
                // return this.makeBuffer(type);
                // 그냥 값 없이 바로 커맨드만 보내는 경우
                case functionKeys.GET_ACCELEROMETER:
                case functionKeys.GET_BUTTON:
                case functionKeys.GET_TEMPERATURE:
                case functionKeys.GET_COMPASS_HEADING:
                case functionKeys.RESET_SCREEN:
                case functionKeys.GET_PITCH:
                case functionKeys.GET_ROLL:
                case functionKeys.GET_GESTURE:
                    // return this.makeBuffer(type);
                    return null;
                default:
                    return null;
            }
        }
    }

    /**
     *
     * @param {string|string[]} path
     * @param value
     */
    setStatusMap(path, value) {
        _.set(this.microbitStatusMap, path, value);
    }

    handleLocalData(data) {
        this.pending = false;
        console.log('received from microbit : ', data);
        const receivedCommandType = data[0];
        switch (receivedCommandType) {
            case functionKeys.RST:
            case functionKeys.SET_BPM:
            case functionKeys.PLAY_NOTE:
            case functionKeys.GET_ACCELEROMETER:
            case functionKeys.GET_SENSOR:
            case functionKeys.GET_BUTTON:
            case functionKeys.SET_LED:
            case functionKeys.SET_STRING:
            case functionKeys.SET_IMAGE: {
                break;
            }
            case functionKeys.GET_LED: {
                this.setStatusMap(
                    ['sensorData', 'led', data[1], data[2]],
                    data[3]
                );
                break;
            }
            // case functionKeys.GET_LIGHT_LEVEL: {
            //     // data = [pinNumber, value]
            //     const light = Number(Buffer.from([data[1]]).readUInt8(0));
            //     this.setStatusMap(['sensorData', 'lightLevel'], light);
            //     break;
            // }
            case functionKeys.GET_ANALOG: {
                // data = [pinNumber, value{2} ]
                this.setStatusMap(
                    ['sensorData', 'analog', data[1]],
                    Buffer.from([data[2], data[3]]).readInt16LE(0)
                );
                break;
            }

            case functionKeys.GET_DIGITAL: {
                // data = [pinNumber, value]
                this.setStatusMap(['sensorData', 'digital', data[1]], data[2]);
                break;
            }

            case functionKeys.ACC_REGULAR: {
                // console.log('ACC_REGULAR RECEIVED');
                const x = Number(
                    Buffer.from([data[1], data[2]]).readInt16LE(0)
                );
                const y = Number(
                    Buffer.from([data[3], data[4]]).readInt16LE(0)
                );
                const z = Number(
                    Buffer.from([data[5], data[6]]).readInt16LE(0)
                );
                const strength = Math.sqrt(x * x + y * y + z * z);
                const pitch = Number(
                    Buffer.from([data[7], data[8]]).readInt16LE(0)
                );
                const roll = Number(
                    Buffer.from([data[9], data[10]]).readInt16LE(0)
                );
                const light = Number(Buffer.from([data[11]]).readUInt8(0));
                const temperature = Number(
                    Buffer.from([data[12]]).readUInt8(0)
                );
                const button = Number(Buffer.from([data[13]]).readUInt8(0));
                // console.log(x, y, z, 'ACC', pitch, roll);
                // console.log(light, temperature, button);

                this.setStatusMap(['sensorData', 'accelerometer', 'x'], x);
                this.setStatusMap(['sensorData', 'accelerometer', 'y'], y);
                this.setStatusMap(['sensorData', 'accelerometer', 'z'], z);

                this.setStatusMap(
                    ['sensorData', 'accelerometer', 'strength'],
                    strength
                );
                this.setStatusMap(['sensorData', 'tilt', 'pitch']);
                this.setStatusMap(['sensorData', 'tilt', 'roll'], roll);
                this.setStatusMap(['sensorData', 'lightLevel'], light);
                this.setStatusMap(['sensorData', 'temperature'], temperature);
                this.setStatusMap(['sensorData', 'button'], button);
                break;
            }
            case functionKeys.SENSOR_REGULAR: {
                // console.log('SENSOR_REGULAR RECEIVED');
                this.setStatusMap(
                    ['sensorData', 'compassHeading'],
                    Buffer.from([data[1], data[2]]).readUInt16LE(0)
                );
                this.setStatusMap(
                    ['sensorData', 'gesture'],
                    Buffer.from([data[3]]).readUInt8(0)
                );
                break;
            }
        }
        // this.socket.send('abcde');
        // const count = data[data.length - 3];
        // const blockId = this.executeCheckList[count];
        // if (blockId) {
        //     const socketData = this.handler.encode();
        //     socketData.blockId = blockId;
        //     this.setSocketData({
        //         data,
        //         socketData,
        //     });
        //     this.socket.send(socketData);
        // }
    }

    disconnect(connect) {
        connect.close();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.isDraing = false;
        this.sp = null;
    }

    reset() {
        this.sp = null;
    }
}

module.exports = new Microbit2();
<commit_msg>servo motor 수정<commit_after>const _ = require('lodash');
const BaseModule = require('./baseModule');

/**
 * NOTE
 * microbit getGesture 결과 목록표
 * MICROBIT_ACCELEROMETER_EVT_TILT_UP	1
 * MICROBIT_ACCELEROMETER_EVT_TILT_DOWN	2
 * MICROBIT_ACCELEROMETER_EVT_TILT_LEFT	3
 * MICROBIT_ACCELEROMETER_EVT_TILT_RIGHT	4
 * MICROBIT_ACCELEROMETER_EVT_FACE_UP	5
 * MICROBIT_ACCELEROMETER_EVT_FACE_DOWN	6
 * MICROBIT_ACCELEROMETER_EVT_FREEFALL	7
 * MICROBIT_ACCELEROMETER_EVT_3G	8
 * MICROBIT_ACCELEROMETER_EVT_6G	9
 * MICROBIT_ACCELEROMETER_EVT_8G	10
 * MICROBIT_ACCELEROMETER_EVT_SHAKE	11
 *
 */

const MICROBIT_BUFFER_SIZE = 16;
const functionKeys = {
    TEST_MESSAGE: 0xfa,
    RESET: 0xfe,
    CHECK_READY: 0xff,
    SET_LED: 0x01,
    SET_STRING: 0x02,
    SET_IMAGE: 0x03,
    SET_DIGITAL: 0x07,
    SET_ANALOG: 0x08,
    RESET_SCREEN: 0x09,
    SET_ANALOG_PERIOD: 0x10,
    SET_SERVO: 0x11,
    SET_SERVO_PERIOD: 0x12,
    SET_TONE: 0x13,
    SET_TEMPO: 0x14,
    SET_CUSTOM_IMAGE: 0x15,

    GET_LED: 0x31,
    GET_ANALOG: 0x32,
    GET_DIGITAL: 0x33,
    GET_BUTTON: 0x34,
    GET_LIGHT_LEVEL: 0x35,
    GET_TEMPERATURE: 0x36,
    GET_COMPASS_HEADING: 0x37,
    GET_ACCELEROMETER: 0x38,
    GET_PITCH: 0x39,
    GET_ROLL: 0x40,
    GET_GESTURE: 0x41,
    PLAY_NOTE: 0x04,
    CHANGE_BPM: 0x05,
    SET_BPM: 0x06,
    ACC_REGULAR: 0xaa,
    SENSOR_REGULAR: 0xab,
};

class Microbit2 extends BaseModule {
    constructor() {
        super();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.startChecksum = [0xff, 0x01];
        this.endChecksum = [0x0d, 0x0a];

        this.resetMicrobitStatusMap();
        this.commandQueue = [];
    }

    resetMicrobitStatusMap() {
        this.microbitStatusMap = {
            payload: undefined,
            sensorData: {
                digital: {},
                analog: {},
                led: {
                    0: [0, 0, 0, 0, 0],
                    1: [0, 0, 0, 0, 0],
                    2: [0, 0, 0, 0, 0],
                    3: [0, 0, 0, 0, 0],
                    4: [0, 0, 0, 0, 0],
                },
                button: false,
                lightLevel: 0,
                temperature: 0,
                compassHeading: 0,
                accelerometer: {
                    x: 0,
                    y: 0,
                    z: 0,
                    strength: 0,
                },
            },
        };
    }
    getStatusMapCodes() {
        return [170, 171, 172];
    }
    /**
     * payload 에 앞, 뒤 고정 버퍼를 추가한다.
     * @param {number} key
     * @param {number[]?} payload
     * @returns {Buffer}
     */
    makeBuffer(key, payload) {
        const payloadLength =
            MICROBIT_BUFFER_SIZE -
            this.startChecksum.length -
            this.endChecksum.length -
            1; // key length

        // payload 는 최대 payloadLength 까지. 이보다 적은 경우 0 으로 fill.
        // payload 가 없는 경우는 빈 배열로 대체한다.
        const slicedPayload = _.chain(_.fill(Array(payloadLength), 0))
            .zipWith(payload || [], (original, input) => input || 0)
            .slice(0, payloadLength)
            .value();

        return Buffer.from(
            this.startChecksum
                .concat([key])
                .concat(...slicedPayload)
                .concat(this.endChecksum)
        );
    }

    // socketReconnection() {
    //     this.socket.send(this.handler.encode());
    // }

    requestInitialData() {
        const aa = this.makeBuffer(functionKeys.CHECK_READY);
        console.log('send to : ', aa);
        return aa;
    }

    // validateLocalData(data) {
    //     return data[0] === this.BUFFER_END_ACK_FLAG;
    // }

    setSerialPort(sp) {
        this.sp = sp;
    }

    setSocket(socket) {
        this.socket = socket;
    }

    /**
     * 정상 handshake response 는 [ff 11 22 33 ~ 0d 0a] 이다.
     * @param {Buffer} data
     * @param config
     * @returns {boolean}
     */
    checkInitialData(data, config) {
        // data[1~4] 는 commandId 로, 체크하지 않는다.
        console.log('checkInitialData : ', data);
        // return true;
        return (
            data[0] === functionKeys.CHECK_READY &&
            data[1] === 0x11 &&
            data[2] === 0x22 &&
            data[3] === 0x33
        );
    }

    requestRemoteData(handler) {
        handler.write('payload', this.microbitStatusMap);
    }

    lostController(connector, stateCallback) {
        // 아무일도 안하지만, 해당 함수가 선언되면 lostTimer 가 선언되지 않음.
    }

    handleRemoteData(handler) {
        const type = handler.read('type') || undefined;
        const payload = handler.read('payload') || {};

        // 리퀘스트 목록이 마지막으로 확인한 버전과 다르기 때문에, 업데이트한다.
        // 업데이트는 중복되지 않는 id 의 커맨드만 뒤에 추가한다.
        this.commandQueue.push({
            type,
            payload,
        });
    }

    requestLocalData() {
        if (this.commandQueue.length !== 0 && !this.pending) {
            this.pending = true;
            const { type, payload } = this.commandQueue.shift();
            switch (type) {
                case functionKeys.SET_LED: {
                    const { x, y, value } = payload;
                    const valueType = {
                        on: 1,
                        off: 0,
                        toggle: 2,
                    };
                    // 임시로 statusMap 을 업데이트 한다.
                    // 실제 값은 getLED 시 다시 업데이트 된다.
                    let dummyCacheLedValue = 0;
                    if (value === 'toggle') {
                        dummyCacheLedValue =
                            _.get(this.microbitStatusMap, [
                                'sensorData',
                                'led',
                                x,
                                y,
                            ]) === 0
                                ? 1
                                : 0;
                    } else {
                        dummyCacheLedValue = valueType[value];
                    }
                    _.set(
                        this.microbitStatusMap,
                        ['sensorData', 'led', x, y],
                        dummyCacheLedValue
                    );
                    return this.makeBuffer(functionKeys.SET_LED, [
                        x,
                        y,
                        valueType[value],
                    ]);
                }

                case functionKeys.SET_CUSTOM_IMAGE: {
                    const { value } = payload;
                    let dataToSend = [];
                    let temp = 0;
                    for (let i = 0; i < 25; i++) {
                        let x = parseInt(i / 5);
                        let y = i % 5;
                        if (value[x][y] == 1) {
                            temp += value[x][y] * Math.pow(2, 24 - i);
                        }
                    }
                    while (temp > 0) {
                        let remainder = temp % 256;
                        temp = parseInt(temp / 256);
                        dataToSend.unshift(remainder);
                    }
                    return this.makeBuffer(
                        functionKeys.SET_CUSTOM_IMAGE,
                        dataToSend
                    );
                }

                case functionKeys.GET_LED: {
                    const { x, y } = payload;
                    return this.makeBuffer(functionKeys.GET_LED, [x, y]);
                }
                case functionKeys.RESET:
                    this.resetMicrobitStatusMap();
                    return this.makeBuffer(functionKeys.RESET);
                case functionKeys.SET_STRING:
                    return this.makeBuffer(
                        functionKeys.SET_STRING,
                        Buffer.from(payload).toJSON().data
                    );
                case functionKeys.SET_DIGITAL: {
                    const { pinNumber, value } = payload;
                    return this.makeBuffer(functionKeys.SET_DIGITAL, [
                        pinNumber,
                        value,
                    ]);
                }
                // 전달값이 uint8_t 이상인 경우
                case functionKeys.SET_ANALOG:
                case functionKeys.SET_SERVO_PERIOD:
                case functionKeys.SET_ANALOG_PERIOD: {
                    const { pinNumber, value } = payload;
                    const uInt8Value = [];
                    let targetValue = value;
                    while (targetValue) {
                        uInt8Value.push(targetValue & 0xff);
                        targetValue >>= 8;
                    }
                    return this.makeBuffer(type, [pinNumber, ...uInt8Value]);
                }
                case functionKeys.SET_TONE: {
                    // const { noteValue, beatValue } = payload; console.
                    const noteValue = payload.noteValue;
                    const beatValue = payload.beatValue;
                    const noteValue1 = noteValue / 256;
                    const noteValue2 = noteValue % 256;
                    const beatValue1 = beatValue / (256 * 256 * 256);
                    const beatValue2 = beatValue / (256 * 256);
                    const beatValue3 = beatValue / 256;
                    const beatValue4 = beatValue % 256;

                    return this.makeBuffer(type, [
                        noteValue1,
                        noteValue2,
                        beatValue1,
                        beatValue2,
                        beatValue3,
                        beatValue4,
                    ]);
                }
                case functionKeys.SET_TEMPO: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }
                // 필요한 값이 value property 하나인 경우 전부
                case functionKeys.SET_SERVO: {
                    const { pinNumber, value } = payload;
                    return this.makeBuffer(type, [pinNumber, value]);
                }
                case functionKeys.SET_IMAGE: {
                    const { value } = payload;
                    return this.makeBuffer(type, [value]);
                }

                case functionKeys.GET_ANALOG:
                case functionKeys.GET_DIGITAL: {
                    const value = payload[0];
                    return this.makeBuffer(type, [value]);
                }

                case functionKeys.GET_LIGHT_LEVEL:
                // return this.makeBuffer(type);
                // 그냥 값 없이 바로 커맨드만 보내는 경우
                case functionKeys.GET_ACCELEROMETER:
                case functionKeys.GET_BUTTON:
                case functionKeys.GET_TEMPERATURE:
                case functionKeys.GET_COMPASS_HEADING:
                case functionKeys.RESET_SCREEN:
                case functionKeys.GET_PITCH:
                case functionKeys.GET_ROLL:
                case functionKeys.GET_GESTURE:
                    // return this.makeBuffer(type);
                    return null;
                default:
                    return null;
            }
        }
    }

    /**
     *
     * @param {string|string[]} path
     * @param value
     */
    setStatusMap(path, value) {
        _.set(this.microbitStatusMap, path, value);
    }

    handleLocalData(data) {
        this.pending = false;
        console.log('received from microbit : ', data);
        const receivedCommandType = data[0];
        switch (receivedCommandType) {
            case functionKeys.RST:
            case functionKeys.SET_BPM:
            case functionKeys.PLAY_NOTE:
            case functionKeys.GET_ACCELEROMETER:
            case functionKeys.GET_SENSOR:
            case functionKeys.GET_BUTTON:
            case functionKeys.SET_LED:
            case functionKeys.SET_STRING:
            case functionKeys.SET_IMAGE: {
                break;
            }
            case functionKeys.GET_LED: {
                this.setStatusMap(
                    ['sensorData', 'led', data[1], data[2]],
                    data[3]
                );
                break;
            }
            // case functionKeys.GET_LIGHT_LEVEL: {
            //     // data = [pinNumber, value]
            //     const light = Number(Buffer.from([data[1]]).readUInt8(0));
            //     this.setStatusMap(['sensorData', 'lightLevel'], light);
            //     break;
            // }
            case functionKeys.GET_ANALOG: {
                // data = [pinNumber, value{2} ]
                this.setStatusMap(
                    ['sensorData', 'analog', data[1]],
                    Buffer.from([data[2], data[3]]).readInt16LE(0)
                );
                break;
            }

            case functionKeys.GET_DIGITAL: {
                // data = [pinNumber, value]
                this.setStatusMap(['sensorData', 'digital', data[1]], data[2]);
                break;
            }

            case functionKeys.ACC_REGULAR: {
                // console.log('ACC_REGULAR RECEIVED');
                const x = Number(
                    Buffer.from([data[1], data[2]]).readInt16LE(0)
                );
                const y = Number(
                    Buffer.from([data[3], data[4]]).readInt16LE(0)
                );
                const z = Number(
                    Buffer.from([data[5], data[6]]).readInt16LE(0)
                );
                const strength = Math.sqrt(x * x + y * y + z * z);
                const pitch = Number(
                    Buffer.from([data[7], data[8]]).readInt16LE(0)
                );
                const roll = Number(
                    Buffer.from([data[9], data[10]]).readInt16LE(0)
                );
                const light = Number(Buffer.from([data[11]]).readUInt8(0));
                const temperature = Number(
                    Buffer.from([data[12]]).readUInt8(0)
                );
                const button = Number(Buffer.from([data[13]]).readUInt8(0));
                // console.log(x, y, z, 'ACC', pitch, roll);
                // console.log(light, temperature, button);

                this.setStatusMap(['sensorData', 'accelerometer', 'x'], x);
                this.setStatusMap(['sensorData', 'accelerometer', 'y'], y);
                this.setStatusMap(['sensorData', 'accelerometer', 'z'], z);

                this.setStatusMap(
                    ['sensorData', 'accelerometer', 'strength'],
                    strength
                );
                this.setStatusMap(['sensorData', 'tilt', 'pitch']);
                this.setStatusMap(['sensorData', 'tilt', 'roll'], roll);
                this.setStatusMap(['sensorData', 'lightLevel'], light);
                this.setStatusMap(['sensorData', 'temperature'], temperature);
                this.setStatusMap(['sensorData', 'button'], button);
                break;
            }
            case functionKeys.SENSOR_REGULAR: {
                // console.log('SENSOR_REGULAR RECEIVED');
                this.setStatusMap(
                    ['sensorData', 'compassHeading'],
                    Buffer.from([data[1], data[2]]).readUInt16LE(0)
                );
                this.setStatusMap(
                    ['sensorData', 'gesture'],
                    Buffer.from([data[3]]).readUInt8(0)
                );
                break;
            }
        }
        // this.socket.send('abcde');
        // const count = data[data.length - 3];
        // const blockId = this.executeCheckList[count];
        // if (blockId) {
        //     const socketData = this.handler.encode();
        //     socketData.blockId = blockId;
        //     this.setSocketData({
        //         data,
        //         socketData,
        //     });
        //     this.socket.send(socketData);
        // }
    }

    disconnect(connect) {
        connect.close();
        this.sendBuffers = [];
        this.recentCheckData = [];
        this.isDraing = false;
        this.sp = null;
    }

    reset() {
        this.sp = null;
    }
}

module.exports = new Microbit2();
<|endoftext|>