<commit_before>#!/usr/bin/env node
var fs = require('fs'),
    path = require('path'),
    helpers = require('../helpers'),
    logicCli = require('./logic'),
    argv = require('optimist').argv;

// ----------------------------------------
//  Main
// ----------------------------------------
var params = argv._;
var command = params[0];
var first = helpers.sanitizePath(params[1]);
var second = helpers.sanitizePath(params[2]);
var templatesDir = path.join(__dirname, "../..", "samples");

var packagesLocalFolder = path.join(process.cwd(), "/packages");
var packagesAtlasboardFolder = path.join(__dirname, "../../packages");

// ----------------------------------
// Generate
// ----------------------------------
if (command === "generate") {
  var packageFolder = "default";
  var itemName = second;
  if (second.indexOf('#') > -1){ //package namespacing
    packageFolder = second.split('#')[0];
    itemName = second.split('#')[1];
  }
  return logicCli.generate(process.cwd(), packageFolder, first, itemName, function(err){
    if (err){
      console.error(err);
    }
  });
}
// ----------------------------------
// New item
// ----------------------------------
else if (command === "new") {
  var srcDir = path.join(templatesDir, "project");
  var destDir = path.join(process.cwd(), first);

  return logicCli.newProject (srcDir, destDir, function(err){
    if (err){
      console.error(err);
    }
    else{
      process.chdir(first);
      var childProcess = require('child_process');
      var child = childProcess.spawn('npm', ["install"], {stdio: 'inherit'});
      console.log ('Installing npm dependencies...');
      child.on('exit', function () {
        console.log('\nSUCCESS !!');
        console.log('\nNew project "%s" successfully created. Now:\n', first);
        console.log(' 1. cd ' + first);
        console.log(' 2. start your server with `atlasboard start`');
        console.log(' 3. visit it at http://localhost:3000\n');
      });
    }
  });
}
// ----------------------------------
// Display component list
// ----------------------------------
else if (command === "list") {
  function parse(package){
    console.log('\t\tPackage "' + path.basename(package.dir) + '":');
    package.items.forEach(function(item){
      console.log('\t\t    - ' + path.basename(item, '.js'));
    });
  }

  return logicCli.list([packagesLocalFolder, packagesAtlasboardFolder], function(err, packages){
    if (err){
      console.error('Error reading package folder');
      return;
    }

    console.log('Available widgets and jobs within all package folders:');
    packages.forEach(function(package){
      console.log(' ' + package.package);
      console.log('\t- Widgets:');
      package.widgets.forEach(parse);

      console.log('\t- Jobs:');
      package.jobs.forEach(parse);
    });
  });
}
// ----------------------------------
// Launch app
// ----------------------------------
else if (command === "start") {
  return logicCli.start(first); //first parameters can be the port number
}
else{
  // ----------------------------------
  // No command found.
  // if we got here we better show the help message
  // ----------------------------------
  var projectPackageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "../..", "package.json")));
  var version = projectPackageJson.version;
  console.log("\nAtlasBoard Version %s\n", version);
  console.log(" LIST OF AVAILABLE COMMANDS:\n");
  var commands = [
    {command: "new NAME", description: "Creates a new fully functional dashboard with the name given in NAME whose base lies in the current directory."},
    {command: "list", description: "List all available components (widgets or jobs) within all available packages."},
    {command: "generate (widget/dashboard/job) NAME", description: "Generates a basic widget/dashboard/job with the given NAME when run in an AtlasBoard project base directory."},
    {command: "start", description: "When run in a project's base directory, starts the AtlasBoard server."},
    {command: "help", description: "Displays this help text."}
];
  commands.forEach(function(item) {
    console.log(" atlasboard %s\n    %s\n", item.command, item.description);
  });
}<commit_msg>Handle errors on child process when executing "npm install".<commit_after>#!/usr/bin/env node
var fs = require('fs'),
    path = require('path'),
    helpers = require('../helpers'),
    logicCli = require('./logic'),
    argv = require('optimist').argv;

// ----------------------------------------
//  Main
// ----------------------------------------
var params = argv._;
var command = params[0];
var first = helpers.sanitizePath(params[1]);
var second = helpers.sanitizePath(params[2]);
var templatesDir = path.join(__dirname, "../..", "samples");

var packagesLocalFolder = path.join(process.cwd(), "/packages");
var packagesAtlasboardFolder = path.join(__dirname, "../../packages");

// ----------------------------------
// Generate
// ----------------------------------
if (command === "generate") {
  var packageFolder = "default";
  var itemName = second;
  if (second.indexOf('#') > -1){ //package namespacing
    packageFolder = second.split('#')[0];
    itemName = second.split('#')[1];
  }
  return logicCli.generate(process.cwd(), packageFolder, first, itemName, function(err){
    if (err){
      console.error(err);
    }
  });
}
// ----------------------------------
// New item
// ----------------------------------
else if (command === "new") {
  var srcDir = path.join(templatesDir, "project");
  var destDir = path.join(process.cwd(), first);
  return logicCli.newProject (srcDir, destDir, function(err){
    if (err){
      console.error(err);
    }
    else{
      process.chdir(first);
      var childProcess = require('child_process');
      var child = childProcess.spawn('npm', ["install"], {stdio: 'inherit'});
      console.log ('Installing npm dependencies...');
      child.on('error', function () {
        console.log('\nError installing dependencies. Please run "npm install" inside the dashboard directory');
      });
      child.on('exit', function () {
        console.log('\nSUCCESS !!');
        console.log('\nNew project "%s" successfully created. Now:\n', first);
        console.log(' 1. cd ' + first);
        console.log(' 2. start your server with `atlasboard start`');
        console.log(' 3. visit it at http://localhost:3000\n');
      });
    }
  });
}
// ----------------------------------
// Display component list
// ----------------------------------
else if (command === "list") {
  function parse(package){
    console.log('\t\tPackage "' + path.basename(package.dir) + '":');
    package.items.forEach(function(item){
      console.log('\t\t    - ' + path.basename(item, '.js'));
    });
  }

  return logicCli.list([packagesLocalFolder, packagesAtlasboardFolder], function(err, packages){
    if (err){
      console.error('Error reading package folder');
      return;
    }

    console.log('Available widgets and jobs within all package folders:');
    packages.forEach(function(package){
      console.log(' ' + package.package);
      console.log('\t- Widgets:');
      package.widgets.forEach(parse);

      console.log('\t- Jobs:');
      package.jobs.forEach(parse);
    });
  });
}
// ----------------------------------
// Launch app
// ----------------------------------
else if (command === "start") {
  return logicCli.start(first); //first parameters can be the port number
}
else{
  // ----------------------------------
  // No command found.
  // if we got here we better show the help message
  // ----------------------------------
  var projectPackageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "../..", "package.json")));
  var version = projectPackageJson.version;
  console.log("\nAtlasBoard Version %s\n", version);
  console.log(" LIST OF AVAILABLE COMMANDS:\n");
  var commands = [
    {command: "new NAME", description: "Creates a new fully functional dashboard with the name given in NAME whose base lies in the current directory."},
    {command: "list", description: "List all available components (widgets or jobs) within all available packages."},
    {command: "generate (widget/dashboard/job) NAME", description: "Generates a basic widget/dashboard/job with the given NAME when run in an AtlasBoard project base directory."},
    {command: "start", description: "When run in a project's base directory, starts the AtlasBoard server."},
    {command: "help", description: "Displays this help text."}
];
  commands.forEach(function(item) {
    console.log(" atlasboard %s\n    %s\n", item.command, item.description);
  });
}<|endoftext|>


<commit_before>import { LightningElement, api, wire } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { deleteRecord, getFieldValue, getRecord, getRecordCreateDefaults, getRecordNotifyChange } from 'lightning/uiRecordApi';

import getDataImportModel from '@salesforce/apex/BGE_DataImportBatchEntry_CTRL.getDataImportModel';
import runBatchDryRun from '@salesforce/apex/BGE_DataImportBatchEntry_CTRL.runBatchDryRun';
import getDataImportRows from '@salesforce/apex/BGE_DataImportBatchEntry_CTRL.getDataImportRows';
import saveAndDryRunDataImport from '@salesforce/apex/GE_GiftEntryController.saveAndDryRunDataImport';

import { handleError } from 'c/utilTemplateBuilder';
import { isNotEmpty, isUndefined } from 'c/utilCommon';
import GeFormService from 'c/geFormService';

import geDonorColumnLabel from '@salesforce/label/c.geDonorColumnLabel';
import geDonationColumnLabel from '@salesforce/label/c.geDonationColumnLabel';
import bgeActionDelete from '@salesforce/label/c.bgeActionDelete';
import geBatchGiftsCount from '@salesforce/label/c.geBatchGiftsCount';
import geBatchGiftsTotal from '@salesforce/label/c.geBatchGiftsTotal';

import commonOpen from '@salesforce/label/c.commonOpen';
import GeLabelService from 'c/geLabelService';

import DATA_IMPORT_OBJECT from '@salesforce/schema/DataImport__c';
import STATUS_FIELD from '@salesforce/schema/DataImport__c.Status__c';
import FAILURE_INFORMATION_FIELD from '@salesforce/schema/DataImport__c.FailureInformation__c';
import DONATION_AMOUNT from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DONATION_RECORD_TYPE_NAME
    from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';
import BATCH_CURRENCY_ISO_CODE
    from '@salesforce/schema/DataImportBatch__c.CurrencyIsoCode';
import CURRENCY from '@salesforce/i18n/currency';

const URL_SUFFIX = '_URL';
const URL_LABEL_SUFFIX = '_URL_LABEL';
const REFERENCE = 'REFERENCE';

const columnTypeByDescribeType = {
    'DATE': 'date-local',
    'DATETIME': 'date',
    'EMAIL': 'email',
    'DOUBLE': 'number',
    'INTEGER': 'number',
    'LONG': 'number',
    'PERCENT': 'number',
    'STRING': 'text',
    'PICKLIST': 'text'
};

const COLUMNS = [
    { label: 'Status', fieldName: STATUS_FIELD.fieldApiName, type: 'text' },
    { label: 'Errors', fieldName: FAILURE_INFORMATION_FIELD.fieldApiName, type: 'text' },
    {
        label: geDonorColumnLabel, fieldName: 'donorLink', type: 'url',
        typeAttributes: { label: { fieldName: 'donorName' } }
    },
    {
        label: geDonationColumnLabel, fieldName: 'matchedRecordUrl', type: 'url',
        typeAttributes: { label: { fieldName: 'matchedRecordLabel' } }
    }
];

const ACTIONS_COLUMN = {
    type: 'action',
    typeAttributes: {
        rowActions: [
            { label: commonOpen, name: 'open' },
            { label: bgeActionDelete, name: 'delete' }
        ],
        menuAlignment: 'auto'
    }
};

export default class GeBatchGiftEntryTable extends LightningElement {
    @api batchId;

    get ready() {
        return this._columnsLoaded && this._dataImportModel;
    }

    _batchLoaded = false;
    data = [];
    get hasData() {
        return this.data.length > 0;
    }

    _columnsLoaded = false;
    _columnsBySourceFieldApiName = {};
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    @api title;
    @api total;
    @api expectedTotal;
    @api count;
    @api expectedCount;
    @api userDefinedBatchTableColumnNames;
    isLoaded = true;

    constructor() {
        super();
        COLUMNS.forEach(column => {
            this._columnsBySourceFieldApiName[column.fieldName] = column;
        });
    }

    connectedCallback() {
        this.loadBatch();
    }

    @api
    set sections(sections) {
        this._sections = sections;
        this.buildColumnsFromSections();
    }
    get sections() {
        return this._sections;
    }

    _dataImportModel;
    loadBatch() {
        getDataImportModel({batchId: this.batchId})
            .then(
                response => {
                    this._dataImportModel = JSON.parse(response);
                    this.setTableProperties();
                    this._batchLoaded = true;
                }
            )
            .catch(error => handleError(error));
    }

    _propertiesSet = false;
    setTableProperties() {
        if (this._propertiesSet) {
            return;
        }
        if (!this._dataImportObjectInfo) {
            return;
        }
        if (!this._dataImportModel) {
            return;
        }
        this._count = this._dataImportModel.totalCountOfRows;
        this._total = this._dataImportModel.totalRowAmount;
        this._dataImportModel.dataImportRows.forEach(row => {
            this.data.push(Object.assign(row,
                this.appendUrlColumnProperties.call(row.record,
                    this._dataImportObjectInfo)));
        });
        this.data = this.data.slice(0);
        this._propertiesSet = true;
    }

    get columns() {
        return this.userDefinedBatchTableColumnNames &&
        this.userDefinedBatchTableColumnNames.length > 0 ?
            this.userDefinedColumns.concat(ACTIONS_COLUMN) :
            this.allColumns.concat(ACTIONS_COLUMN);
    }

    get allColumns() {
        return Object.values(this._columnsBySourceFieldApiName);
    }

    get userDefinedColumns() {
        let userDefinedColumns = [];
        this.userDefinedBatchTableColumnNames.forEach(columnName => {
            if (this._columnsBySourceFieldApiName[`${columnName}${URL_SUFFIX}`]) {
                userDefinedColumns.push(this._columnsBySourceFieldApiName[`${columnName}${URL_SUFFIX}`]);
            } else if (isUndefined(this._columnsBySourceFieldApiName[columnName])) {
                return;
            } else {
                userDefinedColumns.push(this._columnsBySourceFieldApiName[columnName]);
            }
        });
        return userDefinedColumns;
    }

    buildColumnsFromSections() {
        this.sections.forEach(section => {
            section.elements.filter(e => e.elementType === 'field')
                .forEach(fieldElement => {
                    const fieldWrapper =
                        GeFormService.getFieldMappingWrapper(
                            fieldElement.dataImportFieldMappingDevNames[0]);

                    if (isNotEmpty(fieldWrapper)) {
                        const column = this.getColumn(fieldElement, fieldWrapper);
                        this._columnsBySourceFieldApiName[column.fieldName] = column;
                    }
                });
        });
        this._columnsLoaded = true;
    }

    @api
    upsertData(dataRow, idProperty) {
        const existingRowIndex = this.data.findIndex(row =>
            row[idProperty] === dataRow[idProperty]
        );

        if (existingRowIndex !== -1) {
            this.data.splice(existingRowIndex, 1, dataRow);
            this.data = this.data.splice(0);
        } else {
            this.data = [dataRow].concat(this.data);
        }
    }

    handleRowActions(event) {
        switch (event.detail.action.name) {
            case 'open':
                this.loadRow(event.detail.row);
                break;
            case 'delete':
                deleteRecord(event.detail.row.Id).then(() => {
                    this.deleteDIRow(event.detail.row);
                }).catch(error => {
                    handleError(error);
                });
                break;
        }
    }

    deleteDIRow(rowToDelete) {
        const isRowToDelete = row => row.Id === rowToDelete.Id;
        const index = this.data.findIndex(isRowToDelete);
        this.data.splice(index, 1);
        this.data = this.data.splice(0);
        this.dispatchEvent(new CustomEvent('delete', {
            detail: {
                amount: rowToDelete[DONATION_AMOUNT.fieldApiName]
            }
        }));
    }

    loadMoreData(event) {
        event.target.isLoading = true;
        const disableInfiniteLoading = function () {
            this.enableInfiniteLoading = false;
        }.bind(event.target);

        const disableIsLoading = function () {
            this.isLoading = false;
        }.bind(event.target);

        getDataImportRows({ batchId: this.batchId, offset: this.data.length })
            .then(rows => {
                rows.forEach(row => {
                    this.data.push(
                        Object.assign(row,
                            this.appendUrlColumnProperties.call(row.record,
                                this._dataImportObjectInfo)));
                });
                this.data = this.data.splice(0);
                if (this.data.length >= this.count) {
                    disableInfiniteLoading();
                }
                disableIsLoading();
            })
            .catch(error => {
                handleError(error);
            });
    }

    @api
    runBatchDryRun(callback) {
        runBatchDryRun({
            batchId: this.batchId,
            numberOfRowsToReturn: this.data.length
        })
            .then(result => {
                const dataImportModel = JSON.parse(result);
                this._count = dataImportModel.totalCountOfRows;
                this._total = dataImportModel.totalRowAmount;
                dataImportModel.dataImportRows.forEach(row => {
                    this.upsertData(
                        Object.assign(row,
                            this.appendUrlColumnProperties.call(row.record,
                                this._dataImportObjectInfo)), 'Id');
                });
            })
            .catch(error => {
                handleError(error);
            })
            .finally(() => {
                callback();
            });
    }

    get geBatchGiftsCountLabel() {
        return geBatchGiftsCount;
    }

    get geBatchGiftsTotalLabel() {
        return geBatchGiftsTotal;
    }

    loadRow(row) {
        this.dispatchEvent(new CustomEvent('loaddata', {
            detail: row
        }));
    }

    /*************************************************************************************
     * @description Internal setters used to communicate the current count and total
     *              up to the App, which needs them to keep track of whether the batch's
     *              expected totals match.
     */
    set _count(count) {
        this.dispatchEvent(new CustomEvent('countchanged', {
            detail: {
                value: count
            }
        }));
    }

    set _total(total) {
        this.dispatchEvent(new CustomEvent('totalchanged', {
            detail: {
                value: total
            }
        }));
    }

    handleMenuItemSelect(event) {
        if (event.detail.value === 'selectcolumns') {
            const selectColumns = new CustomEvent('selectcolumns', {
                detail: {
                    options: this.allColumns
                        .map(({label, fieldName}) => ({
                            label, value: fieldName
                        })),
                    values: this.columns
                        .map(({fieldName}) => fieldName)
                }
            });
            this.dispatchEvent(selectColumns);
        }
    }

    get qaLocatorTableMenu() {
        return 'button Show menu';
    }

    get qaLocatorSelectBatchTableColumns() {
        return `link ${this.CUSTOM_LABELS.geSelectBatchTableColumns}`;

    }

    /*************************************************************************************
     * @description For each relationship field on an object, this function appends two
     *              properties to the object intended for use with url-type columns in
     *              lightning-datatables. One to be used as the url value and another
     *              to be used as its label.
     * @param objectInfo objectInfo of the record object in context.
     * @param urlSuffix value to be appended to the field name to comprise first new
     *        property name.
     * @param urlLabelSuffix value to be appended to the field name to comprise second new
     *        property name.
     * @returns {object}
     */
    appendUrlColumnProperties(objectInfo, urlSuffix = URL_SUFFIX,
                              urlLabelSuffix = URL_LABEL_SUFFIX) {
        Object.keys(this)
            .filter(key =>
                key.endsWith('__r') || this[key].attributes
            )
            .forEach(key => {
                const referenceObj = this[key];
                const urlFieldName = key.endsWith('__r') ?
                    `${key.replace(/.$/,"c")}${urlSuffix}` :
                    `${key}${urlSuffix}`;
                const urlLabelFieldName = key.endsWith('__r') ?
                    `${key.replace(/.$/,"c")}${urlLabelSuffix}` :
                    `${key}${urlLabelSuffix}`;

                this[urlFieldName] = `/${referenceObj.Id}`;

                if (referenceObj.Name) {
                    this[urlLabelFieldName] = referenceObj.Name;
                } else {
                    try {
                        const field = objectInfo.fields[key];
                        const nameField = field.referenceToInfos[0].nameFields[0];
                        this[urlLabelFieldName] = referenceObj[nameField];
                    } catch (e) {
                        this[urlLabelFieldName] = referenceObj.Id;
                    }
                }
        });
        return this;
    }

    @wire(getRecord, { recordId: '$batchId', fields: [BATCH_CURRENCY_ISO_CODE]})
    batch;

    get batchCurrencyISOCode() {
        return getFieldValue(this.batch.data, BATCH_CURRENCY_ISO_CODE);
    }

    @wire(getRecordCreateDefaults, { objectApiName: DATA_IMPORT_OBJECT })
    dataImportCreateDefaults;

    userDefaultCurrency() {
        return CURRENCY;
    }

    @api
    handleSubmit(event) {
        this.isCurrencyCompatible().then(result => {
           saveAndDryRunDataImport({
               batchId: this.batchId,
               dataImport: event.detail.dataImportRecord
           }).then(result => {
               let dataImportModel = JSON.parse(result);
               let row = dataImportModel.dataImportRows[0];
               Object.assign(row,
                   this.appendUrlColumnProperties.call(row.record,
                       this._dataImportObjectInfo));
               this.upsertData(row, 'Id');
               this._count = dataImportModel.totalCountOfRows;
               this._total = dataImportModel.totalRowAmount;
               event.detail.success(); //Re-enable the Save button
           }).catch(error => {
               event.detail.error(error);
           })
        }).catch(err => {
            event.detail.error(err);
        });
    }

    isCurrencyCompatible = async () => {
        if(this.userDefaultCurrency === this.batchCurrencyISOCode) {
            return true;
        } else {
            throw this.getCurrencyMismatchError();
        }
    }

    getCurrencyMismatchError() {
        return GeLabelService.format(
            this.CUSTOM_LABELS.geErrorBatchGiftEntryCurrencyMismatch,
            [this.batchCurrencyISOCode]);
    }

    _dataImportObjectInfo;
    @wire(getObjectInfo, {objectApiName: DATA_IMPORT_OBJECT})
    wiredDataImportObjectInfo({error, data}) {
        if (data) {
            this._dataImportObjectInfo = data;
            if (!this._propertiesSet) {
                this.setTableProperties();
            }
        }
    }

    getColumnTypeFromFieldType(dataType) {
        return columnTypeByDescribeType[dataType] || dataType.toLowerCase();
    }

    getColumn(element, fieldWrapper) {
        const isReferenceField = element.dataType === REFERENCE &&
            fieldWrapper.Source_Field_API_Name !== DONATION_RECORD_TYPE_NAME.fieldApiName;

        const columnFieldName =
            fieldWrapper.Source_Field_API_Name.toLowerCase().endsWith('id') ?
            fieldWrapper.Source_Field_API_Name.slice(0, -2) :
            fieldWrapper.Source_Field_API_Name;

        let column = {
            label: element.customLabel,
            fieldName: isReferenceField ?
                `${columnFieldName}${URL_SUFFIX}` :
                fieldWrapper.Source_Field_API_Name,
            type: this.getColumnTypeFromFieldType(element.dataType)
        };

        if (column.type === 'currency') {
            column.typeAttributes = {
                currencyCode: this.batchCurrencyISOCode
            };
        }

        if (isReferenceField) {
            column.type = 'url';
            column.target = '_blank';
            column.typeAttributes = {
                label: {
                    fieldName:
                        `${columnFieldName}${URL_LABEL_SUFFIX}`
                }
            };
        }
        return column;
    }

}<commit_msg>Styled imports<commit_after>import { LightningElement, api, wire } from 'lwc';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import {
    deleteRecord,
    getFieldValue,
    getRecord,
    getRecordCreateDefaults
} from 'lightning/uiRecordApi';

import getDataImportModel from '@salesforce/apex/BGE_DataImportBatchEntry_CTRL.getDataImportModel';
import runBatchDryRun from '@salesforce/apex/BGE_DataImportBatchEntry_CTRL.runBatchDryRun';
import getDataImportRows from '@salesforce/apex/BGE_DataImportBatchEntry_CTRL.getDataImportRows';
import saveAndDryRunDataImport from '@salesforce/apex/GE_GiftEntryController.saveAndDryRunDataImport';

import { handleError } from 'c/utilTemplateBuilder';
import { isNotEmpty, isUndefined } from 'c/utilCommon';
import GeFormService from 'c/geFormService';

import geDonorColumnLabel from '@salesforce/label/c.geDonorColumnLabel';
import geDonationColumnLabel from '@salesforce/label/c.geDonationColumnLabel';
import bgeActionDelete from '@salesforce/label/c.bgeActionDelete';
import geBatchGiftsCount from '@salesforce/label/c.geBatchGiftsCount';
import geBatchGiftsTotal from '@salesforce/label/c.geBatchGiftsTotal';

import commonOpen from '@salesforce/label/c.commonOpen';
import GeLabelService from 'c/geLabelService';

import DATA_IMPORT_OBJECT from '@salesforce/schema/DataImport__c';
import STATUS_FIELD from '@salesforce/schema/DataImport__c.Status__c';
import FAILURE_INFORMATION_FIELD from '@salesforce/schema/DataImport__c.FailureInformation__c';
import DONATION_AMOUNT from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DONATION_RECORD_TYPE_NAME
    from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';
import BATCH_CURRENCY_ISO_CODE
    from '@salesforce/schema/DataImportBatch__c.CurrencyIsoCode';
import CURRENCY from '@salesforce/i18n/currency';

const URL_SUFFIX = '_URL';
const URL_LABEL_SUFFIX = '_URL_LABEL';
const REFERENCE = 'REFERENCE';

const columnTypeByDescribeType = {
    'DATE': 'date-local',
    'DATETIME': 'date',
    'EMAIL': 'email',
    'DOUBLE': 'number',
    'INTEGER': 'number',
    'LONG': 'number',
    'PERCENT': 'number',
    'STRING': 'text',
    'PICKLIST': 'text'
};

const COLUMNS = [
    { label: 'Status', fieldName: STATUS_FIELD.fieldApiName, type: 'text' },
    { label: 'Errors', fieldName: FAILURE_INFORMATION_FIELD.fieldApiName, type: 'text' },
    {
        label: geDonorColumnLabel, fieldName: 'donorLink', type: 'url',
        typeAttributes: { label: { fieldName: 'donorName' } }
    },
    {
        label: geDonationColumnLabel, fieldName: 'matchedRecordUrl', type: 'url',
        typeAttributes: { label: { fieldName: 'matchedRecordLabel' } }
    }
];

const ACTIONS_COLUMN = {
    type: 'action',
    typeAttributes: {
        rowActions: [
            { label: commonOpen, name: 'open' },
            { label: bgeActionDelete, name: 'delete' }
        ],
        menuAlignment: 'auto'
    }
};

export default class GeBatchGiftEntryTable extends LightningElement {
    @api batchId;

    get ready() {
        return this._columnsLoaded && this._dataImportModel;
    }

    _batchLoaded = false;
    data = [];
    get hasData() {
        return this.data.length > 0;
    }

    _columnsLoaded = false;
    _columnsBySourceFieldApiName = {};
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    @api title;
    @api total;
    @api expectedTotal;
    @api count;
    @api expectedCount;
    @api userDefinedBatchTableColumnNames;
    isLoaded = true;

    constructor() {
        super();
        COLUMNS.forEach(column => {
            this._columnsBySourceFieldApiName[column.fieldName] = column;
        });
    }

    connectedCallback() {
        this.loadBatch();
    }

    @api
    set sections(sections) {
        this._sections = sections;
        this.buildColumnsFromSections();
    }
    get sections() {
        return this._sections;
    }

    _dataImportModel;
    loadBatch() {
        getDataImportModel({batchId: this.batchId})
            .then(
                response => {
                    this._dataImportModel = JSON.parse(response);
                    this.setTableProperties();
                    this._batchLoaded = true;
                }
            )
            .catch(error => handleError(error));
    }

    _propertiesSet = false;
    setTableProperties() {
        if (this._propertiesSet) {
            return;
        }
        if (!this._dataImportObjectInfo) {
            return;
        }
        if (!this._dataImportModel) {
            return;
        }
        this._count = this._dataImportModel.totalCountOfRows;
        this._total = this._dataImportModel.totalRowAmount;
        this._dataImportModel.dataImportRows.forEach(row => {
            this.data.push(Object.assign(row,
                this.appendUrlColumnProperties.call(row.record,
                    this._dataImportObjectInfo)));
        });
        this.data = this.data.slice(0);
        this._propertiesSet = true;
    }

    get columns() {
        return this.userDefinedBatchTableColumnNames &&
        this.userDefinedBatchTableColumnNames.length > 0 ?
            this.userDefinedColumns.concat(ACTIONS_COLUMN) :
            this.allColumns.concat(ACTIONS_COLUMN);
    }

    get allColumns() {
        return Object.values(this._columnsBySourceFieldApiName);
    }

    get userDefinedColumns() {
        let userDefinedColumns = [];
        this.userDefinedBatchTableColumnNames.forEach(columnName => {
            if (this._columnsBySourceFieldApiName[`${columnName}${URL_SUFFIX}`]) {
                userDefinedColumns.push(this._columnsBySourceFieldApiName[`${columnName}${URL_SUFFIX}`]);
            } else if (isUndefined(this._columnsBySourceFieldApiName[columnName])) {
                return;
            } else {
                userDefinedColumns.push(this._columnsBySourceFieldApiName[columnName]);
            }
        });
        return userDefinedColumns;
    }

    buildColumnsFromSections() {
        this.sections.forEach(section => {
            section.elements.filter(e => e.elementType === 'field')
                .forEach(fieldElement => {
                    const fieldWrapper =
                        GeFormService.getFieldMappingWrapper(
                            fieldElement.dataImportFieldMappingDevNames[0]);

                    if (isNotEmpty(fieldWrapper)) {
                        const column = this.getColumn(fieldElement, fieldWrapper);
                        this._columnsBySourceFieldApiName[column.fieldName] = column;
                    }
                });
        });
        this._columnsLoaded = true;
    }

    @api
    upsertData(dataRow, idProperty) {
        const existingRowIndex = this.data.findIndex(row =>
            row[idProperty] === dataRow[idProperty]
        );

        if (existingRowIndex !== -1) {
            this.data.splice(existingRowIndex, 1, dataRow);
            this.data = this.data.splice(0);
        } else {
            this.data = [dataRow].concat(this.data);
        }
    }

    handleRowActions(event) {
        switch (event.detail.action.name) {
            case 'open':
                this.loadRow(event.detail.row);
                break;
            case 'delete':
                deleteRecord(event.detail.row.Id).then(() => {
                    this.deleteDIRow(event.detail.row);
                }).catch(error => {
                    handleError(error);
                });
                break;
        }
    }

    deleteDIRow(rowToDelete) {
        const isRowToDelete = row => row.Id === rowToDelete.Id;
        const index = this.data.findIndex(isRowToDelete);
        this.data.splice(index, 1);
        this.data = this.data.splice(0);
        this.dispatchEvent(new CustomEvent('delete', {
            detail: {
                amount: rowToDelete[DONATION_AMOUNT.fieldApiName]
            }
        }));
    }

    loadMoreData(event) {
        event.target.isLoading = true;
        const disableInfiniteLoading = function () {
            this.enableInfiniteLoading = false;
        }.bind(event.target);

        const disableIsLoading = function () {
            this.isLoading = false;
        }.bind(event.target);

        getDataImportRows({ batchId: this.batchId, offset: this.data.length })
            .then(rows => {
                rows.forEach(row => {
                    this.data.push(
                        Object.assign(row,
                            this.appendUrlColumnProperties.call(row.record,
                                this._dataImportObjectInfo)));
                });
                this.data = this.data.splice(0);
                if (this.data.length >= this.count) {
                    disableInfiniteLoading();
                }
                disableIsLoading();
            })
            .catch(error => {
                handleError(error);
            });
    }

    @api
    runBatchDryRun(callback) {
        runBatchDryRun({
            batchId: this.batchId,
            numberOfRowsToReturn: this.data.length
        })
            .then(result => {
                const dataImportModel = JSON.parse(result);
                this._count = dataImportModel.totalCountOfRows;
                this._total = dataImportModel.totalRowAmount;
                dataImportModel.dataImportRows.forEach(row => {
                    this.upsertData(
                        Object.assign(row,
                            this.appendUrlColumnProperties.call(row.record,
                                this._dataImportObjectInfo)), 'Id');
                });
            })
            .catch(error => {
                handleError(error);
            })
            .finally(() => {
                callback();
            });
    }

    get geBatchGiftsCountLabel() {
        return geBatchGiftsCount;
    }

    get geBatchGiftsTotalLabel() {
        return geBatchGiftsTotal;
    }

    loadRow(row) {
        this.dispatchEvent(new CustomEvent('loaddata', {
            detail: row
        }));
    }

    /*************************************************************************************
     * @description Internal setters used to communicate the current count and total
     *              up to the App, which needs them to keep track of whether the batch's
     *              expected totals match.
     */
    set _count(count) {
        this.dispatchEvent(new CustomEvent('countchanged', {
            detail: {
                value: count
            }
        }));
    }

    set _total(total) {
        this.dispatchEvent(new CustomEvent('totalchanged', {
            detail: {
                value: total
            }
        }));
    }

    handleMenuItemSelect(event) {
        if (event.detail.value === 'selectcolumns') {
            const selectColumns = new CustomEvent('selectcolumns', {
                detail: {
                    options: this.allColumns
                        .map(({label, fieldName}) => ({
                            label, value: fieldName
                        })),
                    values: this.columns
                        .map(({fieldName}) => fieldName)
                }
            });
            this.dispatchEvent(selectColumns);
        }
    }

    get qaLocatorTableMenu() {
        return 'button Show menu';
    }

    get qaLocatorSelectBatchTableColumns() {
        return `link ${this.CUSTOM_LABELS.geSelectBatchTableColumns}`;

    }

    /*************************************************************************************
     * @description For each relationship field on an object, this function appends two
     *              properties to the object intended for use with url-type columns in
     *              lightning-datatables. One to be used as the url value and another
     *              to be used as its label.
     * @param objectInfo objectInfo of the record object in context.
     * @param urlSuffix value to be appended to the field name to comprise first new
     *        property name.
     * @param urlLabelSuffix value to be appended to the field name to comprise second new
     *        property name.
     * @returns {object}
     */
    appendUrlColumnProperties(objectInfo, urlSuffix = URL_SUFFIX,
                              urlLabelSuffix = URL_LABEL_SUFFIX) {
        Object.keys(this)
            .filter(key =>
                key.endsWith('__r') || this[key].attributes
            )
            .forEach(key => {
                const referenceObj = this[key];
                const urlFieldName = key.endsWith('__r') ?
                    `${key.replace(/.$/,"c")}${urlSuffix}` :
                    `${key}${urlSuffix}`;
                const urlLabelFieldName = key.endsWith('__r') ?
                    `${key.replace(/.$/,"c")}${urlLabelSuffix}` :
                    `${key}${urlLabelSuffix}`;

                this[urlFieldName] = `/${referenceObj.Id}`;

                if (referenceObj.Name) {
                    this[urlLabelFieldName] = referenceObj.Name;
                } else {
                    try {
                        const field = objectInfo.fields[key];
                        const nameField = field.referenceToInfos[0].nameFields[0];
                        this[urlLabelFieldName] = referenceObj[nameField];
                    } catch (e) {
                        this[urlLabelFieldName] = referenceObj.Id;
                    }
                }
        });
        return this;
    }

    @wire(getRecord, { recordId: '$batchId', fields: [BATCH_CURRENCY_ISO_CODE]})
    batch;

    get batchCurrencyISOCode() {
        return getFieldValue(this.batch.data, BATCH_CURRENCY_ISO_CODE);
    }

    @wire(getRecordCreateDefaults, { objectApiName: DATA_IMPORT_OBJECT })
    dataImportCreateDefaults;

    userDefaultCurrency() {
        return CURRENCY;
    }

    @api
    handleSubmit(event) {
        this.isCurrencyCompatible().then(result => {
           saveAndDryRunDataImport({
               batchId: this.batchId,
               dataImport: event.detail.dataImportRecord
           }).then(result => {
               let dataImportModel = JSON.parse(result);
               let row = dataImportModel.dataImportRows[0];
               Object.assign(row,
                   this.appendUrlColumnProperties.call(row.record,
                       this._dataImportObjectInfo));
               this.upsertData(row, 'Id');
               this._count = dataImportModel.totalCountOfRows;
               this._total = dataImportModel.totalRowAmount;
               event.detail.success(); //Re-enable the Save button
           }).catch(error => {
               event.detail.error(error);
           })
        }).catch(err => {
            event.detail.error(err);
        });
    }

    isCurrencyCompatible = async () => {
        if(this.userDefaultCurrency === this.batchCurrencyISOCode) {
            return true;
        } else {
            throw this.getCurrencyMismatchError();
        }
    }

    getCurrencyMismatchError() {
        return GeLabelService.format(
            this.CUSTOM_LABELS.geErrorBatchGiftEntryCurrencyMismatch,
            [this.batchCurrencyISOCode]);
    }

    _dataImportObjectInfo;
    @wire(getObjectInfo, {objectApiName: DATA_IMPORT_OBJECT})
    wiredDataImportObjectInfo({error, data}) {
        if (data) {
            this._dataImportObjectInfo = data;
            if (!this._propertiesSet) {
                this.setTableProperties();
            }
        }
    }

    getColumnTypeFromFieldType(dataType) {
        return columnTypeByDescribeType[dataType] || dataType.toLowerCase();
    }

    getColumn(element, fieldWrapper) {
        const isReferenceField = element.dataType === REFERENCE &&
            fieldWrapper.Source_Field_API_Name !== DONATION_RECORD_TYPE_NAME.fieldApiName;

        const columnFieldName =
            fieldWrapper.Source_Field_API_Name.toLowerCase().endsWith('id') ?
            fieldWrapper.Source_Field_API_Name.slice(0, -2) :
            fieldWrapper.Source_Field_API_Name;

        let column = {
            label: element.customLabel,
            fieldName: isReferenceField ?
                `${columnFieldName}${URL_SUFFIX}` :
                fieldWrapper.Source_Field_API_Name,
            type: this.getColumnTypeFromFieldType(element.dataType)
        };

        if (column.type === 'currency') {
            column.typeAttributes = {
                currencyCode: this.batchCurrencyISOCode
            };
        }

        if (isReferenceField) {
            column.type = 'url';
            column.target = '_blank';
            column.typeAttributes = {
                label: {
                    fieldName:
                        `${columnFieldName}${URL_LABEL_SUFFIX}`
                }
            };
        }
        return column;
    }

}<|endoftext|>


<commit_before>/**
 * New App Generator
 * -----------------
 * Generates skeleton files needed to create a new SocketStream application
 */

"use strict";

require('colors');

var fs       = require('fs'),
    path     = require('path'),
    util     = require('util'),
    log      = console.log,
    dir_mode = '0755',

    // Private
    success = function(name, alternative) {
      return log(" ✓".green, name, (alternative || '').grey);
    },

    // TODO - investigate whether this would be better
    // using the async version of fs.mkdir - PJENSEN
    makeRootDirectory = function(name) {
      try {
        fs.mkdirSync(name, dir_mode); // Create the root directory
        return true;
      } catch (e) {
        if (e.code === 'EEXIST') {
          log("Sorry the '" + name + "' directory already exists. Please choose another name for your app.");
          return false;
        } else {
          throw e;
        }
      }
  };

exports.generate = function(program) {

  var appjs,
      codeExtension = program.coffee && 'coffee' || 'js',
      selectedFormatters = [],
      name               = program.args[1],
      source             = path.join(process.env.PWD, 'new_project'),
      viewExtension      = program.jade && 'jade' || 'html',
      mods               = [],
      packagejs          = '',
      clientFiles        = {
        css: [],
        code: []
      },

      /**
       * [mkdir description]
       * @param  {String}   dir Directory path to create
       * @return {Boolean}      True if directory successfully created
       */
      mkdir = function(dir) {
        return fs.mkdirSync(path.join(name, dir), dir_mode);
      },

      /**
       * Copy files/directories
       * @param  {[String} src  Copy From directory
       * @param  {[String} dest Copy To directory
       * @return {Object}       Stream object
       */
      cp = function(src, dest) {
        var destination = path.join(name, dest || src),
            read        = fs.createReadStream(path.join(source, src)),
            write       = fs.createWriteStream(destination);

        return util.pump(read, write);
      },

        /**
         * Wrights contend to the certain file
         * @param  {String} fileName File to wright
         * @param  {[type]} content  Content string
         * @return {Boolean}
         */
        write = function(fileName, content) {
          return fs.writeFileSync(path.join(name, fileName), content, 'utf8');
        };

  /* Check for appleacation name existing */
  if (name === void 0) {
    return log("Please provide a name for your application: $> socketstream new <MyAppName>");
  }

  if (makeRootDirectory(name)) {

    /* Force stylus for now */
    program.stylus = true;

    ['coffee', 'jade', 'less', 'stylus'].forEach(function(formatter) {
      if (program[formatter]) {
        return selectedFormatters.push(formatter);
      }
    });

    /* Create essential directories */
    mkdir('/client');
    mkdir('/client/code');
    mkdir('/client/code/app');
    mkdir('/client/code/libs');
    mkdir('/client/views');
    mkdir('/client/css');
    mkdir('/client/css/libs');
    mkdir('/client/templates');
    mkdir('/client/static');
    mkdir('/client/static/images');
    mkdir('/server');
    mkdir('/server/rpc');
    mkdir('/server/middleware');

    /* Copy essential files */
    cp('/.gitignore');
    cp('/.nodemonignore');
    cp('/README.md');
    cp('/client/static/favicon.ico');
    cp('/client/code/libs/jquery.min.js');
    cp("/client/code/app/entry." + codeExtension);
    clientFiles.code.push('libs/jquery.min.js');
    clientFiles.code.push('app');

    /* Install chat demo files */
    if (program.minimal) {
      cp("/client/views/app.minimal." + viewExtension, "/client/views/app." + viewExtension);
      cp("/client/code/app/app.minimal." + codeExtension, "/client/code/app/app." + codeExtension);
      cp("/client/css/app.minimal.styl", "/client/css/app.styl");
      cp("/server/rpc/.gitkeep");
      cp("/server/middleware/.gitkeep");
      cp("/client/templates/.gitkeep");
      clientFiles.css.push('app.styl');
    } else {
      cp('/client/static/images/logo.png');
      cp("/client/code/app/app.demo." + codeExtension, "/client/code/app/app." + codeExtension);
      cp("/server/middleware/example." + codeExtension);
      cp("/server/rpc/demo." + codeExtension);
      cp('/client/css/libs/reset.css');
      cp("/client/css/app.demo.styl", "/client/css/app.styl");
      mkdir('/client/templates/chat');
      cp("/client/templates/chat/message." + viewExtension);
      cp("/client/views/app.demo." + viewExtension, "/client/views/app." + viewExtension);
      clientFiles.css.push('libs/reset.css');
      clientFiles.css.push('app.styl');
    }

    /* Generate app.js */
    appjs = "// My SocketStream 0.3 app\n\nvar http = require('http'),\n    ss = require('socketstream');\n\n// Define a single-page client called 'main'\nss.client.define('main', {\n  view: 'app." + viewExtension + "',\n  css:  ['" + (clientFiles.css.join("', '")) + "'],\n  code: ['" + (clientFiles.code.join("', '")) + "'],\n  tmpl: '*'\n});\n\n// Serve this client on the root URL\nss.http.route('/', function(req, res){\n  res.serveClient('main');\n});\n";

    /* List any selected formatters */
    if (selectedFormatters.length > 0) {
      appjs += "\n// Code Formatters\n";
    }
    selectedFormatters.forEach(function(name) {
      return appjs += "ss.client.formatters.add(require('ss-" + name + "'));\n";
    });
    appjs += "\n// Use server-side compiled Hogan (Mustache) templates. Others engines available\nss.client.templateEngine.use(require('ss-hogan'));\n\n// Minimize and pack assets if you type: SS_ENV=production node app.js\nif (ss.env === 'production') ss.client.packAssets();\n\n// Start web server\nvar server = http.Server(ss.http.middleware);\nserver.listen(3000);\n";
    if (program.repl) {
      appjs += "\n// Start Console Server (REPL)\n// To install client: sudo npm install -g ss-console\n// To connect: ss-console <optional_host_or_port>\nvar consoleServer = require('ss-console')(ss);\nconsoleServer.listen(5000);\n";
    }
    appjs += "\n// Start SocketStream\nss.start(server);";
    write('/app.js', appjs);

    /* Generate package.json */
    packagejs = "{\n  \"name\": \"" + name + "\",\n  \"description\": \"An awesome real time application\",\n  \"version\": \"0.0.1\",\n  \"author\": \"Me <me@mydomain.com>\",\n  \"private\": true,\n  \"engines\": { \"node\": \">= 0.6.0\" },\n  \"dependencies\": {\n";
    packagejs += "    \"socketstream\": \"0.3.x\"";
    mods = selectedFormatters.concat(['hogan']);
    if (program.repl) {
      mods.push('console');
    }
    mods.forEach(function(name) {
      return packagejs += ",\n    \"ss-" + name + "\": \"0.1.x\"";
    });

    packagejs += "\n  },\n  \"scripts\":{\n    \"start\":\"node app.js\"\n  }\n}";
    write('/package.json', packagejs);

    /* Show finish text */
    log(("Success! Created app '" + name + "' with:").yellow);

    if (program.minimal) {
      success("Minimal install (no demo)");
    } else {
      success("Basic chat demo", "(-m for minimal install)");
    }
    if (program.coffee) {
      success("CoffeeScript example code");
    } else {
      success("Javascript example code", "(-c if you prefer CoffeeScript)");
    }
    if (program.jade) {
      success("Jade for views");
    } else {
      success("Plain HTML for views", "(-j if you prefer Jade)");
    }

    /**
     * TODO
     *
     * To be implemented in the future. Contributions welcome
     *
     * if program.stylus
     *   success("Stylus for CSS")
     * else if program.less
     *   success("Less for CSS")
     * else
     *   success("Plain CSS", "(-s if you prefer Stylus, -l for Less)")
     */

    /* Add the REPL if selected */
    if (program.repl) {
      success("Console Server / REPL");
    }

    log("Next, run the following commands:".yellow);
    log("   cd " + name);
    log("   [sudo] npm install");
    log("To start your app:".yellow);
    log("   node app.js");
  }
};
<commit_msg>Fixed indent<commit_after>/**
 * New App Generator
 * -----------------
 * Generates skeleton files needed to create a new SocketStream application
 */

"use strict";

require('colors');

var fs       = require('fs'),
    path     = require('path'),
    util     = require('util'),
    log      = console.log,
    dir_mode = '0755',

    // Private
    success = function(name, alternative) {
      return log(" ✓".green, name, (alternative || '').grey);
    },

    // TODO - investigate whether this would be better
    // using the async version of fs.mkdir - PJENSEN
    makeRootDirectory = function(name) {
      try {
        fs.mkdirSync(name, dir_mode); // Create the root directory
        return true;
      } catch (e) {
        if (e.code === 'EEXIST') {
          log("Sorry the '" + name + "' directory already exists. Please choose another name for your app.");
          return false;
        } else {
          throw e;
        }
      }
  };

exports.generate = function(program) {

  var appjs,
      codeExtension = program.coffee && 'coffee' || 'js',
      selectedFormatters = [],
      name               = program.args[1],
      source             = path.join(process.env.PWD, 'new_project'),
      viewExtension      = program.jade && 'jade' || 'html',
      mods               = [],
      packagejs          = '',
      clientFiles        = {
        css: [],
        code: []
      },

      /**
       * [mkdir description]
       * @param  {String}   dir Directory path to create
       * @return {Boolean}      True if directory successfully created
       */
      mkdir = function(dir) {
        return fs.mkdirSync(path.join(name, dir), dir_mode);
      },

      /**
       * Copy files/directories
       * @param  {[String} src  Copy From directory
       * @param  {[String} dest Copy To directory
       * @return {Object}       Stream object
       */
      cp = function(src, dest) {
        var destination = path.join(name, dest || src),
            read        = fs.createReadStream(path.join(source, src)),
            write       = fs.createWriteStream(destination);

        return util.pump(read, write);
      },

      /**
       * Wrights contend to the certain file
       * @param  {String} fileName File to wright
       * @param  {[type]} content  Content string
       * @return {Boolean}
       */
      write = function(fileName, content) {
        return fs.writeFileSync(path.join(name, fileName), content, 'utf8');
      };

  /* Check for appleacation name existing */
  if (name === void 0) {
    return log("Please provide a name for your application: $> socketstream new <MyAppName>");
  }

  if (makeRootDirectory(name)) {

    /* Force stylus for now */
    program.stylus = true;

    ['coffee', 'jade', 'less', 'stylus'].forEach(function(formatter) {
      if (program[formatter]) {
        return selectedFormatters.push(formatter);
      }
    });

    /* Create essential directories */
    mkdir('/client');
    mkdir('/client/code');
    mkdir('/client/code/app');
    mkdir('/client/code/libs');
    mkdir('/client/views');
    mkdir('/client/css');
    mkdir('/client/css/libs');
    mkdir('/client/templates');
    mkdir('/client/static');
    mkdir('/client/static/images');
    mkdir('/server');
    mkdir('/server/rpc');
    mkdir('/server/middleware');

    /* Copy essential files */
    cp('/.gitignore');
    cp('/.nodemonignore');
    cp('/README.md');
    cp('/client/static/favicon.ico');
    cp('/client/code/libs/jquery.min.js');
    cp("/client/code/app/entry." + codeExtension);
    clientFiles.code.push('libs/jquery.min.js');
    clientFiles.code.push('app');

    /* Install chat demo files */
    if (program.minimal) {
      cp("/client/views/app.minimal." + viewExtension, "/client/views/app." + viewExtension);
      cp("/client/code/app/app.minimal." + codeExtension, "/client/code/app/app." + codeExtension);
      cp("/client/css/app.minimal.styl", "/client/css/app.styl");
      cp("/server/rpc/.gitkeep");
      cp("/server/middleware/.gitkeep");
      cp("/client/templates/.gitkeep");
      clientFiles.css.push('app.styl');
    } else {
      cp('/client/static/images/logo.png');
      cp("/client/code/app/app.demo." + codeExtension, "/client/code/app/app." + codeExtension);
      cp("/server/middleware/example." + codeExtension);
      cp("/server/rpc/demo." + codeExtension);
      cp('/client/css/libs/reset.css');
      cp("/client/css/app.demo.styl", "/client/css/app.styl");
      mkdir('/client/templates/chat');
      cp("/client/templates/chat/message." + viewExtension);
      cp("/client/views/app.demo." + viewExtension, "/client/views/app." + viewExtension);
      clientFiles.css.push('libs/reset.css');
      clientFiles.css.push('app.styl');
    }

    /* Generate app.js */
    appjs = "// My SocketStream 0.3 app\n\nvar http = require('http'),\n    ss = require('socketstream');\n\n// Define a single-page client called 'main'\nss.client.define('main', {\n  view: 'app." + viewExtension + "',\n  css:  ['" + (clientFiles.css.join("', '")) + "'],\n  code: ['" + (clientFiles.code.join("', '")) + "'],\n  tmpl: '*'\n});\n\n// Serve this client on the root URL\nss.http.route('/', function(req, res){\n  res.serveClient('main');\n});\n";

    /* List any selected formatters */
    if (selectedFormatters.length > 0) {
      appjs += "\n// Code Formatters\n";
    }
    selectedFormatters.forEach(function(name) {
      return appjs += "ss.client.formatters.add(require('ss-" + name + "'));\n";
    });
    appjs += "\n// Use server-side compiled Hogan (Mustache) templates. Others engines available\nss.client.templateEngine.use(require('ss-hogan'));\n\n// Minimize and pack assets if you type: SS_ENV=production node app.js\nif (ss.env === 'production') ss.client.packAssets();\n\n// Start web server\nvar server = http.Server(ss.http.middleware);\nserver.listen(3000);\n";
    if (program.repl) {
      appjs += "\n// Start Console Server (REPL)\n// To install client: sudo npm install -g ss-console\n// To connect: ss-console <optional_host_or_port>\nvar consoleServer = require('ss-console')(ss);\nconsoleServer.listen(5000);\n";
    }
    appjs += "\n// Start SocketStream\nss.start(server);";
    write('/app.js', appjs);

    /* Generate package.json */
    packagejs = "{\n  \"name\": \"" + name + "\",\n  \"description\": \"An awesome real time application\",\n  \"version\": \"0.0.1\",\n  \"author\": \"Me <me@mydomain.com>\",\n  \"private\": true,\n  \"engines\": { \"node\": \">= 0.6.0\" },\n  \"dependencies\": {\n";
    packagejs += "    \"socketstream\": \"0.3.x\"";
    mods = selectedFormatters.concat(['hogan']);
    if (program.repl) {
      mods.push('console');
    }
    mods.forEach(function(name) {
      return packagejs += ",\n    \"ss-" + name + "\": \"0.1.x\"";
    });

    packagejs += "\n  },\n  \"scripts\":{\n    \"start\":\"node app.js\"\n  }\n}";
    write('/package.json', packagejs);

    /* Show finish text */
    log(("Success! Created app '" + name + "' with:").yellow);

    if (program.minimal) {
      success("Minimal install (no demo)");
    } else {
      success("Basic chat demo", "(-m for minimal install)");
    }
    if (program.coffee) {
      success("CoffeeScript example code");
    } else {
      success("Javascript example code", "(-c if you prefer CoffeeScript)");
    }
    if (program.jade) {
      success("Jade for views");
    } else {
      success("Plain HTML for views", "(-j if you prefer Jade)");
    }

    /**
     * TODO
     *
     * To be implemented in the future. Contributions welcome
     *
     * if program.stylus
     *   success("Stylus for CSS")
     * else if program.less
     *   success("Less for CSS")
     * else
     *   success("Plain CSS", "(-s if you prefer Stylus, -l for Less)")
     */

    /* Add the REPL if selected */
    if (program.repl) {
      success("Console Server / REPL");
    }

    log("Next, run the following commands:".yellow);
    log("   cd " + name);
    log("   [sudo] npm install");
    log("To start your app:".yellow);
    log("   node app.js");
  }
};
<|endoftext|>


<commit_before>var async = require('async'),
  extend = require('../extend'),
  generator = extend.generator.list(),
  processor = extend.processor.list(),
  helper = extend.helper.list(),
  render = require('../render'),
  renderSync = render.renderSync,
  Collection = require('../model').Collection,
  util = require('../util'),
  file = util.file,
  yfm = util.yfm,
  _ = require('underscore'),
  path = require('path'),
  fs = require('fs'),
  config = hexo.config,
  baseDir = hexo.base_dir,
  themeDir = hexo.theme_dir,
  publicDir = hexo.public_dir,
  i18n = hexo.i18n,
  layoutCache = {};

var themeRender = function(template, locals){
  if (!layoutCache[template]) return '';

  var layout = layoutCache[template],
    source = layout.source,
    extname = path.extname(source).substring(1),
    newHelper = _.clone(helper);

  _.each(newHelper, function(val, key){
    newHelper[key] = val(source, layout.content, locals);
  });

  var newLocals = _.extend(locals, newHelper);

  if (layout.layout){
    var content = themeRender(layout.layout, _.extend(locals, {body: layout._content}));
  } else {
    var content = layout._content;
  }

  var result = renderSync(content, extname, newLocals);

  return result;
};

extend.console.register('generate', 'Generate static files', function(args){
  var renderer = Object.keys(extend.renderer.list()),
    start = new Date(),
    ignoreTheme = false,
    publicExist = false;

  if (_.indexOf(args, '-t') !== -1 || _.indexOf(args, '--theme') !== -1) ignoreTheme = true;

  async.auto({
    // Load theme config
    load: function(next){
      render.compile(themeDir + '_config.yml', next);
    },
    // Check if public folder exists
    check: function(next){
      fs.exists(publicDir, function(exist){
        if (exist) publicExist = exist;
        next();
      });
    },
    // Load cache
    cache: ['check', function(next){
      if (ignoreTheme && publicExist){
        var cachePath = baseDir + '.cache';

        fs.exists(cachePath, function(exist){
          if (exist){
            file.read(cachePath, function(err, content){
              next(err, JSON.parse(content).theme);
            });
          } else {
            ignoreTheme = false;
            next();
          }
        });
      } else {
        next();
      }
    }],
    // Load theme layout
    theme_layout: function(next){
      var layoutDir = themeDir + 'layout/';

      file.dir(layoutDir, function(files){
        async.forEach(files, function(item, next){
          var extname = path.extname(item),
            dirs = item.split(path.sep);

          for (var i=0, len=dirs.length; i<len; i++){
            var front = dirs[i].substr(0, 1);
            if (front === '_' || front === '.') return next();
          }

          file.read(layoutDir + item, function(err, content){
            if (err) throw err;

            var name = item.substring(0, item.length - extname.length);
            layoutCache[name] = yfm(content);
            layoutCache[name].source = layoutDir + item;
            next();
          });
        }, next);
      });
    },
    // Install theme assets
    theme_source: ['check', 'cache', function(next){
      if (ignoreTheme && publicExist) return next();

      console.log('Installing theme.');

      var sourceDir = themeDir + 'source/',
        list = [];

      file.dir(sourceDir, function(files){
        async.forEach(files, function(item, next){
          var extname = path.extname(item),
            dirs = item.split(path.sep);

          for (var i=0, len=dirs.length; i<len; i++){
            var front = dirs[i].substr(0, 1);
            if (front === '_' || front === '.') return next();
          }

          if (_.indexOf(renderer, extname.substring(1)) !== -1){
            render.compile(sourceDir + item, function(err, result, output){
              if (err) throw err;
              var dest = item.substring(0, item.length - extname.length) + '.' + output;
              list.push(dest);
              file.write(publicDir + dest, result, next);
            });
          } else {
            list.push(item);
            file.copy(sourceDir + item, publicDir + item, next);
          }
        }, function(err){
          next(err, list);
        });
      });
    }],
    // Load theme i18n
    theme_i18n: function(next){
      var target = themeDir + 'languages';

      fs.exists(target, function(exist){
        if (exist){
          var theme_i18n = new i18n();
          theme_i18n.load(target, function(){
            next(null, theme_i18n.get);
          });
        } else {
          next();
        }
      });
    },
    store: ['theme_source', function(next, results){
      var cacheData = results.theme_source;
      if (cacheData){
        file.write(baseDir + '.cache', JSON.stringify({theme: cacheData}), next);
      } else {
        next();
      }
    }],
    clear: ['theme_source', function(next, results){
      if (publicExist){
        console.log('Clearing.');
        file.empty(publicDir, results.theme_source, next);
      } else {
        next();
      }
    }],
    process: ['clear', function(next){
      var site = {
        posts: new Collection(),
        pages: new Collection()
      };

      async.forEachSeries(processor, function(item, next){
        item(site, function(err, locals){
          if (err) throw err;
          if (locals) site = locals;
          next();
        });
      }, function(){
        next(null, site);
      });
    }]
  }, function(err, results){
    if (err) throw err;

    var site = results.process;
    Object.freeze(site);

    async.forEach(generator, function(item, next){
      item(site, function(layout, locals){
        var newLocals = {
          page: locals,
          site: site,
          config: config,
          theme: results.load[0],
          __: results.theme_i18n
        };

        return themeRender(layout, newLocals);
      }, next);
    }, function(){
      var finish = new Date(),
        elapsed = (finish.getTime() - start.getTime()) / 1000;
      console.log('Site generated in %ss.', elapsed.toFixed(3));
    });
  });
});<commit_msg>Fixed ignoring theme installation bug<commit_after>var async = require('async'),
  extend = require('../extend'),
  generator = extend.generator.list(),
  processor = extend.processor.list(),
  helper = extend.helper.list(),
  render = require('../render'),
  renderSync = render.renderSync,
  Collection = require('../model').Collection,
  util = require('../util'),
  file = util.file,
  yfm = util.yfm,
  _ = require('underscore'),
  path = require('path'),
  fs = require('fs'),
  config = hexo.config,
  baseDir = hexo.base_dir,
  themeDir = hexo.theme_dir,
  publicDir = hexo.public_dir,
  i18n = hexo.i18n,
  layoutCache = {};

var themeRender = function(template, locals){
  if (!layoutCache[template]) return '';

  var layout = layoutCache[template],
    source = layout.source,
    extname = path.extname(source).substring(1),
    newHelper = _.clone(helper);

  _.each(newHelper, function(val, key){
    newHelper[key] = val(source, layout.content, locals);
  });

  var newLocals = _.extend(locals, newHelper);

  if (layout.layout){
    var content = themeRender(layout.layout, _.extend(locals, {body: layout._content}));
  } else {
    var content = layout._content;
  }

  var result = renderSync(content, extname, newLocals);

  return result;
};

extend.console.register('generate', 'Generate static files', function(args){
  var renderer = Object.keys(extend.renderer.list()),
    start = new Date(),
    ignoreTheme = false,
    publicExist = false;

  if (_.indexOf(args, '-t') !== -1 || _.indexOf(args, '--theme') !== -1) ignoreTheme = true;

  async.auto({
    // Load theme config
    load: function(next){
      render.compile(themeDir + '_config.yml', next);
    },
    // Check if public folder exists
    check: function(next){
      fs.exists(publicDir, function(exist){
        if (exist) publicExist = exist;
        next();
      });
    },
    // Load cache
    cache: ['check', function(next){
      if (ignoreTheme && publicExist){
        var cachePath = baseDir + '.cache';

        fs.exists(cachePath, function(exist){
          if (exist){
            file.read(cachePath, function(err, content){
              next(err, JSON.parse(content).theme);
            });
          } else {
            ignoreTheme = false;
            next();
          }
        });
      } else {
        next();
      }
    }],
    // Load theme layout
    theme_layout: function(next){
      var layoutDir = themeDir + 'layout/';

      file.dir(layoutDir, function(files){
        async.forEach(files, function(item, next){
          var extname = path.extname(item),
            dirs = item.split(path.sep);

          for (var i=0, len=dirs.length; i<len; i++){
            var front = dirs[i].substr(0, 1);
            if (front === '_' || front === '.') return next();
          }

          file.read(layoutDir + item, function(err, content){
            if (err) throw err;

            var name = item.substring(0, item.length - extname.length);
            layoutCache[name] = yfm(content);
            layoutCache[name].source = layoutDir + item;
            next();
          });
        }, next);
      });
    },
    // Install theme assets
    theme_source: ['check', 'cache', function(next, results){
      if (ignoreTheme && publicExist) return next(null, results.cache);

      console.log('Installing theme.');

      var sourceDir = themeDir + 'source/',
        list = [];

      file.dir(sourceDir, function(files){
        async.forEach(files, function(item, next){
          var extname = path.extname(item),
            dirs = item.split(path.sep);

          for (var i=0, len=dirs.length; i<len; i++){
            var front = dirs[i].substr(0, 1);
            if (front === '_' || front === '.') return next();
          }

          if (_.indexOf(renderer, extname.substring(1)) !== -1){
            render.compile(sourceDir + item, function(err, result, output){
              if (err) throw err;
              var dest = item.substring(0, item.length - extname.length) + '.' + output;
              list.push(dest);
              file.write(publicDir + dest, result, next);
            });
          } else {
            list.push(item);
            file.copy(sourceDir + item, publicDir + item, next);
          }
        }, function(err){
          next(err, list);
        });
      });
    }],
    // Load theme i18n
    theme_i18n: function(next){
      var target = themeDir + 'languages';

      fs.exists(target, function(exist){
        if (exist){
          var theme_i18n = new i18n();
          theme_i18n.load(target, function(){
            next(null, theme_i18n.get);
          });
        } else {
          next();
        }
      });
    },
    store: ['theme_source', function(next, results){
      var cacheData = results.theme_source;
      if (cacheData){
        file.write(baseDir + '.cache', JSON.stringify({theme: cacheData}), next);
      } else {
        next();
      }
    }],
    clear: ['theme_source', function(next, results){
      if (publicExist){
        console.log('Clearing.');
        file.empty(publicDir, results.theme_source, next);
      } else {
        next();
      }
    }],
    process: ['clear', function(next){
      var site = {
        posts: new Collection(),
        pages: new Collection()
      };

      async.forEachSeries(processor, function(item, next){
        item(site, function(err, locals){
          if (err) throw err;
          if (locals) site = locals;
          next();
        });
      }, function(){
        next(null, site);
      });
    }]
  }, function(err, results){
    if (err) throw err;

    var site = results.process;
    Object.freeze(site);

    async.forEach(generator, function(item, next){
      item(site, function(layout, locals){
        var newLocals = {
          page: locals,
          site: site,
          config: config,
          theme: results.load[0],
          __: results.theme_i18n
        };

        return themeRender(layout, newLocals);
      }, next);
    }, function(){
      var finish = new Date(),
        elapsed = (finish.getTime() - start.getTime()) / 1000;
      console.log('Site generated in %ss.', elapsed.toFixed(3));
    });
  });
});<|endoftext|>


<commit_before>'use strict'

const Logger = require('../util/logger')

class Orphaned {
  constructor (config, jirascope) {
    this.config = config
    this.jirascope = jirascope
  }

  async run () {
    await this.jirascope.populate()
    let count = 0
    Object.values(this.jirascope.issues).forEach((issue) => {
      if (issue.statusSummary === 'Done') {
        return
      }
      if (issue.links.length === 0) {
        console.log(`${issue.key}, ${issue.status}, ${issue.summary}`)
        count++
      }
    })
    Logger.global.success(`found ${count} orphaned issues`)
  }
}

exports = module.exports = Orphaned
<commit_msg>collect orphans<commit_after>'use strict'

const Logger = require('../util/logger')

class Orphaned {
  constructor (config, jirascope) {
    this.config = config
    this.jirascope = jirascope
  }

  async run () {
    await this.jirascope.populate()
    let orphans = []
    Object.values(this.jirascope.issues).forEach((issue) => {
      if (issue.statusSummary === 'Done') {
        return
      }
      if (issue.links.length === 0) {
        orphans.push(issue)
        console.log(`${issue.key}, ${issue.status}, ${issue.summary}`)
      }
    })
    Logger.global.success(`found ${orphans.length} orphaned issues`)
  }
}

exports = module.exports = Orphaned
<|endoftext|>


<commit_before>/*
 * trend.js
 *
 * Copyright (c) 2015 James G Jenner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Logic is based on Timothy Banas article on calculating trend line
 * http://classroom.synonym.com/calculate-trendline-2709.html
 *
 * Some redundancy is kept so that code is comparable to reference material
 */

function calcTrendLine(xSeries, ySeries) {
  if(xSeries.length < ySeries.length) {
    return 0;
  }

  var n = ySeries.length;
  var idx;
  var sumPlus = 0;
  var sumSquaredX = 0;
  var sumX = 0;
  var sumY = 0;

  for(idx = 0; idx < ySeries.length; idx++) {
    sumPlus += xSeries[idx] * ySeries[idx];
    sumX += xSeries[idx];
    sumY += ySeries[idx];
    sumSquaredX += Math.pow(xSeries[idx], 2);
  }

  var a = n * sumPlus;
  var b = sumX * sumY;
  var c = n * sumSquaredX;
  var d = Math.pow(sumX, 2);
  var m = (a - b) / (c - d);

  var e = sumY;
  var f = m * sumX;

  var bSlope = (e - f) / n;
  var y = m * ySeries.length + bSlope;

  return y;
}
<commit_msg>Corrected y = mx + b, was using inccorect x<commit_after>/*
 * trend.js
 *
 * Copyright (c) 2015 James G Jenner
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 * Logic is based on Timothy Banas article on calculating trend line
 * http://classroom.synonym.com/calculate-trendline-2709.html
 *
 * Some redundancy is kept so that code is comparable to reference material
 */

function calcTrendLine(xSeries, ySeries) {
  if(xSeries.length < ySeries.length) {
    return 0;
  }

  var n = ySeries.length;
  var idx;
  var sumPlus = 0;
  var sumSquaredX = 0;
  var sumX = 0;
  var sumY = 0;

  for(idx = 0; idx < ySeries.length; idx++) {
    sumPlus += xSeries[idx] * ySeries[idx];
    sumX += xSeries[idx];
    sumY += ySeries[idx];
    sumSquaredX += Math.pow(xSeries[idx], 2);
  }

  var a = n * sumPlus;
  var b = sumX * sumY;
  var c = n * sumSquaredX;
  var d = Math.pow(sumX, 2);
  var m = (a - b) / (c - d);

  var e = sumY;
  var f = m * sumX;

  var bSlope = (e - f) / n;
  var y = m * xSeries.length + bSlope;

  return y;
}
<|endoftext|>


<commit_before>var S = require('string');

var plugged = require('../client');
var redis = require('../db/redis_db');
var config = require('../../config');
var utils = require('../utils');
var langfile = require('../../langfile');
var db = require('../db/sql_db');

module.exports = {
    names:['meh', 'm'],
    handler: function (data) {
        redis.hget('user:roles',data.id).then(function (perm) {
            perm = parseInt(perm, 10);
            if (config.options.bouncer_plus ? (perm > 1) : (perm > 2)) {
                plugged.meh();
                if(S(data.message).contains('-s') && plugged.getMedia() !== undefined){
                    db.models.Song.update({autovote: 'w'}, {where: {plug_id: plugged.getMedia().id}});
                }
            }
        });
        plugged.removeChatMessage(data.cid);
    }
};<commit_msg>commiter is silly. beat him!<commit_after>var S = require('string');

var plugged = require('../client');
var redis = require('../db/redis_db');
var config = require('../../config');
var utils = require('../utils');
var langfile = require('../../langfile');
var db = require('../db/sql_db');

module.exports = {
    names:['meh', 'm'],
    handler: function (data) {
        redis.hget('user:roles',data.id).then(function (perm) {
            perm = parseInt(perm, 10);
            if (config.options.bouncer_plus ? (perm > 1) : (perm > 2)) {
                plugged.meh();
                if(S(data.message).contains('-s') && plugged.getMedia() !== undefined){
                    db.models.Song.update({autovote: 'm'}, {where: {plug_id: plugged.getMedia().id}});
                }
            }
        });
        plugged.removeChatMessage(data.cid);
    }
};<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

/**
 * @namespace
 * @name Key
 */
var Key = this.Key = new function() {
	// TODO: Make sure the keys are called the same as in Scriptographer
	// Missing: tab, cancel, clear, page-down, page-up, comma, minus, period,
	// slash, etc etc etc.

	var keys = {
		 8: 'backspace',
		13: 'enter',
		16: 'shift',
		17: 'control',
		18: 'option',
		19: 'pause',
		20: 'caps-lock',
		27: 'escape',
		32: 'space',
		35: 'end',
		36: 'home',
		37: 'left',
		38: 'up',
		39: 'right',
		40: 'down',
		46: 'delete',
		91: 'command'
	},

	modifiers = {
		shift: false,
		control: false,
		option: false,
		command: false,
		capsLock: false,

		toString: function() {
			return Base.formatObject(this);
		}
	},

	// Since only keypress gets proper keyCodes that are actually representing
	// characters, we need to perform a little trickery here to use these codes
	// in onKeyDown/Up: keydown is used to store the downCode and handle
	// modifiers and special keys such as arrows, space, etc, keypress fires the
	// actual onKeyDown event and maps the keydown keyCode to the keypress
	// charCode so keyup can do the right thing too.
	charCodeMap = {}, // keyCode -> charCode mappings for pressed keys
	keyMap = {}, // Map for currently pressed keys
	downCode; // The last keyCode from keydown

	function handleKey(down, keyCode, charCode, event) {
		var character = String.fromCharCode(charCode),
			key = keys[keyCode] || character.toLowerCase(),
			handler = down ? 'onKeyDown' : 'onKeyUp',
			scope = View.focused && View.focused._scope,
			tool = scope && scope.tool;
		keyMap[key] = down;
		if (tool && tool[handler]) {
			// Call the onKeyDown or onKeyUp handler if present
			// When the handler function returns false, prevent the
			// default behaviour of the key event:
			// PORT: Add to Sg
			var keyEvent = new KeyEvent(down, key, character, event);
			if (tool[handler](keyEvent) === false)
				keyEvent.preventDefault();
		}
	}

	DomEvent.add(document, {
		keydown: function(event) {
			var code = event.which || event.keyCode;
			// If the keyCode is in keys, it needs to be handled by keydown and
			// not in keypress after (arrows for example wont be triggering
			// a keypress, but space would).
			var key = keys[code], name;
			if (key) {
				// Do not fire handleKey for modifiers, but for other keys such
				// ass arrows, delete, backspace, etc.
				if (modifiers[name = Base.camelize(key)] !== undefined) {
					modifiers[name] = true;
				} else {
					// No char code for special keys, but mark as pressed
					charCodeMap[code] = 0;
					handleKey(true, code, null, event);
				}
				// Do not set downCode as we handled it already. Space would
				// be handled twice otherwise, once here, once in keypress.
			} else {
				downCode = code;
			}
		},

		keypress: function(event) {
			if (downCode != null) {
				var code = event.which || event.keyCode;
				// Link the downCode from keydown with the code form keypress, so
				// keyup can retrieve that code again.
				charCodeMap[downCode] = code;
				handleKey(true, downCode, code, event);
				downCode = null;
			}
		},

		keyup: function(event) {
			var code = event.which || event.keyCode,
				key = keys[code], name;
			if (key && modifiers[name = Base.camelize(key)] !== undefined) {
				modifiers[name] = false;
			} else if (charCodeMap[code] != null) {
				handleKey(false, code, charCodeMap[code], event);
				delete charCodeMap[code];
			}
		}
	});

	return {
		/** @lends Key */

		modifiers: modifiers,

		/**
		 * Checks whether the specified key is pressed.
		 *
		 * @param {String} key One of: 'backspace', 'enter', 'shift', 'control',
		 * 'option', 'pause', 'caps-lock', 'escape', 'space', 'end', 'home',
		 * 'left', 'up', 'right', 'down', 'delete', 'command'
		 * @return {Boolean} {@true if the key is pressed}
		 *
		 * @example
		 * // Whenever the user clicks, create a circle shaped path. If the
		 * // shift key is down, fill it with red, otherwise fill it with blue:
		 * function onMouseDown(event) {
		 * 	var path = new Path.Circle(event.point, 10);
		 * 	if(Key.isDown('shift')) {
		 * 		path.fillColor = 'red';
		 * 	} else {
		 * 		path.fillColor = 'blue';
		 * 	}
		 * }
		 */
		isDown: function(key) {
			return !!keyMap[key];
		}
	};
};<commit_msg>Documentation: fix Key.isDown example.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * All rights reserved.
 */

/**
 * @namespace
 * @name Key
 */
var Key = this.Key = new function() {
	// TODO: Make sure the keys are called the same as in Scriptographer
	// Missing: tab, cancel, clear, page-down, page-up, comma, minus, period,
	// slash, etc etc etc.

	var keys = {
		 8: 'backspace',
		13: 'enter',
		16: 'shift',
		17: 'control',
		18: 'option',
		19: 'pause',
		20: 'caps-lock',
		27: 'escape',
		32: 'space',
		35: 'end',
		36: 'home',
		37: 'left',
		38: 'up',
		39: 'right',
		40: 'down',
		46: 'delete',
		91: 'command'
	},

	modifiers = {
		shift: false,
		control: false,
		option: false,
		command: false,
		capsLock: false,

		toString: function() {
			return Base.formatObject(this);
		}
	},

	// Since only keypress gets proper keyCodes that are actually representing
	// characters, we need to perform a little trickery here to use these codes
	// in onKeyDown/Up: keydown is used to store the downCode and handle
	// modifiers and special keys such as arrows, space, etc, keypress fires the
	// actual onKeyDown event and maps the keydown keyCode to the keypress
	// charCode so keyup can do the right thing too.
	charCodeMap = {}, // keyCode -> charCode mappings for pressed keys
	keyMap = {}, // Map for currently pressed keys
	downCode; // The last keyCode from keydown

	function handleKey(down, keyCode, charCode, event) {
		var character = String.fromCharCode(charCode),
			key = keys[keyCode] || character.toLowerCase(),
			handler = down ? 'onKeyDown' : 'onKeyUp',
			scope = View.focused && View.focused._scope,
			tool = scope && scope.tool;
		keyMap[key] = down;
		if (tool && tool[handler]) {
			// Call the onKeyDown or onKeyUp handler if present
			// When the handler function returns false, prevent the
			// default behaviour of the key event:
			// PORT: Add to Sg
			var keyEvent = new KeyEvent(down, key, character, event);
			if (tool[handler](keyEvent) === false)
				keyEvent.preventDefault();
		}
	}

	DomEvent.add(document, {
		keydown: function(event) {
			var code = event.which || event.keyCode;
			// If the keyCode is in keys, it needs to be handled by keydown and
			// not in keypress after (arrows for example wont be triggering
			// a keypress, but space would).
			var key = keys[code], name;
			if (key) {
				// Do not fire handleKey for modifiers, but for other keys such
				// ass arrows, delete, backspace, etc.
				if (modifiers[name = Base.camelize(key)] !== undefined) {
					modifiers[name] = true;
				} else {
					// No char code for special keys, but mark as pressed
					charCodeMap[code] = 0;
					handleKey(true, code, null, event);
				}
				// Do not set downCode as we handled it already. Space would
				// be handled twice otherwise, once here, once in keypress.
			} else {
				downCode = code;
			}
		},

		keypress: function(event) {
			if (downCode != null) {
				var code = event.which || event.keyCode;
				// Link the downCode from keydown with the code form keypress, so
				// keyup can retrieve that code again.
				charCodeMap[downCode] = code;
				handleKey(true, downCode, code, event);
				downCode = null;
			}
		},

		keyup: function(event) {
			var code = event.which || event.keyCode,
				key = keys[code], name;
			if (key && modifiers[name = Base.camelize(key)] !== undefined) {
				modifiers[name] = false;
			} else if (charCodeMap[code] != null) {
				handleKey(false, code, charCodeMap[code], event);
				delete charCodeMap[code];
			}
		}
	});

	return {
		/** @lends Key */

		modifiers: modifiers,

		/**
		 * Checks whether the specified key is pressed.
		 *
		 * @param {String} key One of: 'backspace', 'enter', 'shift', 'control',
		 * 'option', 'pause', 'caps-lock', 'escape', 'space', 'end', 'home',
		 * 'left', 'up', 'right', 'down', 'delete', 'command'
		 * @return {Boolean} {@true if the key is pressed}
		 *
		 * @example
		 * // Whenever the user clicks, create a circle shaped path. If the
		 * // 'a' key is pressed, fill it with red, otherwise fill it with blue:
		 * function onMouseDown(event) {
		 * 	var path = new Path.Circle(event.point, 10);
		 * 	if(Key.isDown('a')) {
		 * 		path.fillColor = 'red';
		 * 	} else {
		 * 		path.fillColor = 'blue';
		 * 	}
		 * }
		 */
		isDown: function(key) {
			return !!keyMap[key];
		}
	};
};<|endoftext|>