<commit_before>import {LightningElement, api, track, wire} from 'lwc';
import {isNotEmpty, debouncify, isUndefined} from 'c/utilCommon';
import GeFormService from 'c/geFormService';
import GeLabelService from 'c/geLabelService';
import {getObjectInfo} from "lightning/uiObjectInfoApi";
import { fireEvent } from 'c/pubsubNoPageRef';
import DI_DONATION_AMOUNT from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DONATION_DONOR_FIELD from '@salesforce/schema/DataImport__c.Donation_Donor__c';
import DONATION_RECORD_TYPE_NAME from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';

const LOOKUP_TYPE = 'REFERENCE';
const PICKLIST_TYPE = 'PICKLIST';
const TEXT_AREA_TYPE = 'TEXTAREA';
const BOOLEAN_TYPE = 'BOOLEAN';
const DELAY = 300;
const RICH_TEXT_FORMATS = [
    'font', 'size', 'bold', 'italic', 'underline', 'strike', 'list', 'indent', 'align', 'link', 'clean', 'table', 'header'
];
const CURRENCY = 'currency';
const PERCENT = 'percent';
const DECIMAL = 'decimal';

export default class GeFormField extends LightningElement {
    @track value;
    @track picklistValues = [];
    @track objectDescribeInfo;
    @track richTextValid = true;
    @track _disabled = false;
    @api element;
    @api targetFieldName;
    _defaultValue = null;
    @api recordTypeId;

    richTextFormats = RICH_TEXT_FORMATS;
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    handleValueChangeSync = (event) => {
        this.value = this.getValueFromChangeEvent(event);
        const detail = {
            element: this.element,
            value: this.value,
            targetFieldName: this.targetFieldName
        };
        const evt = new CustomEvent('valuechange', {detail, bubbles: true});
        this.dispatchEvent(evt);

        if (this.isLookup) {
            const detail = {
                ...event.detail,
                objectMappingDevName: this.objectMappingDevName
            }

            const selectRecordEvent = new CustomEvent(
                'lookuprecordselect',
                { detail: detail });
            this.dispatchEvent(selectRecordEvent);
        }

        if (this.isPicklist) {
            this.handlePicklistChange();
        }

        if(this.isRichText) {
            this.checkRichTextValidity();
        }

        if(this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
            // fire event for reactive widget component containing the Data Import field API name and Value
            // currently only used for the Donation Amount.
            fireEvent(null, 'widgetData', { donationAmount: this.value });
        }
    };

    handleValueChange = debouncify(this.handleValueChangeSync.bind(this), DELAY);

    handlePicklistChange() {
        if (this.fieldApiName === DONATION_DONOR_FIELD.fieldApiName) {
            const changeDonationDonorEvent = new CustomEvent(
                'changedonationdonor',
                {detail: {value: this.value}});
            this.dispatchEvent(changeDonationDonorEvent);
        }
    }

    /**
     * Retrieve object metadata. Used to configure how fields are displayed on the form.
     */
    @wire(getObjectInfo, { objectApiName: '$objectApiName' })
    wiredObjectInfo(response) {
        if(response.data) {
            this.objectDescribeInfo = response.data;
        }
    }

    connectedCallback() {
        const { defaultValue, recordValue } = this.element;

        if(recordValue) {

            // set the record value to the element value
            this.value = recordValue;
        } else if(defaultValue) {

            // Set the default value if there is one
            // and no record value.
            this._defaultValue = defaultValue;
            this.value = defaultValue;
            this.handlePicklistChange();
        }
    }

    getValueFromChangeEvent(event) {
        if(this.fieldType === BOOLEAN_TYPE) {
            return event.detail.checked.toString();
        } else if(this.isRichText) {
            return event.target.value;
        }

        return event.detail.value;
    }

    /**
     * TRUE when a field is required and filled in correctly, or not required at all.
     * @returns {boolean}
     */
    @api
    isValid() {
        // We need to check for invalid values, regardless if the field is required
        let fieldIsValid = this.checkFieldValidity();

        if(this.element !== null && this.element.required) {
            return isNotEmpty(this.value) && fieldIsValid;
        }

        return fieldIsValid;
    }

    /**
     * TRUE when a field is filled in, and is the correct format.
     * @returns {boolean}
     */
    checkFieldValidity() {
        // TODO: Handle other input types, if needed
        const inputField = this.template.querySelector('[data-id="inputComponent"]');
        if(typeof inputField !== 'undefined'
            && inputField !== null
            && typeof inputField.reportValidity === 'function'
            && typeof inputField.checkValidity === 'function') {
                inputField.reportValidity();
                return inputField.checkValidity();
        } else if(this.isRichText) {
            this.checkRichTextValidity();
            if(!this.richTextValid) {
                // workaround, field will not display as invalid if it is untouched
                inputField.focus();
                inputField.blur();
            }
            return this.richTextValid;
        }
        return true;
    }

    checkRichTextValidity() {
        if(this.element.required) {
            const isValid = isNotEmpty(this.value) && this.value.length > 0;
            this.richTextValid = isValid;
            return isValid;
        }
        return true;
    }

    @api
    disable() {
        this._disabled = true;
    }

    @api
    enable() {
        this._disabled = false;
    }

    @api
    get fieldAndValue() {
        let fieldAndValue = {};

        // KIET TBD: This is where we are keeping the field mapping
        // CMT record name at, element.value.
        // However, it may change to the array dataImportFieldMappingDevNames
        // If so, we need to update this to reflect that.
        // In the Execute Anonymous code, both fields are populated.

        if (this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName) {
            // value is the RecordType Id, but the DataImport's source field expects
            // the RecordType Name
            fieldAndValue[this.formElementName] =
                this.objectDescribeInfo.recordTypeInfos[this.value].name;
        } else {
            fieldAndValue[this.formElementName] = this.value;
        }

        return fieldAndValue;
    }

    get formElementName() {
        return this.element.componentName ? this.element.componentName : this.element.dataImportFieldMappingDevNames[0];
    }

    get inputType() {
        return GeFormService.getInputTypeFromDataType(this.fieldType);
    }

    get formatter() {
        return GeFormService.getNumberFormatterByDescribeType(this.fieldType);
    }

    get required() {
        return (this.fieldInfo && this.fieldInfo.Is_Required && this.fieldType !== BOOLEAN_TYPE) ||
            (this.element && this.element.required);
    }

    get disabled() {
        return this._disabled || (this.element && this.element.disabled);
    }

    get granularity() {
        if (this.formatter) {
            switch (this.fieldType.toLowerCase()) {
                case CURRENCY: return '0.01';
                case PERCENT: return '0.01';
                case DECIMAL: return '0.001';
                default: return 'any';
            }
        }
        return undefined;
    }

    get fieldInfo() {
        return isNotEmpty(this.targetFieldName) ?
            GeFormService.getFieldMappingWrapperFromTarget(this.targetFieldName) :
            GeFormService.getFieldMappingWrapper(this.formElementName);
    }

    get fieldDescribeInfo() {
        if(this.objectDescribeInfo) {
            return this.objectDescribeInfo.fields[this.fieldApiName];
        }
    }

    get objectInfo() {
        return GeFormService.getObjectMappingWrapper(this.objectMappingDevName);
    }

    get fieldType() {
        return this.fieldInfo.Target_Field_Data_Type;
    }

    get isLightningInput() {
        return typeof GeFormService.getInputTypeFromDataType(this.fieldType) !== 'undefined';
    }

    get isRichText() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get isLookup() {
        return this.fieldType === LOOKUP_TYPE;
    }

    @api
    get isPicklist() {
        return this.fieldType === PICKLIST_TYPE;
    }

    get isTextArea() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return !this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get objectMappingDevName() {
        return this.fieldInfo.Target_Object_Mapping_Dev_Name;
    }

    get objectApiName() {
        if(typeof this.objectInfo !== 'undefined') {
            return this.objectInfo.Object_API_Name;
        }
    }

    get fieldApiName() {
        return this.fieldInfo.Target_Field_API_Name;
    }

    @api
    get sourceFieldAPIName() {
        return this.fieldInfo.Source_Field_API_Name;
    }

    @api
    get fieldLabel() {
        return this.element.customLabel;
    }

    @api
    get fieldValueAndLabel() {

        let fieldWrapper = { value: this.value, label: this.fieldLabel };
        let returnMap = {};
        returnMap[ this.sourceFieldAPIName ] = fieldWrapper;

        return returnMap;

    }

    @api
    setCustomValidity(errorMessage) {

        let inputField = this.template.querySelector('[data-id="inputComponent"]');
        inputField.setCustomValidity(errorMessage);
        inputField.reportValidity();

    }

    @api
    clearCustomValidity() {

        if (this.isLookup) {
            let inputField = this.template.querySelector('[data-id="inputComponent"]');
            inputField.clearCustomValidity();
        } else {
            this.setCustomValidity('');
        }

    }

    /**
     * Load a value into the form field.
     * @param data  An sObject potentially containing a value to load.
     * */
    @api
    load(data) {
        let value;
        if (data.hasOwnProperty(this.sourceFieldAPIName)) {
            value = data[this.sourceFieldAPIName];
            if (value === null || value.value === null) {
                this.reset();
            } else {
                this.value = value.value || value;

                if (this.isLookup) {
                    this.loadLookUp(data, this.value);
                }
            }

            if (this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
                // fire event for reactive widget component containing the Data Import field API name and Value
                // currently only used for the Donation Amount.
                fireEvent(null, 'widgetData', {donationAmount: this.value});
            }

        } else if (!isUndefined(data.value)) {
            // When the geFormField cmp is used inside of geFormWidgetAllocation,
            // geFormWidgetAllocation selects the field cmp to load a value into manually,
            // and passes an {value: <value>} object.  To support that case this block
            // loads the value directly even though data does not have a property for
            // this.sourceFieldAPIName
           this.value = data.value;
        } else {
            // Property isn't defined.  Don't do anything.
            return false;
        }
    }

    /**
     * Loads a value into a look-up field
     * @param data An sObject potentially containing a value to load.
     * @param value A form field value
     */
    loadLookUp(data, value) {
        const lookup = this.template.querySelector('c-ge-form-field-lookup');

        let displayValue;
        const relationshipFieldName = this.sourceFieldAPIName.replace('__c', '__r');

        if (data[relationshipFieldName] &&
            data[relationshipFieldName]['Name']) {
            displayValue = data[relationshipFieldName].Name;

        } else if (data[this.sourceFieldAPIName] &&
            data[this.sourceFieldAPIName]['displayValue']) {
            displayValue = data[this.sourceFieldAPIName].displayValue;

        } else if (data.displayValue) {
            displayValue = data.displayValue;

        }

        lookup.setSelected({value, displayValue});

    }

    @api
    reset() {
        this.value = this._defaultValue;

        if (this.isLookup) {
            const lookup = this.template.querySelector('c-ge-form-field-lookup');
            lookup.reset();
        }

        if (this.isPicklist) {
            this.template.querySelector('c-ge-form-field-picklist').reset();
            this.handlePicklistChange();
        }
    }

}<commit_msg>Load recordTypeId value correctly when opening from row<commit_after>import {LightningElement, api, track, wire} from 'lwc';
import {isNotEmpty, debouncify, isUndefined} from 'c/utilCommon';
import GeFormService from 'c/geFormService';
import GeLabelService from 'c/geLabelService';
import {getObjectInfo} from "lightning/uiObjectInfoApi";
import { fireEvent } from 'c/pubsubNoPageRef';
import DI_DONATION_AMOUNT from '@salesforce/schema/DataImport__c.Donation_Amount__c';
import DONATION_DONOR_FIELD from '@salesforce/schema/DataImport__c.Donation_Donor__c';
import DONATION_RECORD_TYPE_NAME from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';

const LOOKUP_TYPE = 'REFERENCE';
const PICKLIST_TYPE = 'PICKLIST';
const TEXT_AREA_TYPE = 'TEXTAREA';
const BOOLEAN_TYPE = 'BOOLEAN';
const DELAY = 300;
const RICH_TEXT_FORMATS = [
    'font', 'size', 'bold', 'italic', 'underline', 'strike', 'list', 'indent', 'align', 'link', 'clean', 'table', 'header'
];
const CURRENCY = 'currency';
const PERCENT = 'percent';
const DECIMAL = 'decimal';

export default class GeFormField extends LightningElement {
    @track value;
    @track picklistValues = [];
    @track objectDescribeInfo;
    @track richTextValid = true;
    @track _disabled = false;
    @api element;
    @api targetFieldName;
    _defaultValue = null;
    @api recordTypeId;

    richTextFormats = RICH_TEXT_FORMATS;
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    handleValueChangeSync = (event) => {
        this.value = this.getValueFromChangeEvent(event);
        const detail = {
            element: this.element,
            value: this.value,
            targetFieldName: this.targetFieldName
        };
        const evt = new CustomEvent('valuechange', {detail, bubbles: true});
        this.dispatchEvent(evt);

        if (this.isLookup) {
            const detail = {
                ...event.detail,
                objectMappingDevName: this.objectMappingDevName
            }

            const selectRecordEvent = new CustomEvent(
                'lookuprecordselect',
                { detail: detail });
            this.dispatchEvent(selectRecordEvent);
        }

        if (this.isPicklist) {
            this.handlePicklistChange();
        }

        if(this.isRichText) {
            this.checkRichTextValidity();
        }

        if(this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
            // fire event for reactive widget component containing the Data Import field API name and Value
            // currently only used for the Donation Amount.
            fireEvent(null, 'widgetData', { donationAmount: this.value });
        }
    };

    handleValueChange = debouncify(this.handleValueChangeSync.bind(this), DELAY);

    handlePicklistChange() {
        if (this.fieldApiName === DONATION_DONOR_FIELD.fieldApiName) {
            const changeDonationDonorEvent = new CustomEvent(
                'changedonationdonor',
                {detail: {value: this.value}});
            this.dispatchEvent(changeDonationDonorEvent);
        }
    }

    /**
     * Retrieve object metadata. Used to configure how fields are displayed on the form.
     */
    @wire(getObjectInfo, { objectApiName: '$objectApiName' })
    wiredObjectInfo(response) {
        if(response.data) {
            this.objectDescribeInfo = response.data;
        }
    }

    connectedCallback() {
        const { defaultValue, recordValue } = this.element;

        if(recordValue) {

            // set the record value to the element value
            this.value = recordValue;
        } else if(defaultValue) {

            // Set the default value if there is one
            // and no record value.
            this._defaultValue = defaultValue;
            this.value = defaultValue;
            this.handlePicklistChange();
        }
    }

    getValueFromChangeEvent(event) {
        if(this.fieldType === BOOLEAN_TYPE) {
            return event.detail.checked.toString();
        } else if(this.isRichText) {
            return event.target.value;
        }

        return event.detail.value;
    }

    /**
     * TRUE when a field is required and filled in correctly, or not required at all.
     * @returns {boolean}
     */
    @api
    isValid() {
        // We need to check for invalid values, regardless if the field is required
        let fieldIsValid = this.checkFieldValidity();

        if(this.element !== null && this.element.required) {
            return isNotEmpty(this.value) && fieldIsValid;
        }

        return fieldIsValid;
    }

    /**
     * TRUE when a field is filled in, and is the correct format.
     * @returns {boolean}
     */
    checkFieldValidity() {
        // TODO: Handle other input types, if needed
        const inputField = this.template.querySelector('[data-id="inputComponent"]');
        if(typeof inputField !== 'undefined'
            && inputField !== null
            && typeof inputField.reportValidity === 'function'
            && typeof inputField.checkValidity === 'function') {
                inputField.reportValidity();
                return inputField.checkValidity();
        } else if(this.isRichText) {
            this.checkRichTextValidity();
            if(!this.richTextValid) {
                // workaround, field will not display as invalid if it is untouched
                inputField.focus();
                inputField.blur();
            }
            return this.richTextValid;
        }
        return true;
    }

    checkRichTextValidity() {
        if(this.element.required) {
            const isValid = isNotEmpty(this.value) && this.value.length > 0;
            this.richTextValid = isValid;
            return isValid;
        }
        return true;
    }

    @api
    disable() {
        this._disabled = true;
    }

    @api
    enable() {
        this._disabled = false;
    }

    @api
    get fieldAndValue() {
        let fieldAndValue = {};

        // KIET TBD: This is where we are keeping the field mapping
        // CMT record name at, element.value.
        // However, it may change to the array dataImportFieldMappingDevNames
        // If so, we need to update this to reflect that.
        // In the Execute Anonymous code, both fields are populated.

        if (this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName) {
            // value is the RecordType Id, but the DataImport's source field expects
            // the RecordType Name
            fieldAndValue[this.formElementName] =
                this.objectDescribeInfo.recordTypeInfos[this.value].name;
        } else {
            fieldAndValue[this.formElementName] = this.value;
        }

        return fieldAndValue;
    }

    get formElementName() {
        return this.element.componentName ? this.element.componentName : this.element.dataImportFieldMappingDevNames[0];
    }

    get inputType() {
        return GeFormService.getInputTypeFromDataType(this.fieldType);
    }

    get formatter() {
        return GeFormService.getNumberFormatterByDescribeType(this.fieldType);
    }

    get required() {
        return (this.fieldInfo && this.fieldInfo.Is_Required && this.fieldType !== BOOLEAN_TYPE) ||
            (this.element && this.element.required);
    }

    get disabled() {
        return this._disabled || (this.element && this.element.disabled);
    }

    get granularity() {
        if (this.formatter) {
            switch (this.fieldType.toLowerCase()) {
                case CURRENCY: return '0.01';
                case PERCENT: return '0.01';
                case DECIMAL: return '0.001';
                default: return 'any';
            }
        }
        return undefined;
    }

    get fieldInfo() {
        return isNotEmpty(this.targetFieldName) ?
            GeFormService.getFieldMappingWrapperFromTarget(this.targetFieldName) :
            GeFormService.getFieldMappingWrapper(this.formElementName);
    }

    get fieldDescribeInfo() {
        if(this.objectDescribeInfo) {
            return this.objectDescribeInfo.fields[this.fieldApiName];
        }
    }

    get objectInfo() {
        return GeFormService.getObjectMappingWrapper(this.objectMappingDevName);
    }

    get fieldType() {
        return this.fieldInfo.Target_Field_Data_Type;
    }

    get isLightningInput() {
        return typeof GeFormService.getInputTypeFromDataType(this.fieldType) !== 'undefined';
    }

    get isRichText() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get isLookup() {
        return this.fieldType === LOOKUP_TYPE;
    }

    @api
    get isPicklist() {
        return this.fieldType === PICKLIST_TYPE;
    }

    get isTextArea() {
        if(typeof this.fieldDescribeInfo !== 'undefined' && this.fieldType === TEXT_AREA_TYPE) {
            return !this.fieldDescribeInfo.htmlFormatted;
        }
    }

    @api
    get objectMappingDevName() {
        return this.fieldInfo.Target_Object_Mapping_Dev_Name;
    }

    get objectApiName() {
        if(typeof this.objectInfo !== 'undefined') {
            return this.objectInfo.Object_API_Name;
        }
    }

    get fieldApiName() {
        return this.fieldInfo.Target_Field_API_Name;
    }

    @api
    get sourceFieldAPIName() {
        return this.fieldInfo.Source_Field_API_Name;
    }

    @api
    get fieldLabel() {
        return this.element.customLabel;
    }

    @api
    get fieldValueAndLabel() {

        let fieldWrapper = { value: this.value, label: this.fieldLabel };
        let returnMap = {};
        returnMap[ this.sourceFieldAPIName ] = fieldWrapper;

        return returnMap;

    }

    @api
    setCustomValidity(errorMessage) {

        let inputField = this.template.querySelector('[data-id="inputComponent"]');
        inputField.setCustomValidity(errorMessage);
        inputField.reportValidity();

    }

    @api
    clearCustomValidity() {

        if (this.isLookup) {
            let inputField = this.template.querySelector('[data-id="inputComponent"]');
            inputField.clearCustomValidity();
        } else {
            this.setCustomValidity('');
        }

    }

    /**
     * Load a value into the form field.
     * @param data  An sObject potentially containing a value to load.
     * */
    @api
    load(data) {
        let value;
        if (data.hasOwnProperty(this.sourceFieldAPIName)) {
            value = data[this.sourceFieldAPIName];
            if (value === null || value.value === null) {
                this.reset();
            } else {
                this.value = value.value || value;

                if (this.isLookup) {
                    this.loadLookUp(data, this.value);
                }
            }

            if (this.sourceFieldAPIName === DI_DONATION_AMOUNT.fieldApiName) {
                // fire event for reactive widget component containing the Data Import field API name and Value
                // currently only used for the Donation Amount.
                fireEvent(null, 'widgetData', {donationAmount: this.value});
            }

        } else if (!isUndefined(data.value)) {
            // When the geFormField cmp is used inside of geFormWidgetAllocation,
            // geFormWidgetAllocation selects the field cmp to load a value into manually,
            // and passes an {value: <value>} object.  To support that case this block
            // loads the value directly even though data does not have a property for
            // this.sourceFieldAPIName
           this.value = data.value;
        } else {
            // Property isn't defined.  Don't do anything.
            return false;
        }
    }

    /**
     * Loads a value into a look-up field
     * @param data An sObject potentially containing a value to load.
     * @param value A form field value
     */
    loadLookUp(data, value) {
        const lookup = this.template.querySelector('c-ge-form-field-lookup');

        let displayValue;
        const relationshipFieldName = this.sourceFieldAPIName.replace('__c', '__r');

        if (data[relationshipFieldName] &&
            data[relationshipFieldName]['Name']) {
            displayValue = data[relationshipFieldName].Name;

        } else if (data[this.sourceFieldAPIName] &&
            data[this.sourceFieldAPIName]['displayValue']) {
            displayValue = data[this.sourceFieldAPIName].displayValue;

        } else if (data.displayValue) {
            displayValue = data.displayValue;

        }

        if (this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName) {
            lookup.setSelected({value: value, displayValue: value});
        } else {
            lookup.setSelected({value, displayValue});
        }
    }

    @api
    reset() {
        this.value = this._defaultValue;

        if (this.isLookup) {
            const lookup = this.template.querySelector('c-ge-form-field-lookup');
            lookup.reset();
        }

        if (this.isPicklist) {
            this.template.querySelector('c-ge-form-field-picklist').reset();
            this.handlePicklistChange();
        }
    }

}<|endoftext|>


<commit_before>var database = require('../database.js');

exports.index = function (req, res) {
	database.DonationRequest.find({}).populate("groups").run(function (err, requests) {
		res.render("requests", {requests: requests, currentCategory: "requests"});
	});
};

exports.show = function (req, res) {
	database.DonationRequest.findById(req.params.id).populate('donors').populate('groups').run(function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			res.render("requests/show", {request: request, currentCategory: "requests"});
		}
	});
};

exports.edit = function (req, res) {
	database.DonationRequest.findById(req.params.id, function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			request.errors = [];
			database.Group.find({}, function (err, groups) {
				database.Donor.find({}, function (err, donors) {
					res.render("requests/_form", {request: request, currentCategory: "requests", donors: donors, groups:groups});
				});
			});
		}
	});
};

exports.update = function (req, res) {
	database.DonationRequest.findById(req.params.id, function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			var requestData = req.body.request;

			for (i in requestData) request[i] = requestData[i];

			request.save(function (err, result) {
				if (err) {
					requestData.errors = err.errors;
					database.Group.find({}, function (err, groups) {
						database.Donor.find({}, function (err, donors) {
							res.render("requests/_form", {request: requestData, currentCategory: "requests", donors: donors, groups:groups});
						});
					});
				} else {
					res.redirect("/requests/" + result._id);
				}
			});
		}
	});
};

exports.add = function (req, res) {
	var request = {};
	request.errors = [];
	database.Group.find({}, function (err, groups) {
		database.Donor.find({}, function (err, donors) {
			res.render("requests/_form", {request: request, currentCategory: "requests", donors: donors, groups: groups});
		});
	});
};

exports.create = function (req, res) {
	console.dir(req.body.request);

	var request = new database.DonationRequest(req.body.request);
	// FIXME: Doesn't work : request.user = req.session.user;
	request.save(function (err, result) {
		if (err) {
			console.dir(err);

			var requestData = req.body.request;
			requestData.errors = err;
			database.Group.find({}, function (err, groups) {
				database.Donor.find({}, function (err, donors) {
					res.render("requests/_form", {request: requestData, currentCategory: "requests", donors: donors, groups:groups});
				});
			});
		} else {
			if (req.body.action == "Save") {
				res.redirect("/requests/" + result._id);
			} else {
				sendRequest( result._id, function () {
					res.redirect("/requests/" + result._id);
				});
			}
		}
	});
};

exports.destroy = function (req, res) {
	database.DonationRequest.findById(req.params.id).remove(function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			res.redirect("/requests");
		}
	});
};
<commit_msg>Fix adding of requests<commit_after>var database = require('../database.js');

exports.index = function (req, res) {
	database.DonationRequest.find({}).populate("groups").run(function (err, requests) {
		res.render("requests", {requests: requests, currentCategory: "requests"});
	});
};

exports.add = function (req, res) {
	var request = {};
	request.errors = [];
	database.Group.find({}, function (err, groups) {
		database.Donor.find({}, function (err, donors) {
			res.render("requests/_form", {request: request, currentCategory: "requests", donors: donors, groups: groups});
		});
	});
};

exports.create = function (req, res) {
	console.dir(req.body.request);

	var request = new database.DonationRequest(req.body.request);
	// FIXME: Doesn't work : request.user = req.session.user;
	request.save(function (err, result) {
		if (err) {
			console.dir(err);

			var requestData = req.body.request;
			requestData.errors = err;
			database.Group.find({}, function (err, groups) {
				database.Donor.find({}, function (err, donors) {
					res.render("requests/_form", {request: requestData, currentCategory: "requests", donors: donors, groups:groups});
				});
			});
		} else {
			if (req.body.action == "Save") {
				res.redirect("/requests/" + result._id);
			} else {
				sendRequest( result._id, function () {
					res.redirect("/requests/" + result._id);
				});
			}
		}
	});
};

exports.edit = function (req, res) {
	database.DonationRequest.findById(req.params.id, function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			request.errors = [];
			database.Group.find({}, function (err, groups) {
				database.Donor.find({}, function (err, donors) {
					res.render("requests/_form", {request: request, currentCategory: "requests", donors: donors, groups:groups});
				});
			});
		}
	});
};

exports.update = function (req, res) {
	database.DonationRequest.findById(req.params.id, function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			var requestData = req.body.request;

			for (i in requestData) request[i] = requestData[i];

			request.save(function (err, result) {
				if (err) {
					requestData.errors = err.errors;
					database.Group.find({}, function (err, groups) {
						database.Donor.find({}, function (err, donors) {
							res.render("requests/_form", {request: requestData, currentCategory: "requests", donors: donors, groups:groups});
						});
					});
				} else {
					res.redirect("/requests/" + result._id);
				}
			});
		}
	});
};

exports.show = function (req, res) {
	database.DonationRequest.findById(req.params.id).populate('donors').populate('groups').run(function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			res.render("requests/show", {request: request, currentCategory: "requests"});
		}
	});
};

exports.destroy = function (req, res) {
	database.DonationRequest.findById(req.params.id).remove(function (err, request) {
		if (err || !request) {
			res.send("Could not find request: " + req.params.id);
		} else {
			res.redirect("/requests");
		}
	});
};
<|endoftext|>


<commit_before>// ORNAMENTS ///////////////////////////////////////////////////////

// Added as part of the Ingress #Helios in 2014, ornaments
// are additional image overlays for portals.
// currently there are 6 known types of ornaments: ap$x$suffix
// - cluster portals (without suffix)
// - volatile portals (_v)
// - meeting points (_start)
// - finish points (_end)
//
// Beacons and Frackers were introduced at the launch of the Ingress
// ingame store on November 1st, 2015
// - Beacons (pe$TAG - $NAME) ie: 'peNIA - NIANTIC'
// - Frackers ('peFRACK')
// (there are 7 different colors for each of them)

window.ornaments = {

  OVERLAY_SIZE: 60,
  OVERLAY_OPACITY: 0.6,
  iconUrls: [],
  excludedOrnaments: [],

  setup: function () {
    this._portals = {};
    var layerGroup = L.layerGroup;
    if (window.map.options.preferCanvas && L.Browser.canvas && !window.DISABLE_CANVASICONLAYER) {
      layerGroup = L.canvasIconLayer;
      L.CanvasIconLayer.mergeOptions({ padding: L.Canvas.prototype.options.padding });
    }
    this.load();
    this._layer = layerGroup();
    this._beacons = layerGroup();
    this._frackers = layerGroup();
    this._scout = layerGroup();
    this._battle = layerGroup();
    this._excluded = layerGroup(); //to keep excluded ornaments in an own layer

    window.layerChooser.addOverlay(this._layer, 'Ornaments');
    window.layerChooser.addOverlay(this._beacons, 'Beacons');
    window.layerChooser.addOverlay(this._frackers, 'Frackers');
    window.layerChooser.addOverlay(this._scout, 'Scouting');
    window.layerChooser.addOverlay(this._battle, 'Battle');
    window.layerChooser.addOverlay(this._excluded, 'Excluded'); //just for testing

    $('#toolbox').append('Ornaments Opt');

  },

  addPortal: function (portal) {
    this.removePortal(portal);
    var ornaments = portal.options.data.ornaments;
    if (ornaments && ornaments.length) {
      this._portals[portal.options.guid] = ornaments.map(function (ornament) {
        var layer = this._layer;
        var opacity = this.OVERLAY_OPACITY;
        var size = this.OVERLAY_SIZE;
        var anchor = [size / 2, size / 2];
        var iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/' + ornament + '.png';

        if (ornament.startsWith('pe')) {
          layer = ornament === 'peFRACK'
            ? this._frackers
            : this._beacons;
        }

        if (ornament.startsWith('sc')) {
          layer = this._scout;
        }

        if (ornament.startsWith('peB')) {
          layer = this._battle;
        }

        if (typeof (window.ornaments.iconUrls[ornament]) !== 'undefined') {
          opacity = 1;
          iconUrl = window.ornaments.iconUrls[ornament];
          anchor = [size / 2, size];
        }

        var exclude = false;
        exclude = this.excludedOrnaments.some( function(pattern) {
              return ornament.startsWith(pattern)
        });
        if (exclude){
//          opacity = 0;
          layer = this._excluded;
        }

        return L.marker(portal.getLatLng(), {
          icon: L.icon({
            iconUrl: iconUrl,
            iconSize: [size, size],
            iconAnchor: anchor, // https://github.com/IITC-CE/Leaflet.Canvas-Markers/issues/4
            className: 'no-pointer-events'
          }),
          interactive: false,
          keyboard: false,
          opacity: opacity,
          layer: layer,
        }).addTo(layer);

      }, this);
    }
  },

  removePortal: function (portal) {
    var guid = portal.options.guid;
    if (this._portals[guid]) {
//      console.log(window.ornaments._portals[guid], guid);
      this._portals[guid].forEach(function (marker) {
        marker.options.layer.removeLayer(marker);
      });
      delete this._portals[guid];
    }
  },

  load: function () {
    try {
      var dataStr = localStorage['excludedOrnaments'];
      if (dataStr === undefined) return;
      this.excludedOrnaments = JSON.parse(dataStr);
    } catch(e) {
    console.warn('ornaments: failed to load data from localStorage: '+e);
    }
  },

  save: function () {
    localStorage['excludedOrnaments'] = JSON.stringify(this.excludedOrnaments);
  },

  ornamentsOpt: function () {
    var eO = window.ornaments.excludedOrnaments.toString();
    var html = '
'
             + 'Hide Ornaments from IITC that start with:
'
             + '<input type="text" value="'+eO +'" id="ornaments_E"></input>
'
             + '(separator: space or komma allowed)
'
             + 'known Ornaments:
'
             + 'ap1-ap9
'
             + 'sc5_p
'
             + 'bb_s
'
             + 'peFRACK
'
             + 'peNIA
'
             + 'peBN_BLM
'
             + 'peBN_ENL_WINNER-60
'
             + 'peBN_RES_WINNER-60
'
             + 'peBN_TIED_WINNER-60
'
             + 'peBR_REWARD-10_125_38
'
             + 'peBR_REWARD-10_150_75
'
             + 'peBR_REWARD-10_175_113
'
             + 'peFW_ENL
'
             + 'peFW_RES
'
             + 'peLOOK
'
             + 'peNEMESIS
'
             + 'peTOASTY
'
             + 'peBB_BATTLE_RARE
'
             + 'peBB_BATTLE
'
             + '
'
             + '
';

    dialog({
      html:html,
      id:'ornamentsOpt',
      dialogClass:'ui-dialog-content',
      title:'Ornament excludes',
      buttons: {
        'OK': function() {
          // stuff to do
          // remove markers
//          debugger;
          window.ornaments.excludedOrnaments = $("#ornaments_E").val().split(/[\s,]+/);
          console.log(window.ornaments.excludedOrnaments);
          window.ornaments.save();
          // reload markers

          $(this).dialog('close');
        }
      }
    });
  }
};
<commit_msg>selectable Ornaments<commit_after>// ORNAMENTS ///////////////////////////////////////////////////////

// Added as part of the Ingress #Helios in 2014, ornaments
// are additional image overlays for portals.
// currently there are 6 known types of ornaments: ap$x$suffix
// - cluster portals (without suffix)
// - volatile portals (_v)
// - meeting points (_start)
// - finish points (_end)
//
// Beacons and Frackers were introduced at the launch of the Ingress
// ingame store on November 1st, 2015
// - Beacons (pe$TAG - $NAME) ie: 'peNIA - NIANTIC'
// - Frackers ('peFRACK')
// (there are 7 different colors for each of them)

window.ornaments = {

  OVERLAY_SIZE: 60,
  OVERLAY_OPACITY: 0.6,
  iconUrls: [],
  excludedOrnaments: [],
  knownOrnaments: {},

  setup: function () {
    this._portals = {};
    var layerGroup = L.layerGroup;
    if (window.map.options.preferCanvas && L.Browser.canvas && !window.DISABLE_CANVASICONLAYER) {
      layerGroup = L.canvasIconLayer;
      L.CanvasIconLayer.mergeOptions({ padding: L.Canvas.prototype.options.padding });
    }
    this.load();
    this._layer = layerGroup();
    this._beacons = layerGroup();
    this._frackers = layerGroup();
    this._scout = layerGroup();
    this._battle = layerGroup();
    this._excluded = layerGroup(); //to keep excluded ornaments in an own layer

    window.layerChooser.addOverlay(this._layer, 'Ornaments');
    window.layerChooser.addOverlay(this._beacons, 'Beacons');
    window.layerChooser.addOverlay(this._frackers, 'Frackers');
    window.layerChooser.addOverlay(this._scout, 'Scouting');
    window.layerChooser.addOverlay(this._battle, 'Battle');
    window.layerChooser.addOverlay(this._excluded, 'Excluded'); //just for testing

    $('#toolbox').append('Ornaments Opt');

  },

  addPortal: function (portal) {
    this.removePortal(portal);
    var ornaments = portal.options.data.ornaments;
    if (ornaments && ornaments.length) {
      this._portals[portal.options.guid] = ornaments.map(function (ornament) {
        var layer = this._layer;
        var opacity = this.OVERLAY_OPACITY;
        var size = this.OVERLAY_SIZE;
        var anchor = [size / 2, size / 2];
        var iconUrl = '//commondatastorage.googleapis.com/ingress.com/img/map_icons/marker_images/' + ornament + '.png';

        if (!this.knownOrnaments[ornament]) {this.knownOrnaments[ornament]=false};

        if (ornament.startsWith('pe')) {
          layer = ornament === 'peFRACK'
            ? this._frackers
            : this._beacons;
        }

        if (ornament.startsWith('sc')) {
          layer = this._scout;
        }

        if (ornament.startsWith('peB')) {
          layer = this._battle;
        }

        if (typeof (window.ornaments.iconUrls[ornament]) !== 'undefined') {
          opacity = 1;
          iconUrl = window.ornaments.iconUrls[ornament];
          anchor = [size / 2, size];
        }

        var exclude = false;
        if (this.excludedOrnaments && this.excludedOrnaments!= [""]) {
          exclude = this.excludedOrnaments.some( function(pattern) {
                return ornament.startsWith(pattern)
          });
        }
        debugger;
        exclude += window.ornaments.knownOrnaments[ornament];
        if (exclude){
//          opacity = 0;
          layer = this._excluded;
        }

        return L.marker(portal.getLatLng(), {
          icon: L.icon({
            iconUrl: iconUrl,
            iconSize: [size, size],
            iconAnchor: anchor, // https://github.com/IITC-CE/Leaflet.Canvas-Markers/issues/4
            className: 'no-pointer-events'
          }),
          interactive: false,
          keyboard: false,
          opacity: opacity,
          layer: layer,
        }).addTo(layer);

      }, this);
    }
  },

  removePortal: function (portal) {
    var guid = portal.options.guid;
    if (this._portals[guid]) {
//      console.log(window.ornaments._portals[guid], guid);
      this._portals[guid].forEach(function (marker) {
        marker.options.layer.removeLayer(marker);
      });
      delete this._portals[guid];
    }
  },

  load: function () {
    var dataStr;
    try {
      dataStr = localStorage['excludedOrnaments'];
      if (dataStr === undefined) return;
      this.excludedOrnaments = JSON.parse(dataStr);
    } catch(e) {
    console.warn('ornaments: failed to load data from localStorage: '+e);
    };
    try {
      dataStr = localStorage['knownOrnaments'];
      console.log(dataStr);
      if (dataStr === undefined) {
        this.knownOrnaments = {

// commented out for testing. esp. "bb_s" and "sc5_p" should be easy to find on the map.
// these Ornaments should be listed in the ornaments dialog.
/*           "ap1":false,
             "ap2":false,
             "ap3":false,
             "ap4":false,
             "ap5":false,
             "ap6":false,
             "ap7":false,
             "ap8":false,
             "ap9":false,
             "sc5_p":false,
             "bb_s":false,
             "peFRACK":false,
             "peNIA":false,
             "peNEMESIS":false,
             "peTOASTY":false,
             "peFW_ENL":false,
             "peFW_RES":false,
**********************/
             "peBB_BATTLE_RARE":false,
             "peBB_BATTLE":false,
             "peBN_BLM":false,
             "peBN_ENL_WINNER-60":false,
             "peBN_RES_WINNER-60":false,
             "peBN_TIED_WINNER-60":false,
             "peBR_REWARD-10_125_38":false,
             "peBR_REWARD-10_150_75":false,
             "peBR_REWARD-10_175_113":false,
             "peBR_REWARD-10_200_150":false,
             "peBR_REWARD-10_225_188":false,
             "peBR_REWARD-10_250_225":false,
             "peLOOK":false
        };
        this.save;
        return;
      }
      this.knownOrnaments = JSON.parse(dataStr);
    } catch(e) {
    console.warn('ornaments: failed to load data from localStorage: '+e);
    }

  },

  save: function () {
    localStorage['excludedOrnaments'] = JSON.stringify(this.excludedOrnaments);
    localStorage['knownOrnaments'] = JSON.stringify(this.knownOrnaments);
  },

  ornamentsOpt: function () {
    var eO = window.ornaments.excludedOrnaments.toString();
    var text ='';
    for (var name in window.ornaments.knownOrnaments) {
      var checked = window.ornaments.knownOrnaments[name] ?  ' checked' : '';
      text += '' + name + '<input type="checkbox" ' + checked + '>
';
    }
//    console.log (text);
    var html = '
'
             + 'Hide Ornaments from IITC that start with:
'
             + '<input type="text" value="'+eO +'" id="ornaments_E"></input>
'
             + '(separator: space or comma allowed)
'
             + 'known Ornaments, check to hide:
'
             + text
             + '
';

    dialog({
      html:html,
      id:'ornamentsOpt',
//      dialogClass:'ui-dialog-content',
      title:'Ornament excludes',
      buttons: {
        'OK': function() {
          // remove markers
          // todo
          // process the input from the input
          window.ornaments.excludedOrnaments = $("#ornaments_E").val().split(/[\s,]+/);
          window.ornaments.excludedOrnaments = window.ornaments.excludedOrnaments.filter(function (name) { return name !== ""; })
          console.log(window.ornaments.excludedOrnaments);
          // process the input from the checkboxes
          // todo
          window.ornaments.save();
          // reload markers
          // todo
          $(this).dialog('close');
        }
      }
    });
  }
};
<|endoftext|>


<commit_before>// apollo client imports need to remain in this file
// apollo client does not like to mingle with other imports
// apollo client has not told me why
// apollo client demands exclusivity?
// apollo client is probably fine and it's webpack
// webpack is evil
import ApolloClient, {
  createNetworkInterface,
  readQueryFromStore,
} from "apollo-client";
import { AuthTokenHeaderMiddleware } from "apollo-client/lib/src/middleware"
import { addReducer, addMiddleware } from "../store";
import { connect } from "react-apollo";
import { applyMiddleware } from "redux";
import { Tokens } from "../collections";

// let token = "basic ";
// if (Meteor.userId()) {
//   token += new Buffer(`apollos:${Meteor.settings.public.rock.token}`).toString("base64");
// } else {
//   token += new Buffer(`guest:guest`).toString("base64");
// }

let token = Accounts._storedLoginToken && Accounts._storedLoginToken() ? Accounts._storedLoginToken() : null;

const networkInterface = createNetworkInterface(
  Meteor.settings.public.heighliner, {
    headers: {
      Authorization: token,
    },
  }
);

const apolloClient = new ApolloClient({
  networkInterface,
});

addReducer({
  apollo: apolloClient.reducer(),
});
addMiddleware(apolloClient.middleware());

export {
  apolloClient,
  readQueryFromStore,
  connect,
};
<commit_msg>export gql<commit_after>// apollo client imports need to remain in this file
// apollo client does not like to mingle with other imports
// apollo client has not told me why
// apollo client demands exclusivity?
// apollo client is probably fine and it's webpack
// webpack is evil
import ApolloClient, {
  createNetworkInterface,
  readQueryFromStore,
} from "apollo-client";
import gql from "apollo-client/lib/src/gql";
import { AuthTokenHeaderMiddleware } from "apollo-client/lib/src/middleware"
import { addReducer, addMiddleware } from "../store";
import { connect } from "react-apollo";
import { applyMiddleware } from "redux";
import { Tokens } from "../collections";

// let token = "basic ";
// if (Meteor.userId()) {
//   token += new Buffer(`apollos:${Meteor.settings.public.rock.token}`).toString("base64");
// } else {
//   token += new Buffer(`guest:guest`).toString("base64");
// }

let token = Accounts._storedLoginToken && Accounts._storedLoginToken() ? Accounts._storedLoginToken() : null;

const networkInterface = createNetworkInterface(
  Meteor.settings.public.heighliner, {
    headers: {
      Authorization: token,
    },
  }
);

const apolloClient = new ApolloClient({
  networkInterface,
});

addReducer({
  apollo: apolloClient.reducer(),
});
addMiddleware(apolloClient.middleware());

export {
  apolloClient,
  readQueryFromStore,
  connect,
  gql,
};
<|endoftext|>


<commit_before>import React, { Component } from "react";

import Carousel from "components/carousel";
import ImageLoader from "components/image-loader";
import DeviceFrame from "components/device-frame";

export const data = {
  portfolio: true,
  order: 0,
  title: "Nomad",
  color: "blue",
  description: "Fixing one part of a broken health system.",
  path: "/portfolio/nomad",
  media: [
    {
      type: "image",
      src: "/images/nomad/computer-phone.png",
      placeholder: "/images/nomad/computer-phone-small.png",
      aspectRatio: 0.66666
    }
  ],
  styles: {
    background: "#fafafa"
  }
};

export default class Nomad extends Component {
  render() {
    const { title, styles } = data;

    return (

          <ImageLoader
            img="/images/nomad/computer-phone.png"
            aspectRatio={0.66666}
            styles={{
              maxWidth: 800,
              margin: "auto"
            }}
          />

{title}

                {
                  "Nomad is a healthcare marketplace for doctors and nurses, and the people that hire them."
                }

                {
                  "It’s a big industry, fraught with all the issues that plague the rest of the healthcare system in the United States: slow moving bureaucracy, inefficiencies galore, technology from 1988, and everything is way more expensive than it needs to be. We’re changing all that."
                }

                {
                  "We are building a healthcare marketplace that is easy to navigate, user-friendly, efficient, and most importantly:"
                }{" "}

                  {"much cheaper for everyone."}

{"It’s like it’s not 1988 anymore!"}

              <ImageLoader
                placeholder={"/images/nomad/hello-small.png"}
                aspectRatio={1}
                img={"/images/nomad/hello.png"}
              />

              <DeviceFrame>
                <ImageLoader
                  img="/images/nomad/dashboard@2x.png"
                  placeholder="/images/nomad/dashboard-small.png"
                  aspectRatio={2752 / 2880}
                />
              </DeviceFrame>

{"What Do I Do There?"}

{"A little bit of everything."}

                {
                  "I have a unique hybrid role at Nomad: I’m an Art Director and, at the same time, I am the Lead Product Engineer. I spent half of my time designing features, and the other half building them."
                }

            <DeviceFrame>
              <ImageLoader
                img="/images/nomad/find-a-job-few-results.png"
                placeholder="/images/nomad/find-a-job-few-results-small.png"
                aspectRatio={3066 / 2880}
              />
            </DeviceFrame>

Process

                Each project is different, some are refinements to existing
                features, others are brand-new greenfield iniatives. The process
                is therefore slightly modified for each one, but it typically
                follows the same steps.

Research

                Gain knowledge of users, context, technologies, gather user
                data, research competitive products, conduct interviews and
                field studies.

Explore

                Evaluate the simplest ways to communicate to solve the problem.
                Distill the goals and outcomes. Sketches, occasionally
                wireframes, and initial mockups of the overall flow and
                strategy.

Test

                Evaluate and select initial concepts for prototype development
                and usability testing.

Refine

                Incorporate feedback from stakeholders and users. Polish and
                animation explorations.

Deliver

Complete design and produce deliverables.

Implement

                Build the designs. Add a little extra to make it fun. Release as
                A/B tests or staggered rollouts.

Analyze

Evaluate results of tests and releases.

              <DeviceFrame className="load-in load-in--sequence-1">
                <ImageLoader
                  img="/images/nomad/find-a-job-wireframe-free-text.png"
                  aspectRatio={3066 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-2">
                <ImageLoader
                  img="/images/nomad/find-a-job-filters.png"
                  aspectRatio={3066 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-1">
                <ImageLoader
                  img="/images/nomad/find-a-job-wireframe-quick-view.png"
                  aspectRatio={2092 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-2">
                <ImageLoader
                  img="/images/nomad/find-a-job-quick-view.png"
                  aspectRatio={2092 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame
                type="phone--light"
                className="load-in load-in--sequence-1"
              >
                <ImageLoader
                  img="/images/nomad/find-a-job-wireframe-mobile-free-text.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

              <DeviceFrame
                type="phone--light"
                className="load-in load-in--sequence-2"
              >
                <ImageLoader
                  img="/images/nomad/find-a-job-mobile-filters-applied.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

            <DeviceFrame
              styles={{
                maxWidth: 800,
                margin: "auto"
              }}
            >
              <video className="video-player" autoPlay loop playsInline>
                <source src="/videos/nomad-logged-out.mp4" type="video/mp4" />
              </video>
            </DeviceFrame>

              <DeviceFrame type="phone" className="load-in load-in--sequence-1">
                <ImageLoader
                  img="/images/nomad/dashboard-mobile.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-3" type="phone">
                <ImageLoader
                  img="/images/nomad/public-facility-mobile.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in">
                <ImageLoader
                  img="/images/nomad/post-a-job@2x.png"
                  aspectRatio={1600 / 2880}
                />
              </DeviceFrame>

{"Design"}

UI / UX Design

Print / Marketing Material

Sketch

Adobe Illustrator

Adobe Photoshop

Abstract

Invision

Framer

{"Tech"}

React

Redux

Webpack 3

Python / Flask

SCSS

MongoDB

Backbone.js

              <DeviceFrame bodyStyles={{ maxHeight: 800 }}>
                <ImageLoader
                  placeholder="/images/nomad/public-profile-small.png"
                  img="/images/nomad/public-profile@2x.png"
                  aspectRatio={5418 / 2880}
                />
              </DeviceFrame>

    );
  }
}
<commit_msg>Quick fix<commit_after>import React, { Component } from "react";

import Carousel from "components/carousel";
import ImageLoader from "components/image-loader";
import DeviceFrame from "components/device-frame";

export const data = {
  portfolio: true,
  order: 0,
  title: "Nomad",
  color: "blue",
  description: "Fixing one part of a broken health system.",
  path: "/portfolio/nomad",
  media: [
    {
      type: "image",
      src: "/images/nomad/computer-phone.png",
      placeholder: "/images/nomad/computer-phone-small.png",
      aspectRatio: 0.66666
    }
  ],
  styles: {
    background: "#fafafa"
  }
};

export default class Nomad extends Component {
  render() {
    const { title, styles } = data;

    return (

          <ImageLoader
            img="/images/nomad/computer-phone.png"
            aspectRatio={0.66666}
            styles={{
              maxWidth: 800,
              margin: "auto"
            }}
          />

{title}

                {
                  "Nomad is a healthcare marketplace for doctors and nurses, and the people that hire them."
                }

                {
                  "It’s a big industry, fraught with all the issues that plague the rest of the healthcare system in the United States: slow moving bureaucracy, inefficiencies galore, technology from 1988, and everything is way more expensive than it needs to be. We’re changing all that."
                }

                {
                  "We are building a healthcare marketplace that is easy to navigate, user-friendly, efficient, and most importantly:"
                }{" "}

                  {"much cheaper for everyone."}

{"It’s like it’s not 1988 anymore!"}

              <ImageLoader
                placeholder={"/images/nomad/hello-small.png"}
                aspectRatio={1}
                img={"/images/nomad/hello.png"}
              />

              <DeviceFrame>
                <ImageLoader
                  img="/images/nomad/dashboard@2x.png"
                  placeholder="/images/nomad/dashboard-small.png"
                  aspectRatio={2752 / 2880}
                />
              </DeviceFrame>

{"What Do I Do There?"}

{"A little bit of everything."}

                {
                  "I have a unique hybrid role at Nomad: I’m an Art Director and, at the same time, I am the Lead Product Engineer. I spent half of my time designing features, and the other half building them."
                }

            <DeviceFrame>
              <ImageLoader
                img="/images/nomad/find-a-job-few-results.png"
                placeholder="/images/nomad/find-a-job-few-results-small.png"
                aspectRatio={3066 / 2880}
              />
            </DeviceFrame>

Process

                Each project is different, some are refinements to existing
                features, others are brand-new greenfield iniatives. The process
                is therefore slightly modified for each one, but it typically
                follows the same steps.

Research

                Gain knowledge of users, context, technologies, gather user
                data, research competitive products, conduct interviews and
                field studies.

Explore

                Evaluate the simplest ways to communicate to solve the problem.
                Distill the goals and outcomes. Sketches, occasionally
                wireframes, and initial mockups of the overall flow and
                strategy.

Test

                Evaluate and select initial concepts for prototype development
                and usability testing.

Refine

                Incorporate feedback from stakeholders and users. Polish and
                animation explorations.

Deliver

Complete design and produce deliverables.

Implement

                Build the designs. Add a little extra to make it fun. Release as
                A/B tests or staggered rollouts.

Analyze

Evaluate results of tests and releases.

              <DeviceFrame className="load-in load-in--sequence-1">
                <ImageLoader
                  img="/images/nomad/find-a-job-wireframe-free-text.png"
                  aspectRatio={3066 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-2">
                <ImageLoader
                  img="/images/nomad/find-a-job-filters.png"
                  aspectRatio={3066 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-1">
                <ImageLoader
                  img="/images/nomad/find-a-job-wireframe-quick-view.png"
                  aspectRatio={2092 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-2">
                <ImageLoader
                  img="/images/nomad/find-a-job-quick-view.png"
                  aspectRatio={2092 / 2880}
                />
              </DeviceFrame>

              <DeviceFrame
                type="phone--light"
                className="load-in load-in--sequence-1"
              >
                <ImageLoader
                  img="/images/nomad/find-a-job-wireframe-mobile-free-text.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

              <DeviceFrame
                type="phone--light"
                className="load-in load-in--sequence-2"
              >
                <ImageLoader
                  img="/images/nomad/find-a-job-mobile-filters-applied.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

            <DeviceFrame
              styles={{
                maxWidth: 720,
                margin: "auto"
              }}
            >
              <video className="video-player" autoPlay loop playsInline>
                <source src="/videos/nomad-logged-out.mp4" type="video/mp4" />
              </video>
            </DeviceFrame>

              <DeviceFrame type="phone" className="load-in load-in--sequence-1">
                <ImageLoader
                  img="/images/nomad/dashboard-mobile.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in load-in--sequence-3" type="phone">
                <ImageLoader
                  img="/images/nomad/public-facility-mobile.png"
                  aspectRatio={1334 / 750}
                />
              </DeviceFrame>

              <DeviceFrame className="load-in">
                <ImageLoader
                  img="/images/nomad/post-a-job@2x.png"
                  aspectRatio={1600 / 2880}
                />
              </DeviceFrame>

{"Design"}

UI / UX Design

Print / Marketing Material

Sketch

Adobe Illustrator

Adobe Photoshop

Abstract

Invision

Framer

{"Tech"}

React

Redux

Webpack 3

Python / Flask

SCSS

MongoDB

Backbone.js

              <DeviceFrame bodyStyles={{ maxHeight: 800 }}>
                <ImageLoader
                  placeholder="/images/nomad/public-profile-small.png"
                  img="/images/nomad/public-profile@2x.png"
                  aspectRatio={5418 / 2880}
                />
              </DeviceFrame>

    );
  }
}
<|endoftext|>


<commit_before>/**
 * Batch requests in self-contained context
 *
 * @author Wells Johnston <wells@littlstar.com>
 */

'use strict'

const Batch = require('batch');
const ProgressBar = require('progress');

/**
 * Self-contained batch request object, created by {@link S3renity#context}.
 * Once created, you can chain settings commands together before executing
 * a batch request.
 */

class BatchRequest {

  /**
   * Creates a new Context to perform batch operations with. You can
   * either supply an s3 path like s3://<bucket>/path/to/folder
   * or a bucket and prefix.
   *
   * @param {S3renity} s3 - The s3renity instance used for internal requests
   * @param {Promise} sources - The keys for the given context
   */

  constructor(s3, sources) {
    this.s3 = s3;
    this.show_progress = s3.show_progress;
    this.sources = sources;
    this.encoding = 'utf8';
    this._concurrency = Infinity;
  }

  /**
   * Retrieve the source keys being operated on
   *
   * @returns {Promise}
   */

  resolveSources() {
    let deferred = Promise.defer();
    this.sources.then(sources => {
      if (this._limit) {
        sources = sources.slice(0, this._limit);
      }
      deferred.resolve(sources);
    }).catch(e => deferred.reject(e));
    return deferred.promise;
  }

  /**
   * Sets the encoding to use when getting s3 objects with
   * object.Body.toString(encoding). If not set, utf8
   * is used.
   *
   * @param {String} encoding - The encoding
   * @returns {Context} this
   */

  encode(e) {
    this.encoding = e;
    return this;
  }

  /**
   * Sets a transformation function to be used when getting objects from s3.
   * Using transform takes precedence over encode.
   *
   * @param {Function} transformer - The function to use to transform the
   * object. The transforation function takes an s3 object as a parameter
   * and should return the file's contents as a string.
   * @returns {BatchRequest} this
   */

  transform(t) {
    this.transformer = t;
    return this;
  }

  /**
   * Set the concurrency for requests.  Default is Infinity (as many as
   * the computer can handle). Has no effect with reduce.
   *
   * @param {Integer} concurrency The concurrency level to use in the request.
   * @returns {BatchRequest}
   */

  concurrency(c) {
    this._concurrency = c;
    return this;
  }

  /**
   * Limits the number of sources being operated on
   */

  limit(l) {
    this._limit = l;
    return this;
  }

  /**
   * Sets the output directory for map or filter.  If a target is set, map and
   * filter write to that location instead of changing the original objects
   * themselves.
   *
   * @param {String} bucket - The target bucket.
   * @param {String} prefix - The target prefix (folder) where the output will go.
   * @return {BatchRequest} this
   */

  output(bucket, prefix) {
    this.target = {
      bucket: bucket,
      prefix: prefix
    };
    return this;
  }

  /**
   * Run a function over s3 objects in series. This is just a wrapper around each
   * with concurrency 1.
   *
   * @param {Function} func The function to perform over the working context
   * @param {Boolean} [isasync=false] Set to true if `func` is async (returns a
   * Promise).
   * @returns {Promise<string>} The last key iterated over.
   */

  forEach(func, isasync) {
    this.concurrency(1);
    return this.each(func, isasync);
  }

  /**
   * Run a function over s3 objects in parallel.
   *
   * @param {Function} func The function to perform over the working context
   * @param {Boolean} [isasync=false] Set to true if `func` is async (returns a
   * Promise).
   * @returns {Promise<string>} The last key iterated over.
   */

  each(func, isasync) {

    isasync = isasync || false;
    let deferred = Promise.defer();
    let batch = new Batch().concurrency(this._concurrency);

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('each [:bar] :percent', {
        total: sources.length,
        width: 40
      });
      let last = sources[sources.length - 1];

      /* create functions array */
      sources.forEach(source => {

        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(body => {
            if (isasync) {
              func(body, k).then(done).catch(done);
            } else {
              func(body, k);
              done();
            }
          })
          .catch(e => done(e));
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        } else {
          deferred.resolve(last);
        }
      });

    }).catch(e => {
      deferred.reject(e);
    });

    return deferred.promise;
  }

  /**
   * Maps a function over the objects in the working context in parallel, replaceing each
   * object with the return value.  If an output is specified, the objects will not be
   * overwritten, but rather copied to the target location.
   *
   * @public
   * @param {Function} func The function to map over each object in the working
   * context. func takes a string as a parameter and should return a
   * string that will replace the given s3 object.
   * @param {Boolean} [isasync=false] If set to true, this indicates that func is async and returns a promise.
   * @return {Promise}
   */

  map(func, isasync) {

    isasync = isasync || false;

    let self = this;
    let deferred = Promise.defer();
    let batch = new Batch;
    batch.concurrency(this._concurrency);

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('map [:bar] :percent', {
        total: sources.length,
        width: 40
      });
      let lastKey = sources[sources.length - 1];

      sources.forEach(source => {
        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(val => {
            if (isasync) {
              func(val, source.key).then(newval => {
                output(b, k, newval, done);
              }).catch(done)
            } else {
              let newval = func(val, source.key);
              output(b, k, newval, done);
            }
          })
          .catch(done);
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        }
        deferred.resolve(lastKey);
      });

    }).catch(e => {
      deferred.reject(e);
    });

    function output(bucket, key, body, done) {
      if (body == null) {
        throw new Error('mapper function must return a value');
      }
      if (self.target == null) {
        self.s3.put(bucket, key, body, self.encoding).then(() => {
          done();
        }).catch(done);
      } else {

        let b = self.target.bucket;
        let k = self.target.prefix + key;

        self.s3.put(b, k, body).then(() => {
          done();
        }).catch(e => {
          done(e);
        })
      }
    }

    return deferred.promise;
  }

  /**
   * Reduce the objects in the working context to a single value.
   *
   * @param {Function} func Function to execute on each value in the array, taking
   * three arguments:
   *   previousValue - The value previously returned in the last invocation of
   *   func
   *   currentValue  - The current entry being processed
   *   key           - The key of the current object being processed
   *   func either returns the updated value, or a promise that resolves to the
   *   updated value.
   * @param {String} value Optional.  Initial value to use as the first argument
   * @param {Boolean} isasync Optional, defaults to false. If set to true, this
   * indicates that func returns a promise.
   * @return {Promise} Returns the reduced result.
   */

  reduce(func, val, isasync) {

    isasync = isasync || false;
    let deferred = Promise.defer();
    let batch = new Batch;
    batch.concurrency(1);

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('reduce [:bar] :percent', {
        total: sources.length,
        width: 40
      });

      sources.forEach(source => {
        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(body => {
            if (isasync) {
              func(val, body, k).then(newval => {
                val = newval;
                done();
              }).catch(done);
            } else {
              val = func(val, body, k);
              done();
            }
          })
          .catch(e => done(e));
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        }
        deferred.resolve(val);
      });

    }).catch(e => {
      deferred.reject(e);
    });

    return deferred.promise;
  }

  /**
   * Filter the objects in the working context.
   *
   * @public
   * @param {Function} func The function to filter objects by, returning true for
   * objects that should not be filtered and false for those that should. If
   * isasync is set to true, func returns a promise that resolves to true or
   * false.
   * @param {Boolean} isasync Optional, defaults to false. If set to true, this
   * indicates that func returns a promise.
   */

  filter(func, isasync) {

    isasync = isasync || false;
    let self = this;
    let deferred = Promise.defer();
    let batch = new Batch;

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('filter [:bar] :percent', {
        total: sources.length,
        width: 40
      });

      /**
       * loop over every key and run the filter function on each object. keep
       * track of files to keep and remove.
       */
      sources.forEach(source => {

        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(body => {

              if (isasync) {
                func(body, source).then(result => {
                  check(result);
                  if (result) {
                    keep(source).then(_ => done()).catch(e => done(e));
                  } else {
                    remove(source).then(_ => done()).catch(e => done(e));
                  }
                }).catch(e => {
                  done(e);
                });
              } else {
                let result = null;
                result = func(body, source);
                check(result);
                if (result) {
                  keep(source).then(_ => done()).catch(e => done(e));
                } else {
                  remove(source).then(_ => done()).catch(e => done(e));
                }
              }
            })
            .catch(e => done(e));
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        } else {
          deferred.resolve();
        }
      });
    }).catch(e => {
      deferred.reject(e);
    });

    function keep(source) {
      let deferred = Promise.defer();
      if (self.target != null) {
        let b = source.bucket;
        let k = source.key;
        let tb = self.target.bucket;
        let tk = self.target.prefix + source.file;
        self.s3.copy(b, k, tb, tk).then(() => {
          deferred.resolve();
        }).catch(e => {
          deferred.reject(e);
        });
      } else {
        deferred.resolve();
      }
      return deferred.promise;
    }

    function remove(source) {
      let deferred = Promise.defer();
      if (self.target == null) {
        self.s3.delete(source.bucket, source.key).then(() => {
          deferred.resolve();
        }).catch(e => {
          deferred.reject(e);
        });
      } else {
        deferred.resolve();
      }
      return deferred.promise;
    }

    function check(result) {
      if (typeof result != 'boolean') {
        throw new TypeError('filter function must return a boolean');
      }
    }

    return deferred.promise;
  }

  /**
   * Join s3 objects together like Array.prototype.join
   *
   * @param {String} delimiter Delimiter to join objects by.
   * @return {String} the joined objects.
   */

  join(delimiter) {

    let deferred = Promise.defer();

    this.resolveSources().then(sources => {

      let batch = new Batch;

      batch.push(done => {
        this.s3.get(source.bucket, source.key)
          .then(data => done(null, data))
          .catch(e => done(e));
      });

      if (this.show_progress) {
        batch.on('progress', status => {
          console.info(`${status.percent}%`);
        });
      }

      batch.end((err, data) => {
        if (err) {
          deferred.reject(err);
        } else {
          deferred.resolve(data.join(delimiter));
        }
      });

    });

    return deferred.promise;
  }
}

module.exports = BatchRequest;
<commit_msg>better error handling<commit_after>/**
 * Batch requests in self-contained context
 *
 * @author Wells Johnston <wells@littlstar.com>
 */

'use strict'

const Batch = require('batch');
const ProgressBar = require('progress');
const UserFunctionError = new Error('supplied function failed');

/**
 * Self-contained batch request object, created by {@link S3renity#context}.
 * Once created, you can chain settings commands together before executing
 * a batch request.
 */

class BatchRequest {

  /**
   * Creates a new Context to perform batch operations with. You can
   * either supply an s3 path like s3://<bucket>/path/to/folder
   * or a bucket and prefix.
   *
   * @param {S3renity} s3 - The s3renity instance used for internal requests
   * @param {Promise} sources - The keys for the given context
   */

  constructor(s3, sources) {
    this.s3 = s3;
    this.show_progress = s3.show_progress;
    this.sources = sources;
    this.encoding = 'utf8';
    this._concurrency = Infinity;
  }

  /**
   * Retrieve the source keys being operated on
   *
   * @returns {Promise}
   */

  resolveSources() {
    let deferred = Promise.defer();
    this.sources.then(sources => {
      if (this._limit) {
        sources = sources.slice(0, this._limit);
      }
      deferred.resolve(sources);
    }).catch(e => deferred.reject(e));
    return deferred.promise;
  }

  /**
   * Sets the encoding to use when getting s3 objects with
   * object.Body.toString(encoding). If not set, utf8
   * is used.
   *
   * @param {String} encoding - The encoding
   * @returns {Context} this
   */

  encode(e) {
    this.encoding = e;
    return this;
  }

  /**
   * Sets a transformation function to be used when getting objects from s3.
   * Using transform takes precedence over encode.
   *
   * @param {Function} transformer - The function to use to transform the
   * object. The transforation function takes an s3 object as a parameter
   * and should return the file's contents as a string.
   * @returns {BatchRequest} this
   */

  transform(t) {
    this.transformer = t;
    return this;
  }

  /**
   * Set the concurrency for requests.  Default is Infinity (as many as
   * the computer can handle). Has no effect with reduce.
   *
   * @param {Integer} concurrency The concurrency level to use in the request.
   * @returns {BatchRequest}
   */

  concurrency(c) {
    this._concurrency = c;
    return this;
  }

  /**
   * Limits the number of sources being operated on
   */

  limit(l) {
    this._limit = l;
    return this;
  }

  /**
   * Sets the output directory for map or filter.  If a target is set, map and
   * filter write to that location instead of changing the original objects
   * themselves.
   *
   * @param {String} bucket - The target bucket.
   * @param {String} prefix - The target prefix (folder) where the output will go.
   * @return {BatchRequest} this
   */

  output(bucket, prefix) {
    this.target = {
      bucket: bucket,
      prefix: prefix
    };
    return this;
  }

  /**
   * Run a function over s3 objects in series. This is just a wrapper around each
   * with concurrency 1.
   *
   * @param {Function} func The function to perform over the working context
   * @param {Boolean} [isasync=false] Set to true if `func` is async (returns a
   * Promise).
   * @returns {Promise<string>} The last key iterated over.
   */

  forEach(func, isasync) {
    this.concurrency(1);
    return this.each(func, isasync);
  }

  /**
   * Run a function over s3 objects in parallel.
   *
   * @param {Function} func The function to perform over the working context
   * @param {Boolean} [isasync=false] Set to true if `func` is async (returns a
   * Promise).
   * @returns {Promise<string>} The last key iterated over.
   */

  each(func, isasync) {

    isasync = isasync || false;
    let deferred = Promise.defer();
    let batch = new Batch().concurrency(this._concurrency);

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('each [:bar] :percent', {
        total: sources.length,
        width: 40
      });
      let last = sources[sources.length - 1];

      /* create functions array */
      sources.forEach(source => {

        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(body => {
            try {
              if (isasync) {
                func(body, k).then(done).catch(done);
              } else {
                func(body, k);
                done();
              }
            } catch (e) {
              done(UserFunctionError);
            }
          })
          .catch(e => done(e));
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        } else {
          deferred.resolve(last);
        }
      });

    }).catch(e => {
      deferred.reject(e);
    });

    return deferred.promise;
  }

  /**
   * Maps a function over the objects in the working context in parallel, replaceing each
   * object with the return value.  If an output is specified, the objects will not be
   * overwritten, but rather copied to the target location.
   *
   * @public
   * @param {Function} func The function to map over each object in the working
   * context. func takes a string as a parameter and should return a
   * string that will replace the given s3 object.
   * @param {Boolean} [isasync=false] If set to true, this indicates that func is async and returns a promise.
   * @return {Promise}
   */

  map(func, isasync) {

    isasync = isasync || false;

    let self = this;
    let deferred = Promise.defer();
    let batch = new Batch;
    batch.concurrency(this._concurrency);

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('map [:bar] :percent', {
        total: sources.length,
        width: 40
      });
      let lastKey = sources[sources.length - 1];

      sources.forEach(source => {
        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(val => {
            try {
              if (isasync) {
                func(val, source.key).then(newval => {
                  output(b, k, newval, done);
                }).catch(done)
              } else {
                let newval = func(val, source.key);
                output(b, k, newval, done);
              }
            } catch (e) {
              done(UserFunctionError);
            }
          })
          .catch(done);
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        }
        deferred.resolve(lastKey);
      });

    }).catch(e => {
      deferred.reject(e);
    });

    function output(bucket, key, body, done) {
      if (body == null) {
        throw new Error('mapper function must return a value');
      }
      if (self.target == null) {
        self.s3.put(bucket, key, body, self.encoding).then(() => {
          done();
        }).catch(done);
      } else {

        let b = self.target.bucket;
        let k = self.target.prefix + key;

        self.s3.put(b, k, body).then(() => {
          done();
        }).catch(e => {
          done(e);
        })
      }
    }

    return deferred.promise;
  }

  /**
   * Reduce the objects in the working context to a single value.
   *
   * @param {Function} func Function to execute on each value in the array, taking
   * three arguments:
   *   previousValue - The value previously returned in the last invocation of
   *   func
   *   currentValue  - The current entry being processed
   *   key           - The key of the current object being processed
   *   func either returns the updated value, or a promise that resolves to the
   *   updated value.
   * @param {String} value Optional.  Initial value to use as the first argument
   * @param {Boolean} isasync Optional, defaults to false. If set to true, this
   * indicates that func returns a promise.
   * @return {Promise} Returns the reduced result.
   */

  reduce(func, val, isasync) {

    isasync = isasync || false;
    let deferred = Promise.defer();
    let batch = new Batch;
    batch.concurrency(1);

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('reduce [:bar] :percent', {
        total: sources.length,
        width: 40
      });

      sources.forEach(source => {
        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(body => {
            try {
              if (isasync) {
                func(val, body, k).then(newval => {
                  val = newval;
                  done();
                }).catch(done);
              } else {
                val = func(val, body, k);
                done();
              }
            } catch (e) {
              done(UserFunctionError);
            }
          })
          .catch(e => done(e));
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        }
        deferred.resolve(val);
      });

    }).catch(e => {
      deferred.reject(e);
    });

    return deferred.promise;
  }

  /**
   * Filter the objects in the working context.
   *
   * @public
   * @param {Function} func The function to filter objects by, returning true for
   * objects that should not be filtered and false for those that should. If
   * isasync is set to true, func returns a promise that resolves to true or
   * false.
   * @param {Boolean} isasync Optional, defaults to false. If set to true, this
   * indicates that func returns a promise.
   */

  filter(func, isasync) {

    isasync = isasync || false;
    let self = this;
    let deferred = Promise.defer();
    let batch = new Batch;

    this.resolveSources().then(sources => {

      let progress = new ProgressBar('filter [:bar] :percent', {
        total: sources.length,
        width: 40
      });

      /**
       * loop over every key and run the filter function on each object. keep
       * track of files to keep and remove.
       */
      sources.forEach(source => {

        batch.push(done => {

          let b = source.bucket;
          let k = source.key;
          let e = this.encoding;
          let t = this.transformer;

          this.s3.get(b, k, e, t).then(body => {
              if (isasync) {
                try {
                  func(body, source).then(result => {
                    check(result);
                    if (result) {
                      keep(source).then(_ => done()).catch(e => done(e));
                    } else {
                      remove(source).then(_ => done()).catch(e => done(e));
                    }
                  }).catch(e => {
                    done(e);
                  });
                } catch (e) {
                  done(UserFunctionError);
                }
              } else {
                let result = null;
                result = func(body, source);
                check(result);
                if (result) {
                  keep(source).then(_ => done()).catch(e => done(e));
                } else {
                  remove(source).then(_ => done()).catch(e => done(e));
                }
              }
            })
            .catch(e => done(e));
        });
      });

      if (this.show_progress) {
        batch.on('progress', _ => progress.tick());
      }

      batch.end(err => {
        if (err) {
          deferred.reject(err);
        } else {
          deferred.resolve();
        }
      });
    }).catch(e => {
      deferred.reject(e);
    });

    function keep(source) {
      let deferred = Promise.defer();
      if (self.target != null) {
        let b = source.bucket;
        let k = source.key;
        let tb = self.target.bucket;
        let tk = self.target.prefix + source.file;
        self.s3.copy(b, k, tb, tk).then(() => {
          deferred.resolve();
        }).catch(e => {
          deferred.reject(e);
        });
      } else {
        deferred.resolve();
      }
      return deferred.promise;
    }

    function remove(source) {
      let deferred = Promise.defer();
      if (self.target == null) {
        self.s3.delete(source.bucket, source.key).then(() => {
          deferred.resolve();
        }).catch(e => {
          deferred.reject(e);
        });
      } else {
        deferred.resolve();
      }
      return deferred.promise;
    }

    function check(result) {
      if (typeof result != 'boolean') {
        throw new TypeError('filter function must return a boolean');
      }
    }

    return deferred.promise;
  }

  /**
   * Join s3 objects together like Array.prototype.join
   *
   * @param {String} delimiter Delimiter to join objects by.
   * @return {String} the joined objects.
   */

  join(delimiter) {

    let deferred = Promise.defer();

    this.resolveSources().then(sources => {

      let batch = new Batch;

      batch.push(done => {
        this.s3.get(source.bucket, source.key)
          .then(data => done(null, data))
          .catch(e => done(e));
      });

      if (this.show_progress) {
        batch.on('progress', status => {
          console.info(`${status.percent}%`);
        });
      }

      batch.end((err, data) => {
        if (err) {
          deferred.reject(err);
        } else {
          deferred.resolve(data.join(delimiter));
        }
      });

    });

    return deferred.promise;
  }
}

module.exports = BatchRequest;
<|endoftext|>


<commit_before>import extend from "../../helpers/extend.js";
import recursiveInheritance from "../../helpers/recursive-inheritance.js";

function factory(options) {
  options = extend({
    constructor: function () {},
    publics: {},
    statics: {}
  }, options);

  function Factory(...args) {
    this.__classes = {};
    options.constructor.apply(this, args);
  }
  Factory.prototype = extend(
    Object.create(options.constructor),
    {
      constructor: Factory,

      /**
       * @param {String} className
       * @param {Class} __class
       */
      add: function(className, __class) {
        if(this.__classes[className] !== undefined) {
          throw new Error("class is already defined in factory");
        }
        this.__classes[className] = __class;
        return this;
      },

      /**
       * @param  {String} className
       * @param  {Array} args
       * @return {Object}
       */
      create(className, ...args){
        if(this.__classes[className] === undefined) {
          throw new Error("class is not defined in factory");
        }
        return new this.__classes[className](...args);
      }
    },
    options.publics
  );

  return {
    constructor: Factory,
    publics: Factory.prototype,
    statics: options.statics,
    __class: function () {
      return extend(Factory, options.statics);
    }
  };
}

// /**
//  * A Factory Pattern: makes an instance of several
//  * derived classes based on interfaced data or events.
//  * @method
//  * @param  {object} commonFunctionality which will be inherited from the classes
//  * @return {Factory} factory instance
//  */
// function factory(__superclass) {
//   /**
//    * @class
//    */
//   class Factory {
//     /**
//      * @param  {Object} commonFunctionality
//      */
//     constructor (...args) {
//       this.__superclassArgs = args;
//       this.__classes = {};
//     }

//     /**
//      * @param {String} className
//      * @param {Class} __class
//      */
//     add(className, __class) {
//       if(this.__classes[className] !== undefined) {
//         throw new Error("class is already defined in factory");
//       }

//       // __class.prototype = extend(
//       //   Object.create(__superclass.prototype),
//       //   { constructor: __class },
//       //   __class.prototype
//       // );

//       // this.__classes[className] = (...args) => {
//       //   var instance = Object.create(__class.prototype);
//       //   __class.apply(instance, args);
//       //   __superclass.apply(instance, supperArgs);
//       //   return instance;
//       // };

//       this.__classes[className] = recursiveInheritance(__class, __superclass);
//       return this;
//     }

//     /**
//      * @param  {String} className
//      * @param  {Array} args
//      * @return {Object}
//      */
//     create(className, ...args){
//       if(this.__classes[className] === undefined) {
//         throw new Error("class is not defined in factory");
//       }

//       // class is dynamic instance of RecursiveInheritance.
//       return new this.__classes[className](args, this.__superclassArgs);
//     }
//   }

//   return Factory;
// }

export default factory;<commit_msg>cleaned factory<commit_after>import extend from "../../helpers/extend.js";
import recursiveInheritance from "../../helpers/recursive-inheritance.js";

/**
 * A Factory Pattern: makes an instance of several
 * derived classes based on interfaced data or events.
 * @method
 * @param  {object} commonFunctionality which will be inherited from the classes
 * @return {Factory} factory instance
 */
function factory(options) {
  options = extend({
    constructor: function () {},
    publics: {},
    statics: {}
  }, options);

  function Factory(...args) {
    this.__classes = {};
    options.constructor.apply(this, args);
  }
  Factory.prototype = extend(
    Object.create(options.constructor),
    {
      constructor: Factory,

      /**
       * @param {String} className
       * @param {Class} __class
       */
      add: function(className, __class) {
        if(this.__classes[className] !== undefined) {
          throw new Error("class is already defined in factory");
        }
        this.__classes[className] = __class;
        return this;
      },

      /**
       * @param  {String} className
       * @param  {Array} args
       * @return {Object}
       */
      create(className, ...args){
        if(this.__classes[className] === undefined) {
          throw new Error("class is not defined in factory");
        }
        return new this.__classes[className](...args);
      }
    },
    options.publics
  );

  return {
    constructor: Factory,
    publics: Factory.prototype,
    statics: options.statics,
    __class: function () {
      return extend(Factory, options.statics);
    }
  };
}

export default factory;<|endoftext|>