<commit_before>import {api, LightningElement, track, wire} from 'lwc';
import {debouncify, isNotEmpty, relatedRecordFieldNameFor, UtilDescribe, nonePicklistOption } from 'c/utilCommon';
import GeFormService from 'c/geFormService';
import GeLabelService from 'c/geLabelService';
import {getObjectInfo, getPicklistValues} from 'lightning/uiObjectInfoApi';
import DONATION_RECORD_TYPE_NAME
    from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';
import ACCOUNT1_IMPORTED
    from '@salesforce/schema/DataImport__c.Account1Imported__c';
import CONTACT1_IMPORTED
    from '@salesforce/schema/DataImport__c.Contact1Imported__c';
import ACCOUNT_ID from '@salesforce/schema/Opportunity.AccountId';
import PRIMARY_CONTACT from '@salesforce/schema/Opportunity.Primary_Contact__c';
import DATA_IMPORT from '@salesforce/schema/DataImport__c';
import RECORD_TYPE_FIELD from '@salesforce/schema/Opportunity.RecordTypeId';
import OPPORTUNITY from '@salesforce/schema/Opportunity';

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';
const DATE = 'date';
const DATETIME = 'datetime-local';
const CHECKBOX = 'checkbox';

export default class GeFormField extends LightningElement {
    @track _formState;
    @track objectDescribeInfo;
    @track _disabled = false;
    @api element;
    @api targetFieldName;
    _recordTypeId;
    _picklistValues;
    _isRichTextValid = true;

    richTextFormats = RICH_TEXT_FORMATS;
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    get value() {
        return this.valueFromFormState;
    }

    PICKLIST_OPTION_NONE = nonePicklistOption();

    handleValueChangeSync = (event) => {
        this.fireFormFieldChangeEvent(
            this.getValueFromChangeEvent(event));
    };
    handleValueChange = debouncify(this.handleValueChangeSync.bind(this), DELAY);

    /**
     * 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;
            this.objectDescribeUtil = new UtilDescribe(response.data);
            this._recordTypeId = this.recordTypeId();
        }
    }

    getValueFromChangeEvent(event) {
        if (this.isPicklist) {
            const value = event.detail.value;
            const isSelectedValueNone =
                value === this.CUSTOM_LABELS.commonLabelNone;
            if (isSelectedValueNone) {
                return null;
            }
        }

        if (this.isCheckbox && !this.isPicklist) {
            return event.detail.checked.toString();
        }

        if (this.isRichText) {
            return event.target.value;
        }

        if (this.isLookup) {
            const val = event.detail.value;
            if (typeof val === 'string') {
                return val;
            } else if (Array.isArray(val)) {
                return val[0] ? val[0] : null;
            }
        }
        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)
                && this.value !== this.CUSTOM_LABELS.commonLabelNone
                && 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.inputField();
        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._isRichTextValid) {
                // workaround, field will not display as invalid if it is untouched
                inputField.focus();
                inputField.blur();
            }
            return this._isRichTextValid;
        }
        return true;
    }

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

    inputField() {
        return this.template.querySelector('[data-id="inputComponent"]');
    }

    get isRichTextValid() {
        return this._isRichTextValid;
    }

    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.fieldMapping && this.fieldMapping.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 fieldMapping() {
        return isNotEmpty(this.targetFieldName) ?
            GeFormService.getFieldMappingWrapperFromTarget(this.targetFieldName) :
            GeFormService.getFieldMappingWrapper(this.formElementName);
    }

    get targetFieldDescribeInfo() {
        if (this.objectDescribeUtil) {
            return this.objectDescribeInfo.getFieldDescribe(this.targetFieldApiName);
        }
    }

    get objectMapping() {
        return GeFormService.getObjectMapping(this.targetObjectMappingDevName);
    }

    get fieldType() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Target_Field_Data_Type;
        }
    }

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

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

    /**
     * Special handling for RecordTypeId because we render these fields as picklists instead of lookups.
     */
    get isRecordTypePicklist() {
        return this.fieldType === LOOKUP_TYPE && this.targetFieldApiName === RECORD_TYPE_FIELD.fieldApiName;
    }

    @api
    get isLookup() {
        return this.fieldType === LOOKUP_TYPE && this.targetFieldApiName !== RECORD_TYPE_FIELD.fieldApiName;
    }

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

    get isCheckbox() {
        return this.fieldType === BOOLEAN_TYPE;
    }

    get booleanValue() {
        return !!this.value;
    }

    get hasPicklistOverride() {
        return isNotEmpty(this.element.picklistOptionsOverride);
    }

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

    @api
    get targetObjectMappingDevName() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Target_Object_Mapping_Dev_Name;
        }
    }

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

    @api
    get targetFieldApiName() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Target_Field_API_Name;
        }
    }

    @api
    get sourceFieldAPIName() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Source_Field_API_Name;
        }
    }

    // when using lightning-lookup-field, instead of binding to the Data Import fields or donor information
    // we bind to Opportunity.AccountId / Opportunity.Primary_Contact__c
    get lookupFieldApiName() {
        if (this.objectApiName === DATA_IMPORT.objectApiName) {
            if (this.targetFieldApiName === ACCOUNT1_IMPORTED.fieldApiName) {
                return ACCOUNT_ID.fieldApiName;
            } else if (this.targetFieldApiName === CONTACT1_IMPORTED.fieldApiName) {
                return PRIMARY_CONTACT.fieldApiName;
            }
        }
        return this.targetFieldApiName;
    }

    get lookupObjectApiName() {
        if (this.objectApiName === DATA_IMPORT.objectApiName) {
            if (this.targetFieldApiName === ACCOUNT1_IMPORTED.fieldApiName
                || this.targetFieldApiName === CONTACT1_IMPORTED.fieldApiName) {
                return OPPORTUNITY.objectApiName;
            }
        }

        return this.objectApiName;
    }

    @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
    get fieldValueAndFieldApiName() {
        let fieldWrapper = { value: this.value, apiName: this.targetFieldApiName };
        let returnMap = {};
        returnMap[ this.targetFieldApiName ] = fieldWrapper;

        return returnMap;
    }

    @api
    setCustomValidity(errorMessage) {
        const inputField = this.inputField();

        const canSetCustomValidity =
            inputField &&
            typeof inputField.reportValidity === 'function' &&
            typeof inputField.checkValidity === 'function';
        if (canSetCustomValidity) {
            inputField.setCustomValidity(errorMessage);
            inputField.reportValidity();
        }
    }

    @api
    clearCustomValidity() {

        if (!this.isLookup) {
            this.setCustomValidity('');
        }

    }

    get qaLocatorBase() {
        const rowIndex = this.getAttribute('data-qa-row');
        if (rowIndex) {
            return `${this.fieldLabel} ${rowIndex}`;
        } else {
            return this.fieldLabel;
        }
    }

    get qaLocatorInputPrefix() {
        switch (this.inputType) {
            case DATE:
            case DATETIME:
                return 'datetime';
            case CHECKBOX:
                return this.inputType;
            default:
                return 'input';
        }
    }

    get qaLocatorInput() {
        return `${this.qaLocatorInputPrefix} ${this.qaLocatorBase}`;
    }

    get qaLocatorLookup() {
        return `autocomplete ${this.qaLocatorBase}`;
    }

    get qaLocatorRichText() {
        return `richtext ${this.qaLocatorBase}`;
    }

    get qaLocatorTextArea() {
        return `textarea ${this.qaLocatorBase}`;
    }

    @api
    get formState() {
        return this._formState;
    }

    set formState(formState) {
        this._formState = formState;
        this._recordTypeId = this.recordTypeId();
    }

    get valueFromFormState() {
        const value = this.formState[this.sourceFieldAPIName];

        if (value === undefined) {
           return null;
        }

        const isDonationRecordTypeName =
            this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName;
        if (isDonationRecordTypeName) {
            return this.recordTypeIdFor(value);
        }

        if (this.isPicklist && value === null) {
            const hasNoneOptionAvailable =
                this.isValueInOptions(
                    this.CUSTOM_LABELS.commonLabelNone,
                    this.picklistValues
                );
            return hasNoneOptionAvailable ? this.CUSTOM_LABELS.commonLabelNone : '';
        }

        return value;
    }

    fireFormFieldChangeEvent(value) {
        const formFieldChangeEvent = new CustomEvent('formfieldchange', {
            detail:
                {
                    value: value,
                    label: this.isRecordTypePicklist ? this.recordTypeNameFor(value) : value,
                    fieldMappingDevName: this.fieldMappingDevName()
                }
        });
        this.dispatchEvent(formFieldChangeEvent);
    }

    fieldMappingDevName() {
        return this.element.dataImportFieldMappingDevNames[0];
    }

    siblingRecordTypeValue() {
        return this.formState && this.formState[this.siblingRecordTypeField()];
    }

    siblingRecordTypeField() {
        return this.element && this.element.siblingRecordTypeField;
    }

    parentRecordRecordTypeId() {
        return this.parentRecord() &&
            this.parentRecord().recordTypeId;
    }

    parentRecord() {
        return this.formState && this.formState[relatedRecordFieldNameFor(this.parentRecordField())];
    }

    parentRecordField() {
        return this.element &&
            this.element.parentRecordField;
    }

    get picklistValues() {
        if (this.element.picklistOptionsOverride) {
            return this.element.picklistOptionsOverride;
        }
        if (this.targetFieldApiName === RECORD_TYPE_FIELD.fieldApiName && this.objectDescribeUtil) {
            return this.objectDescribeUtil.getPicklistOptionsForRecordTypeIds();
        }
        return this._picklistValues;
    }

    set picklistValues(values) {
        this._picklistValues = values;
    }

    get qaLocatorPicklist() {
        return `combobox ${this.qaLocatorBase}`;
    }

    /**
     * Accessor method returns the full object and field api name.
     *
     * Returns null if the current field is a RecordTypeId so that we
     * don't invoke the wired `wiredPicklistValues` function. Instead
     * we retrieve record types objects from `_objectDescribeInfo`
     * and build the picklist options array from there.
     */
    get fullFieldApiNameForStandardPicklists() {
        if (this.isStandardPicklist()) {
            return `${this.objectApiName}.${this.targetFieldApiName}`;
        }
    }

    isStandardPicklist() {
        return this.isPicklist && !this.isRecordTypeIdLookup && !this.hasPicklistOverride;
    }

    get isRecordTypeIdLookup() {
        return this.targetFieldApiName === RECORD_TYPE_FIELD.fieldApiName;
    }

    @wire(getPicklistValues, {
        fieldApiName: '$fullFieldApiNameForStandardPicklists',
        recordTypeId: '$_recordTypeId'
    })
    wiredPicklistValues({error, data}) {
        if (data) {
            this.picklistValues = [this.PICKLIST_OPTION_NONE, ...data.values];
        }
        if (error) {
            console.error(error);
        }
    }

    isValueInOptions(value, options) {
        if (!options || options.length === 0) return false;
        return options.some(option => {
            return option.value === value;
        });
    }

}
<commit_msg>Re-add removed code w/ refactor<commit_after>import {api, LightningElement, track, wire} from 'lwc';
import {debouncify, isNotEmpty, relatedRecordFieldNameFor, UtilDescribe, nonePicklistOption } from 'c/utilCommon';
import GeFormService from 'c/geFormService';
import GeLabelService from 'c/geLabelService';
import {getObjectInfo, getPicklistValues} from 'lightning/uiObjectInfoApi';
import DONATION_RECORD_TYPE_NAME
    from '@salesforce/schema/DataImport__c.Donation_Record_Type_Name__c';
import ACCOUNT1_IMPORTED
    from '@salesforce/schema/DataImport__c.Account1Imported__c';
import CONTACT1_IMPORTED
    from '@salesforce/schema/DataImport__c.Contact1Imported__c';
import ACCOUNT_ID from '@salesforce/schema/Opportunity.AccountId';
import PRIMARY_CONTACT from '@salesforce/schema/Opportunity.Primary_Contact__c';
import DATA_IMPORT from '@salesforce/schema/DataImport__c';
import RECORD_TYPE_FIELD from '@salesforce/schema/Opportunity.RecordTypeId';
import OPPORTUNITY from '@salesforce/schema/Opportunity';

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';
const DATE = 'date';
const DATETIME = 'datetime-local';
const CHECKBOX = 'checkbox';

export default class GeFormField extends LightningElement {
    @track _formState;
    @track objectDescribeInfo;
    @track _disabled = false;
    @api element;
    @api targetFieldName;
    _recordTypeId;
    _picklistValues;
    _isRichTextValid = true;

    richTextFormats = RICH_TEXT_FORMATS;
    CUSTOM_LABELS = GeLabelService.CUSTOM_LABELS;

    get value() {
        return this.valueFromFormState;
    }

    PICKLIST_OPTION_NONE = nonePicklistOption();

    handleValueChangeSync = (event) => {
        this.fireFormFieldChangeEvent(
            this.getValueFromChangeEvent(event));
    };
    handleValueChange = debouncify(this.handleValueChangeSync.bind(this), DELAY);

    /**
     * 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;
            this.objectDescribeUtil = new UtilDescribe(response.data);
            this._recordTypeId = this.recordTypeId();
        }
    }

    getValueFromChangeEvent(event) {
        if (this.isPicklist) {
            const value = event.detail.value;
            const isSelectedValueNone =
                value === this.CUSTOM_LABELS.commonLabelNone;
            if (isSelectedValueNone) {
                return null;
            }
        }

        if (this.isCheckbox && !this.isPicklist) {
            return event.detail.checked.toString();
        }

        if (this.isRichText) {
            return event.target.value;
        }

        if (this.isLookup) {
            const val = event.detail.value;
            if (typeof val === 'string') {
                return val;
            } else if (Array.isArray(val)) {
                return val[0] ? val[0] : null;
            }
        }
        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)
                && this.value !== this.CUSTOM_LABELS.commonLabelNone
                && 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.inputField();
        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._isRichTextValid) {
                // workaround, field will not display as invalid if it is untouched
                inputField.focus();
                inputField.blur();
            }
            return this._isRichTextValid;
        }
        return true;
    }

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

    inputField() {
        return this.template.querySelector('[data-id="inputComponent"]');
    }

    get isRichTextValid() {
        return this._isRichTextValid;
    }

    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.fieldMapping && this.fieldMapping.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 fieldMapping() {
        return isNotEmpty(this.targetFieldName) ?
            GeFormService.getFieldMappingWrapperFromTarget(this.targetFieldName) :
            GeFormService.getFieldMappingWrapper(this.formElementName);
    }

    get targetFieldDescribeInfo() {
        if (this.objectDescribeUtil) {
            return this.objectDescribeInfo.getFieldDescribe(this.targetFieldApiName);
        }
    }

    get objectMapping() {
        return GeFormService.getObjectMapping(this.targetObjectMappingDevName);
    }

    get fieldType() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Target_Field_Data_Type;
        }
    }

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

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

    /**
     * Special handling for RecordTypeId because we render these fields as picklists instead of lookups.
     */
    get isRecordTypePicklist() {
        return this.fieldType === LOOKUP_TYPE && this.targetFieldApiName === RECORD_TYPE_FIELD.fieldApiName;
    }

    @api
    get isLookup() {
        return this.fieldType === LOOKUP_TYPE && this.targetFieldApiName !== RECORD_TYPE_FIELD.fieldApiName;
    }

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

    get isCheckbox() {
        return this.fieldType === BOOLEAN_TYPE;
    }

    get booleanValue() {
        return !!this.value;
    }

    get hasPicklistOverride() {
        return isNotEmpty(this.element.picklistOptionsOverride);
    }

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

    @api
    get targetObjectMappingDevName() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Target_Object_Mapping_Dev_Name;
        }
    }

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

    @api
    get targetFieldApiName() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Target_Field_API_Name;
        }
    }

    @api
    get sourceFieldAPIName() {
        if (isNotEmpty(this.fieldMapping)) {
            return this.fieldMapping.Source_Field_API_Name;
        }
    }

    // when using lightning-lookup-field, instead of binding to the Data Import fields or donor information
    // we bind to Opportunity.AccountId / Opportunity.Primary_Contact__c
    get lookupFieldApiName() {
        if (this.objectApiName === DATA_IMPORT.objectApiName) {
            if (this.targetFieldApiName === ACCOUNT1_IMPORTED.fieldApiName) {
                return ACCOUNT_ID.fieldApiName;
            } else if (this.targetFieldApiName === CONTACT1_IMPORTED.fieldApiName) {
                return PRIMARY_CONTACT.fieldApiName;
            }
        }
        return this.targetFieldApiName;
    }

    get lookupObjectApiName() {
        if (this.objectApiName === DATA_IMPORT.objectApiName) {
            if (this.targetFieldApiName === ACCOUNT1_IMPORTED.fieldApiName
                || this.targetFieldApiName === CONTACT1_IMPORTED.fieldApiName) {
                return OPPORTUNITY.objectApiName;
            }
        }

        return this.objectApiName;
    }

    @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
    get fieldValueAndFieldApiName() {
        let fieldWrapper = { value: this.value, apiName: this.targetFieldApiName };
        let returnMap = {};
        returnMap[ this.targetFieldApiName ] = fieldWrapper;

        return returnMap;
    }

    @api
    setCustomValidity(errorMessage) {
        const inputField = this.inputField();

        const canSetCustomValidity =
            inputField &&
            typeof inputField.reportValidity === 'function' &&
            typeof inputField.checkValidity === 'function';
        if (canSetCustomValidity) {
            inputField.setCustomValidity(errorMessage);
            inputField.reportValidity();
        }
    }

    @api
    clearCustomValidity() {

        if (!this.isLookup) {
            this.setCustomValidity('');
        }

    }

    get qaLocatorBase() {
        const rowIndex = this.getAttribute('data-qa-row');
        if (rowIndex) {
            return `${this.fieldLabel} ${rowIndex}`;
        } else {
            return this.fieldLabel;
        }
    }

    get qaLocatorInputPrefix() {
        switch (this.inputType) {
            case DATE:
            case DATETIME:
                return 'datetime';
            case CHECKBOX:
                return this.inputType;
            default:
                return 'input';
        }
    }

    get qaLocatorInput() {
        return `${this.qaLocatorInputPrefix} ${this.qaLocatorBase}`;
    }

    get qaLocatorLookup() {
        return `autocomplete ${this.qaLocatorBase}`;
    }

    get qaLocatorRichText() {
        return `richtext ${this.qaLocatorBase}`;
    }

    get qaLocatorTextArea() {
        return `textarea ${this.qaLocatorBase}`;
    }

    @api
    get formState() {
        return this._formState;
    }

    set formState(formState) {
        this._formState = formState;
        this._recordTypeId = this.recordTypeId();
    }

    get valueFromFormState() {
        const value = this.formState[this.sourceFieldAPIName];

        if (value === undefined) {
           return null;
        }

        const isDonationRecordTypeName =
            this.sourceFieldAPIName === DONATION_RECORD_TYPE_NAME.fieldApiName;
        if (isDonationRecordTypeName) {
            return this.recordTypeIdFor(value);
        }

        if (this.isPicklist && value === null) {
            const hasNoneOptionAvailable =
                this.isValueInOptions(
                    this.CUSTOM_LABELS.commonLabelNone,
                    this.picklistValues
                );
            return hasNoneOptionAvailable ? this.CUSTOM_LABELS.commonLabelNone : '';
        }

        return value;
    }

    fireFormFieldChangeEvent(value) {
        const formFieldChangeEvent = new CustomEvent('formfieldchange', {
            detail:
                {
                    value: value,
                    label: this.isRecordTypePicklist ? this.recordTypeNameFor(value) : value,
                    fieldMappingDevName: this.fieldMappingDevName()
                }
        });
        this.dispatchEvent(formFieldChangeEvent);
    }

    fieldMappingDevName() {
        return this.element.dataImportFieldMappingDevNames[0];
    }

    recordTypeId() {
        const siblingRecordTypeId =
            this.siblingRecordTypeField() === DONATION_RECORD_TYPE_NAME.fieldApiName ?
                this.objectDescribeUtil.recordTypeIdFor(this.siblingRecordTypeValue()) :
                this.siblingRecordTypeValue();

        return siblingRecordTypeId ||
            this.parentRecordRecordTypeId() ||
            this.objectDescribeUtil.defaultRecordTypeId() ||
            null;
    }

    siblingRecordTypeValue() {
        return this.formState && this.formState[this.siblingRecordTypeField()];
    }

    siblingRecordTypeField() {
        return this.element && this.element.siblingRecordTypeField;
    }

    parentRecordRecordTypeId() {
        return this.parentRecord() &&
            this.parentRecord().recordTypeId;
    }

    parentRecord() {
        return this.formState && this.formState[relatedRecordFieldNameFor(this.parentRecordField())];
    }

    parentRecordField() {
        return this.element &&
            this.element.parentRecordField;
    }

    get picklistValues() {
        if (this.element.picklistOptionsOverride) {
            return this.element.picklistOptionsOverride;
        }
        if (this.targetFieldApiName === RECORD_TYPE_FIELD.fieldApiName && this.objectDescribeUtil) {
            return this.objectDescribeUtil.getPicklistOptionsForRecordTypeIds();
        }
        return this._picklistValues;
    }

    set picklistValues(values) {
        this._picklistValues = values;
    }

    get qaLocatorPicklist() {
        return `combobox ${this.qaLocatorBase}`;
    }

    /**
     * Accessor method returns the full object and field api name.
     *
     * Returns null if the current field is a RecordTypeId so that we
     * don't invoke the wired `wiredPicklistValues` function. Instead
     * we retrieve record types objects from `_objectDescribeInfo`
     * and build the picklist options array from there.
     */
    get fullFieldApiNameForStandardPicklists() {
        if (this.isStandardPicklist()) {
            return `${this.objectApiName}.${this.targetFieldApiName}`;
        }
    }

    isStandardPicklist() {
        return this.isPicklist && !this.isRecordTypeIdLookup && !this.hasPicklistOverride;
    }

    get isRecordTypeIdLookup() {
        return this.targetFieldApiName === RECORD_TYPE_FIELD.fieldApiName;
    }

    @wire(getPicklistValues, {
        fieldApiName: '$fullFieldApiNameForStandardPicklists',
        recordTypeId: '$_recordTypeId'
    })
    wiredPicklistValues({error, data}) {
        if (data) {
            this.picklistValues = [this.PICKLIST_OPTION_NONE, ...data.values];
        }
        if (error) {
            console.error(error);
        }
    }

    isValueInOptions(value, options) {
        if (!options || options.length === 0) return false;
        return options.some(option => {
            return option.value === value;
        });
    }

}
<|endoftext|>


<commit_before>var margin = {top: 20, right: 20, bottom: 30, left: 60},
    width = 900 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

var formatPercent = d3.format(".0%");

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .2);

var y = d3.scale.linear()
    .domain([105000, 140000])
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(10);

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(function(d) {
    return "Population: " + d.frequency + "";
  })

var svg = d3.select("#graph1").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

svg.call(tip);

d3.tsv("data.tsv", type, function(error, data) {
  x.domain(data.map(function(d) { return d.letter; }));
  y.domain([105000, 140000]);

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(0)")
      .attr("y", -12)
        .attr("x", 8)
        .attr("font-size", 11)
        .attr("font-family", "ROBOTO")
      .attr("dy", "0")
      .style("text-anchor", "start")
      .text("Population");

  svg.selectAll(".bar")
      .data(data)
    .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.letter); })
      .attr("width", x.rangeBand())
      .attr("y", function(d) { return y(d.frequency); })
      .attr("height", function(d) { return height - y(d.frequency); })
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide)

});

function type(d) {
  d.frequency = +d.frequency;
  return d;
}<commit_msg>margin<commit_after>var margin = {top: 20, right: 20, bottom: 50, left: 60},
    width = 900 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

var formatPercent = d3.format(".0%");

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .2);

var y = d3.scale.linear()
    .domain([105000, 140000])
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(10);

var tip = d3.tip()
  .attr('class', 'd3-tip')
  .offset([-10, 0])
  .html(function(d) {
    return "Population: " + d.frequency + "";
  })

var svg = d3.select("#graph1").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

svg.call(tip);

d3.tsv("data.tsv", type, function(error, data) {
  x.domain(data.map(function(d) { return d.letter; }));
  y.domain([105000, 140000]);

  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis);

    svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
    .append("text")
      .attr("transform", "rotate(0)")
      .attr("y", -12)
        .attr("x", 8)
        .attr("font-size", 11)
        .attr("font-family", "ROBOTO")
      .attr("dy", "0")
      .style("text-anchor", "start")
      .text("Population");

  svg.selectAll(".bar")
      .data(data)
    .enter().append("rect")
      .attr("class", "bar")
      .attr("x", function(d) { return x(d.letter); })
      .attr("width", x.rangeBand())
      .attr("y", function(d) { return y(d.frequency); })
      .attr("height", function(d) { return height - y(d.frequency); })
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide)

});

function type(d) {
  d.frequency = +d.frequency;
  return d;
}<|endoftext|>


<commit_before>function setupRoutes() {
  app.use(function (req, res, next) {
    if (req.method === 'GET' && /^\/[\d\w]+(?=$|[\/?#])/.test(req.url)) {
      req.url = req.url.replace(/^\/([\d\w]+).*$/, "/$1.html");
    }

    if (req.url === '/') {
      req.url = '/index.html'
    }

    next();
  });

  app.use(multer({ dest: './tmp/' }));
  app.use(multerAutoReap);

  app.post('/upload', function postUpload(req, res) {
    console.log(req.files);

    res.redirect('/confirm');
  });

  app.use(express.static(__dirname + '/public'));
}

var express         = require('express'),
    app             = express(),
    multer          = require('multer'),
    multerAutoReap  = require('multer-autoreap'),
    http            = require('http'),
    httpserv        = http.createServer(app),
    port            = process.env.PORT || 5000,
    host            = '127.0.0.1';

httpserv.listen(port, host);

setupRoutes();
<commit_msg> host<commit_after>function setupRoutes() {
  app.use(function (req, res, next) {
    if (req.method === 'GET' && /^\/[\d\w]+(?=$|[\/?#])/.test(req.url)) {
      req.url = req.url.replace(/^\/([\d\w]+).*$/, "/$1.html");
    }

    if (req.url === '/') {
      req.url = '/index.html'
    }

    next();
  });

  app.use(multer({ dest: './tmp/' }));
  app.use(multerAutoReap);

  app.post('/upload', function postUpload(req, res) {
    console.log(req.files);

    res.redirect('/confirm');
  });

  app.use(express.static(__dirname + '/public'));
}

var express         = require('express'),
    app             = express(),
    multer          = require('multer'),
    multerAutoReap  = require('multer-autoreap'),
    http            = require('http'),
    httpserv        = http.createServer(app),
    port            = process.env.PORT || 5000,
    host            = process.env.HOST || '127.0.0.1';

httpserv.listen(port, host);

setupRoutes();
<|endoftext|>


<commit_before>/*global Stem, $, _, Backbone, L */

// View that displays discovery information as a map.
// The collection backing this view contains data for
// a set of point of interest markers.
//
// The mapping itself is mostly handled by the Leafletjs
// library.

Stem.Views = Stem.Views || {};

(function () {
    'use strict';

    Stem.Views.PoisAsMap = Backbone.View.extend({

        // Default options for the view.

        defaults: {
            // Aspect ratio for map
            aspectRatio: 0.5,
            // Attribution for the tiles
            attribution: '© ' +
                'OpenStreetMap ' +
                'Contributors & ' +
                'Stamen Design',
            // Tile layers
            mapTiles: 'http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.jpg',
            // Max zoom level supported by the tiles
            maxZoom: 18,
            // 1px solid color file to tint map
            tintUrl:     ''
        },

        // View initialization.

        initialize: function (options) {

            // Save any options passed to constructor.

            this.options = _.extend({}, this.defaults, options);

            // The collection of POI markers may be
            // built incrementally (e.g. from REST
            // API calls). To handle additions to the
            // collection, we hook its `add` event.

            this.listenTo(this.collection, 'add', this.addMarker);

        },

        // Convenience function to calculate a good
        // zoom level based on window size.

        calcZoom: function () {

            // For now, this is sort of hacked
            // together "by eye." Eventually, it
            // would be good to get some user
            // testing results to better match
            // initial zoom levels with
            // `$(window).width()`

            return 10;

        },

        // Convenience function to set the view
        // for the map. Parameters are latitude
        // and longitude of the user.

        setView: function (lat, lng) {

            // See if the user's latitude and
            // longitude are (more or less)
            // within the state boundary. If
            // they're not, we don't want to
            // rely on the user location to
            // set the map view.

            var minLat = Stem.config.geo.southeastCorner[0],
                maxLat = Stem.config.geo.northwestCorner[0],
                minLng = Stem.config.geo.northwestCorner[1],
                maxLng = Stem.config.geo.southeastCorner[1],
                rangeLat = maxLat - minLat,
                rangeLng = maxLng - minLng;

            if ( (lat > (minLat - rangeLat/2)) &&
                 (lat < (maxLat + rangeLat/2)) &&
                 (lng > (minLng - rangeLng/2)) &&
                 (lng < (maxLng + rangeLng/2)) ) {

                this.map.setView(
                    [lat,lng],
                    this.calcZoom()
                );

            } else {

                // If the user isn't located near
                // the state, use the defaults
                // for the map view.

                this.map.setView([
                        Stem.config.geo.latitude,
                        Stem.config.geo.longitude
                    ],
                    this.calcZoom()
                );

            }

        },

        // The render function that creates the
        // map. There's a separate function to
        // actually show the map. That separation
        // lets us prepare the DOM for the map
        // during the render phase but hold off
        // on the final step until it's necessary.
        // The final step hits the browser's location
        // services API which throws a permissions
        // prompt to the user. We'd like to avoid
        // the permisssions prompt until we're absolutely
        // sure the user is going to see the map.

        render: function () {

            // Discovery maps are full-width (less
            // any margins), but Leaflet requires
            // explicit dimensions. Grab them from
            // the browser. We have to be a little
            // tricky here because the normal jQuery
            // `outerWidth()` function isn't reliable
            // if the element is hidden. Instead,
            // we read the CSS property directly.
            // This does assume that the margins are
            // defined in `px` units.

            var width = $(window).width() -
                    parseInt(this.$el.css('margin-left')) -
                    parseInt(this.$el.css('margin-right')),
                height = width * this.options.aspectRatio;

            // Set the map size explicitly to fill
            // the browser window width.

            this.$el.css({
                height: height + 'px',
                width:  width  + 'px'
            });

            // Initialize the underlying map object.

            this.map = L.map(this.el, {
                layers: [L.tileLayer(this.options.mapTiles, {
                            attribution: this.options.attribution,
                            maxZoom: this.options.maxZoom
                        })],
                maxBounds: [ Stem.config.geo.northwestCorner,
                             Stem.config.geo.southeastCorner ],
                scrollWheelZoom: false,
            });

            // If a tint is desired, add it.

            if (this.options.tintUrl) {
                L.tileLayer(this.options.tintUrl, {
                    continuousWorld: true,
                    opacity: 0.1,
                    reuseTiles: true,
                    tileSize: 2048,
                }).addTo(this.map);
            }

            // Now that the map infrastructure
            // is in place, we can look at the
            // collection of POI markers. If there
            // are any already in the collection,
            // add them to the map.

            this.collection.each(function(marker) {
                this.addMarker(marker);
            }, this);

            // Cache a reference to the Leaflet
            // structure in the map element. We
            // do this so that the window resize
            // handler can update Leaflet when
            // the window size changes.

            this.$el.data('map', this.map);
            this.$el.data('aspectRatio', this.options.aspectRatio);

            return this; // for method chaining

        },

        // Function to show the map to the user.
        // This function may cause the browser to
        // throw a permission prompt to the user
        // (for location information), so it's
        // best to avoid it unless we're sure
        // that the map is actually visible.

        show: function () {

            // Use a closure variable to keep track
            // of whether we've found the location
            // or not.

            var locationFound = false;

            // We're going to try to set the initial
            // map location based on the user's
            // current location. The first thing to
            // check is whether or not that information
            // is already available and cached.

            if (Stem.user.geo.latitude && Stem.user.geo.longitude) {
                this.setView(
                    Stem.user.geo.latitude,
                    Stem.user.geo.longitude
                );
            }

            // Okay, the geo-location info isn't
            // conveniently available already, so we'll
            // try to set it automatically using the
            // browser's location services. Before making
            // the API call, we set up simple event handlers
            // to deal with the results.

            this.map.on('locationfound', function(ev) {

                // Set the map view.

                this.setView(ev.latlng.lat, ev.latlng.lng);

                // If we successfully get the location,
                // save it for other maps to use.

                Stem.user.geo.latitude = ev.latlng.lat;
                Stem.user.geo.longitude = ev.latlng.lng;

                // Note that we've found it.

                locationFound = true;

            }, this).on('locationerror', this.locationFailed, this);

            // Now we're set up to make the call
            // to request location information
            // from the user.

            this.map.locate({
                maximumAge: 60*60*1000, // Cached info is okay
                setView:    false
            });

            // Unfortunately, some browers allow
            // location permission requests to
            // fail silently (without returning
            // an error) if, e.g., the user simply
            // dismisses the dialog box without
            // indicating a preference. To deal
            // with that case, we have to add our
            // own explicit timeout.

            _.chain(function() {

                // Once the delay has passed, check
                // to see if we have a location.

                if (!locationFound ||
                    !Stem.user.geo.latitude ||
                    !Stem.user.geo.longitude) {

                    // Nope. Resort to fallback.

                    this.locationFailed();

                }

            }).bind(this).delay(8*1000);

            return this; // for method chaining

        },

        // Handle case when browser geolocation fails.

        locationFailed: function () {

            // We may have gotten here because of
            // a race condition in Leaflet with
            // some browsers. If that's the
            // case, we can still set the view.

            if (Stem.user.geo.latitude &&
                Stem.user.geo.longitude) {

                this.setView(
                    Stem.user.geo.latitude,
                    Stem.user.geo.longitude
                );

            } else {

                // Bummer. We couldn't get the location
                // information from the browser directly.
                // As a backup, let's try using the
                // client's public IP address.

                Stem.Utils.getLocationFromIp(
                    _(function(LatLong) {

                        // Success. Set the map view.

                        this.setView(LatLong[0],LatLong[1]);

                    }).bind(this),
                    _(function() {

                        // Okay. That didn't work either.
                        // Only option left is resorting
                        // to default values.

                        this.setView(
                            Stem.config.geo.latitude,
                            Stem.config.geo.longitude
                        );

                    }).bind(this)

                );

            }

        },

        // Add an additional POI marker to the map.

        addMarker: function (marker) {

            // We only process this if the
            // map has been rendered. (If
            // it hasn't, the markers will
            // just collect in the collection
            // until this view is rendered,
            // at which time we'll catch up.

            if (this.map) {

                // Create a simple HTML icon
                // for the marker. The numbers
                // here are (unfortunately)
                // tied pretty tightly to the
                // CSS that styles the markers.

                var icon = L.divIcon({
                    className: 'fa fa-map-marker poi-marker ' +
                                marker.get('className'),
                    iconAnchor: [20,40],
                    iconSize: [40,40],
                    popupAnchor: [20, -40]
                });

                // Create the marker object with
                // appropriate properties and add
                // it to the map.

                L.marker([
                    marker.get('latitude'),
                    marker.get('longitude')
                ],{
                    icon: icon
                })
                .bindPopup(new Stem.Views.PoiAsMarker({
                    model: marker
                }).render().el, {
                    closeButton: false,
                    maxWidth: 250,
                    minWidth: 250,
                    offset: [140, 0]
                })
                .addTo(this.map);

            }

        }

    });

})();
<commit_msg>remove bounds on map<commit_after>/*global Stem, $, _, Backbone, L */

// View that displays discovery information as a map.
// The collection backing this view contains data for
// a set of point of interest markers.
//
// The mapping itself is mostly handled by the Leafletjs
// library.

Stem.Views = Stem.Views || {};

(function () {
    'use strict';

    Stem.Views.PoisAsMap = Backbone.View.extend({

        // Default options for the view.

        defaults: {
            // Aspect ratio for map
            aspectRatio: 0.5,
            // Attribution for the tiles
            attribution: '© ' +
                'OpenStreetMap ' +
                'Contributors & ' +
                'Stamen Design',
            // Tile layers
            mapTiles: 'http://{s}.tile.stamen.com/toner-lite/{z}/{x}/{y}.jpg',
            // Max zoom level supported by the tiles
            maxZoom: 18,
            // 1px solid color file to tint map
            tintUrl:     ''
        },

        // View initialization.

        initialize: function (options) {

            // Save any options passed to constructor.

            this.options = _.extend({}, this.defaults, options);

            // The collection of POI markers may be
            // built incrementally (e.g. from REST
            // API calls). To handle additions to the
            // collection, we hook its `add` event.

            this.listenTo(this.collection, 'add', this.addMarker);

        },

        // Convenience function to calculate a good
        // zoom level based on window size.

        calcZoom: function () {

            // For now, this is sort of hacked
            // together "by eye." Eventually, it
            // would be good to get some user
            // testing results to better match
            // initial zoom levels with
            // `$(window).width()`

            return 10;

        },

        // Convenience function to set the view
        // for the map. Parameters are latitude
        // and longitude of the user.

        setView: function (lat, lng) {

            // See if the user's latitude and
            // longitude are (more or less)
            // within the state boundary. If
            // they're not, we don't want to
            // rely on the user location to
            // set the map view.

            var minLat = Stem.config.geo.southeastCorner[0],
                maxLat = Stem.config.geo.northwestCorner[0],
                minLng = Stem.config.geo.northwestCorner[1],
                maxLng = Stem.config.geo.southeastCorner[1],
                rangeLat = maxLat - minLat,
                rangeLng = maxLng - minLng;

            if ( (lat > (minLat - rangeLat/2)) &&
                 (lat < (maxLat + rangeLat/2)) &&
                 (lng > (minLng - rangeLng/2)) &&
                 (lng < (maxLng + rangeLng/2)) ) {

                this.map.setView(
                    [lat,lng],
                    this.calcZoom()
                );

            } else {

                // If the user isn't located near
                // the state, use the defaults
                // for the map view.

                this.map.setView([
                        Stem.config.geo.latitude,
                        Stem.config.geo.longitude
                    ],
                    this.calcZoom()
                );

            }

        },

        // The render function that creates the
        // map. There's a separate function to
        // actually show the map. That separation
        // lets us prepare the DOM for the map
        // during the render phase but hold off
        // on the final step until it's necessary.
        // The final step hits the browser's location
        // services API which throws a permissions
        // prompt to the user. We'd like to avoid
        // the permisssions prompt until we're absolutely
        // sure the user is going to see the map.

        render: function () {

            // Discovery maps are full-width (less
            // any margins), but Leaflet requires
            // explicit dimensions. Grab them from
            // the browser. We have to be a little
            // tricky here because the normal jQuery
            // `outerWidth()` function isn't reliable
            // if the element is hidden. Instead,
            // we read the CSS property directly.
            // This does assume that the margins are
            // defined in `px` units.

            var width = $(window).width() -
                    parseInt(this.$el.css('margin-left')) -
                    parseInt(this.$el.css('margin-right')),
                height = width * this.options.aspectRatio;

            // Set the map size explicitly to fill
            // the browser window width.

            this.$el.css({
                height: height + 'px',
                width:  width  + 'px'
            });

            // Initialize the underlying map object. The code
            // to limit the map to Georgia only is currently
            // commented out because it introduces usability
            // issues. (Namely, if a popup extends beyond
            // the map bounds, it's partially unreadable.)
            // If the bounding behavior is desirable, the
            // bounds should be extended to allow full popups
            // to display. It's not considered a problem
            // worth solving at this time, but the code is
            // left in place (though commented out) to
            // indicate where the behavior can be added back.

            this.map = L.map(this.el, {
                layers: [L.tileLayer(this.options.mapTiles, {
                            attribution: this.options.attribution,
                            maxZoom: this.options.maxZoom
                        })],
                // maxBounds: [ Stem.config.geo.northwestCorner,
                //              Stem.config.geo.southeastCorner ],
                scrollWheelZoom: false,
            });

            // If a tint is desired, add it.

            if (this.options.tintUrl) {
                L.tileLayer(this.options.tintUrl, {
                    continuousWorld: true,
                    opacity: 0.1,
                    reuseTiles: true,
                    tileSize: 2048,
                }).addTo(this.map);
            }

            // Now that the map infrastructure
            // is in place, we can look at the
            // collection of POI markers. If there
            // are any already in the collection,
            // add them to the map.

            this.collection.each(function(marker) {
                this.addMarker(marker);
            }, this);

            // Cache a reference to the Leaflet
            // structure in the map element. We
            // do this so that the window resize
            // handler can update Leaflet when
            // the window size changes.

            this.$el.data('map', this.map);
            this.$el.data('aspectRatio', this.options.aspectRatio);

            return this; // for method chaining

        },

        // Function to show the map to the user.
        // This function may cause the browser to
        // throw a permission prompt to the user
        // (for location information), so it's
        // best to avoid it unless we're sure
        // that the map is actually visible.

        show: function () {

            // Use a closure variable to keep track
            // of whether we've found the location
            // or not.

            var locationFound = false;

            // We're going to try to set the initial
            // map location based on the user's
            // current location. The first thing to
            // check is whether or not that information
            // is already available and cached.

            if (Stem.user.geo.latitude && Stem.user.geo.longitude) {
                this.setView(
                    Stem.user.geo.latitude,
                    Stem.user.geo.longitude
                );
            }

            // Okay, the geo-location info isn't
            // conveniently available already, so we'll
            // try to set it automatically using the
            // browser's location services. Before making
            // the API call, we set up simple event handlers
            // to deal with the results.

            this.map.on('locationfound', function(ev) {

                // Set the map view.

                this.setView(ev.latlng.lat, ev.latlng.lng);

                // If we successfully get the location,
                // save it for other maps to use.

                Stem.user.geo.latitude = ev.latlng.lat;
                Stem.user.geo.longitude = ev.latlng.lng;

                // Note that we've found it.

                locationFound = true;

            }, this).on('locationerror', this.locationFailed, this);

            // Now we're set up to make the call
            // to request location information
            // from the user.

            this.map.locate({
                maximumAge: 60*60*1000, // Cached info is okay
                setView:    false
            });

            // Unfortunately, some browers allow
            // location permission requests to
            // fail silently (without returning
            // an error) if, e.g., the user simply
            // dismisses the dialog box without
            // indicating a preference. To deal
            // with that case, we have to add our
            // own explicit timeout.

            _.chain(function() {

                // Once the delay has passed, check
                // to see if we have a location.

                if (!locationFound ||
                    !Stem.user.geo.latitude ||
                    !Stem.user.geo.longitude) {

                    // Nope. Resort to fallback.

                    this.locationFailed();

                }

            }).bind(this).delay(8*1000);

            return this; // for method chaining

        },

        // Handle case when browser geolocation fails.

        locationFailed: function () {

            // We may have gotten here because of
            // a race condition in Leaflet with
            // some browsers. If that's the
            // case, we can still set the view.

            if (Stem.user.geo.latitude &&
                Stem.user.geo.longitude) {

                this.setView(
                    Stem.user.geo.latitude,
                    Stem.user.geo.longitude
                );

            } else {

                // Bummer. We couldn't get the location
                // information from the browser directly.
                // As a backup, let's try using the
                // client's public IP address.

                Stem.Utils.getLocationFromIp(
                    _(function(LatLong) {

                        // Success. Set the map view.

                        this.setView(LatLong[0],LatLong[1]);

                    }).bind(this),
                    _(function() {

                        // Okay. That didn't work either.
                        // Only option left is resorting
                        // to default values.

                        this.setView(
                            Stem.config.geo.latitude,
                            Stem.config.geo.longitude
                        );

                    }).bind(this)

                );

            }

        },

        // Add an additional POI marker to the map.

        addMarker: function (marker) {

            // We only process this if the
            // map has been rendered. (If
            // it hasn't, the markers will
            // just collect in the collection
            // until this view is rendered,
            // at which time we'll catch up.

            if (this.map) {

                // Create a simple HTML icon
                // for the marker. The numbers
                // here are (unfortunately)
                // tied pretty tightly to the
                // CSS that styles the markers.

                var icon = L.divIcon({
                    className: 'fa fa-map-marker poi-marker ' +
                                marker.get('className'),
                    iconAnchor: [20,40],
                    iconSize: [40,40],
                    popupAnchor: [20, -40]
                });

                // Create the marker object with
                // appropriate properties and add
                // it to the map.

                L.marker([
                    marker.get('latitude'),
                    marker.get('longitude')
                ],{
                    icon: icon
                })
                .bindPopup(new Stem.Views.PoiAsMarker({
                    model: marker
                }).render().el, {
                    closeButton: false,
                    maxWidth: 250,
                    minWidth: 250,
                    offset: [140, 0]
                })
                .addTo(this.map);

            }

        }

    });

})();
<|endoftext|>


<commit_before>(function (requirejs, require, define) {

// VideoPlayer module.
define(
'video/03_video_player.js',
['video/02_html5_video.js'],
function (HTML5Video) {

    // VideoPlayer() function - what this module "exports".
    return function (state) {
        state.videoPlayer = {};

        _makeFunctionsPublic(state);
        _initialize(state);
        // No callbacks to DOM events (click, mousemove, etc.).
    };

    // ***************************************************************
    // Private functions start here.
    // ***************************************************************

    // function _makeFunctionsPublic(state)
    //
    //     Functions which will be accessible via 'state' object. When called, these functions will
    //     get the 'state' object as a context.
    function _makeFunctionsPublic(state) {
        state.videoPlayer.pause                       = _.bind(pause, state);
        state.videoPlayer.play                        = _.bind(play, state);
        state.videoPlayer.update                      = _.bind(update, state);
        state.videoPlayer.onSpeedChange               = _.bind(onSpeedChange, state);
        state.videoPlayer.onCaptionSeek               = _.bind(onSeek, state);
        state.videoPlayer.onSlideSeek                 = _.bind(onSeek, state);
        state.videoPlayer.onEnded                     = _.bind(onEnded, state);
        state.videoPlayer.onPause                     = _.bind(onPause, state);
        state.videoPlayer.onPlay                      = _.bind(onPlay, state);
        state.videoPlayer.onUnstarted                 = _.bind(onUnstarted, state);
        state.videoPlayer.handlePlaybackQualityChange = _.bind(handlePlaybackQualityChange, state);
        state.videoPlayer.onPlaybackQualityChange     = _.bind(onPlaybackQualityChange, state);
        state.videoPlayer.onStateChange               = _.bind(onStateChange, state);
        state.videoPlayer.onReady                     = _.bind(onReady, state);
        state.videoPlayer.updatePlayTime              = _.bind(updatePlayTime, state);
        state.videoPlayer.isPlaying                   = _.bind(isPlaying, state);
        state.videoPlayer.log                         = _.bind(log, state);
        state.videoPlayer.duration                    = _.bind(duration, state);
        state.videoPlayer.onVolumeChange              = _.bind(onVolumeChange, state);
    }

    // function _initialize(state)
    //
    //     Create any necessary DOM elements, attach them, and set their initial configuration. Also
    //     make the created DOM elements available via the 'state' object. Much easier to work this
    //     way - you don't have to do repeated jQuery element selects.
    function _initialize(state) {
        var youTubeId;

        if (state.videoType === 'youtube') {
            state.videoPlayer.PlayerState = YT.PlayerState;
            state.videoPlayer.PlayerState.UNSTARTED = -1;
        } else { // if (state.videoType === 'html5') {
            state.videoPlayer.PlayerState = HTML5Video.PlayerState;
        }

        state.videoPlayer.currentTime = 0;

        state.videoPlayer.playerVars = {
            controls: 0,
            wmode: 'transparent',
            rel: 0,
            showinfo: 0,
            enablejsapi: 1,
            modestbranding: 1
        };

        if (state.currentPlayerMode !== 'flash') {
            state.videoPlayer.playerVars.html5 = 1;
        }

        if (state.config.start) {
            state.videoPlayer.playerVars.start = state.config.start;
            state.videoPlayer.playerVars.wmode = 'window';
        }
        if (state.config.end) {
          state.videoPlayer.playerVars.end = state.config.end;
        }

        // There is a bug which prevents YouTube API to correctly set the speed
        // to 1.0 from another speed in Firefox when in HTML5 mode. There is a
        // fix which basically reloads the video at speed 1.0 when this change
        // is requested (instead of simply requesting a speed change to 1.0).
        // This has to be done only when the video is being watched in Firefox.
        // We need to figure out what browser is currently executing this code.
        //
        // TODO: Check the status of
        // http://code.google.com/p/gdata-issues/issues/detail?id=4654
        // When the YouTube team fixes the API bug, we can remove this temporary
        // bug fix.
        state.browserIsFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;

        if (state.videoType === 'html5') {
            state.videoPlayer.player = new HTML5Video.Player(state.el, {
                playerVars:   state.videoPlayer.playerVars,
                videoSources: state.html5Sources,
                events: {
                    onReady:       state.videoPlayer.onReady,
                    onStateChange: state.videoPlayer.onStateChange
                }
            });
        } else { // if (state.videoType === 'youtube') {
            if (state.currentPlayerMode === 'flash') {
                youTubeId = state.youtubeId();
            } else {
                youTubeId = state.youtubeId('1.0');
            }
            state.videoPlayer.player = new YT.Player(state.id, {
                playerVars: state.videoPlayer.playerVars,
                videoId: youTubeId,
                events: {
                    onReady: state.videoPlayer.onReady,
                    onStateChange: state.videoPlayer.onStateChange,
                    onPlaybackQualityChange: state.videoPlayer.onPlaybackQualityChange
                }
            });
        }
    }

    // function _restartUsingFlash(state)
    //
    //     When we are about to play a YouTube video in HTML5 mode and discover that we only
    //     have one available playback rate, we will switch to Flash mode. In Flash speed
    //     switching is done by reloading videos recorded at different frame rates.
    function _restartUsingFlash(state) {
        // Remove from the page current iFrame with HTML5 video.
        state.videoPlayer.player.destroy();

        // Remember for future page loads that we should use Flash mode.
        $.cookie('current_player_mode', 'flash', {
            'expires': 3650,
            'path': '/'
        });
        state.currentPlayerMode = 'flash';

        // Removed configuration option that requests the HTML5 mode.
        delete state.videoPlayer.playerVars.html5;

        // Request for the creation of a new Flash player
        state.videoPlayer.player = new YT.Player(state.id, {
            'playerVars': state.videoPlayer.playerVars,
            'videoId': state.youtubeId(),
            'events': {
                'onReady': state.videoPlayer.onReady,
                'onStateChange': state.videoPlayer.onStateChange,
                'onPlaybackQualityChange': state.videoPlayer.onPlaybackQualityChange
            }
        });
    }

    // ***************************************************************
    // Public functions start here.
    // These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
    // The magic private function that makes them available and sets up their context is makeFunctionsPublic().
    // ***************************************************************

    function pause() {
        if (this.videoPlayer.player.pauseVideo) {
            this.videoPlayer.player.pauseVideo();
        }
    }

    function play() {
        if (this.videoPlayer.player.playVideo) {
            this.videoPlayer.player.playVideo();
        }
    }

    // This function gets the video's current play position in time
    // (currentTime) and its duration.
    // It is called at a regular interval when the video is playing (see below).
    function update() {
        this.videoPlayer.currentTime = this.videoPlayer.player.getCurrentTime();

        if (isFinite(this.videoPlayer.currentTime)) {
            this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
        }
    }

    function onSpeedChange(newSpeed, updateCookie) {
        if (this.currentPlayerMode === 'flash') {
            this.videoPlayer.currentTime = Time.convert(
                this.videoPlayer.currentTime,
                parseFloat(this.speed),
                newSpeed
            );
        }
        newSpeed = parseFloat(newSpeed).toFixed(2).replace(/\.00$/, '.0');

        this.videoPlayer.log(
            'speed_change_video',
            {
                current_time: this.videoPlayer.currentTime,
                old_speed: this.speed,
                new_speed: newSpeed
            }
        );

        this.setSpeed(newSpeed, updateCookie);

        if (
            this.currentPlayerMode === 'html5' &&
            !(this.browserIsFirefox && newSpeed === '1.0' && this.videoType === 'youtube')
        ) {
            this.videoPlayer.player.setPlaybackRate(newSpeed);
        } else {
            // We request the reloading of the video in the case when YouTube is in
            // Flash player mode, or when we are in Firefox, and the new speed is 1.0.
            // The second case is necessary to avoid the bug where in Firefox speed
            // switching to 1.0 in HTML5 player mode is handled incorrectly by YouTube
            // API.
            if (this.videoPlayer.isPlaying()) {
                this.videoPlayer.player.loadVideoById(this.youtubeId(), this.videoPlayer.currentTime);
            } else {
                this.videoPlayer.player.cueVideoById(this.youtubeId(), this.videoPlayer.currentTime);
            }

            this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
        }
    }

    // Every 200 ms, if the video is playing, we call the function update, via
    // clearInterval. This interval is called updateInterval.
    // It is created on a onPlay event. Cleared on a onPause event.
    // Reinitialized on a onSeek event.
    function onSeek(params) {
        this.videoPlayer.log(
            'seek_video',
            {
                old_time: this.videoPlayer.currentTime,
                new_time: params.time,
                type: params.type
            }
        );

        this.videoPlayer.player.seekTo(params.time, true);

        if (this.videoPlayer.isPlaying()) {
            clearInterval(this.videoPlayer.updateInterval);
            this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200);
        } else {
            this.videoPlayer.currentTime = params.time;
        }

        this.videoPlayer.updatePlayTime(params.time);
    }

    function onEnded() {
        this.trigger('videoControl.pause', null);

        if (this.config.show_captions) {
            this.trigger('videoCaption.pause', null);
        }
    }

    function onPause() {
        this.videoPlayer.log(
            'pause_video',
            {
                'currentTime': this.videoPlayer.currentTime
            }
        );

        clearInterval(this.videoPlayer.updateInterval);
        delete this.videoPlayer.updateInterval;

        this.trigger('videoControl.pause', null);

        if (this.config.show_captions) {
            this.trigger('videoCaption.pause', null);
        }
    }

    function onPlay() {
        this.videoPlayer.log(
            'play_video',
            {
                'currentTime': this.videoPlayer.currentTime
            }
        );

        if (!this.videoPlayer.updateInterval) {
            this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200);
        }

        this.trigger('videoControl.play', null);

        if (this.config.show_captions) {
            this.trigger('videoCaption.play', null);
        }
    }

    function onUnstarted() { }

    function handlePlaybackQualityChange(value) {
        this.videoPlayer.player.setPlaybackQuality(value);
    }

    function onPlaybackQualityChange() {
        var quality;

        quality = this.videoPlayer.player.getPlaybackQuality();

        this.trigger('videoQualityControl.onQualityChange', quality);
    }

    function onReady() {
        var availablePlaybackRates, baseSpeedSubs, _this;

        this.videoPlayer.log('load_video');

        availablePlaybackRates = this.videoPlayer.player.getAvailablePlaybackRates();
        if ((this.currentPlayerMode === 'html5') && (this.videoType === 'youtube')) {
            if (availablePlaybackRates.length === 1) {
                // This condition is needed in cases when Firefox version is less than 20. In those versions
                // HTML5 playback could only happen at 1 speed (no speed changing). Therefore, in this case,
                // we need to switch back to Flash.
                //
                // This might also happen in other browsers, therefore when we have 1 speed available, we fall
                // back to Flash.

                _restartUsingFlash(this);

                return;
            } else if (availablePlaybackRates.length > 1) {
                // We need to synchronize available frame rates with the ones
                // that the user specified.

                baseSpeedSubs = this.videos['1.0'];
                _this = this;
                // this.videos is a dictionary containing various frame rates
                // and their associated subs.

                // First clear the dictionary.
                $.each(this.videos, function(index, value) {
                    delete _this.videos[index];
                });
                this.speeds = [];
                // Recreate it with the supplied frame rates.
                $.each(availablePlaybackRates, function(index, value) {
                    _this.videos[value.toFixed(2).replace(/\.00$/, '.0')] = baseSpeedSubs;

                    _this.speeds.push(value.toFixed(2).replace(/\.00$/, '.0'));
                });

                this.trigger('videoSpeedControl.reRender', {'newSpeeds': this.speeds, 'currentSpeed': this.speed});

                this.setSpeed($.cookie('video_speed'));
            }
        }

        if (this.currentPlayerMode === 'html5') {
            this.videoPlayer.player.setPlaybackRate(this.speed);
        }

        if (!onTouchBasedDevice() && $('.video:first').data('autoplay') === 'True') {
            this.videoPlayer.play();
        }
    }

    function onStateChange(event) {
        switch (event.data) {
            case this.videoPlayer.PlayerState.UNSTARTED:
                this.videoPlayer.onUnstarted();
                break;
            case this.videoPlayer.PlayerState.PLAYING:
                this.videoPlayer.onPlay();
                break;
            case this.videoPlayer.PlayerState.PAUSED:
                this.videoPlayer.onPause();
                break;
            case this.videoPlayer.PlayerState.ENDED:
                this.videoPlayer.onEnded();
                break;
        }
    }

    function updatePlayTime(time) {
        var duration;

        duration = this.videoPlayer.duration();

        this.trigger('videoProgressSlider.updatePlayTime', {'time': time, 'duration': duration});
        this.trigger('videoControl.updateVcrVidTime', {'time': time, 'duration': duration});
        this.trigger('videoCaption.updatePlayTime', time);
    }

    function isPlaying() {
        return this.videoPlayer.player.getPlayerState() === this.videoPlayer.PlayerState.PLAYING;
    }

    function duration() {
        var dur;

        dur = this.videoPlayer.player.getDuration();
        if (!isFinite(dur)) {
            dur = this.getDuration();
        }

        return dur;
    }

    function log(eventName, data) {
        var logInfo;

        // Default parameters that always get logged.
        logInfo = {
            'id':   this.id,
            'code': this.youtubeId()
        };

        // If extra parameters were passed to the log.
        if (data) {
            $.each(data, function(paramName, value) {
                logInfo[paramName] = value;
            });
        }

        if (this.videoType === 'youtube') {
            logInfo.code = this.youtubeId();
        } else  if (this.videoType === 'html5') {
                logInfo.code = 'html5';
        }

        Logger.log(eventName, logInfo);
    }

    function onVolumeChange(volume) {
        this.videoPlayer.player.setVolume(volume);
    }
});

}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
<commit_msg>Add filter for playback rates.<commit_after>(function (requirejs, require, define) {

// VideoPlayer module.
define(
'video/03_video_player.js',
['video/02_html5_video.js'],
function (HTML5Video) {

    // VideoPlayer() function - what this module "exports".
    return function (state) {
        state.videoPlayer = {};

        _makeFunctionsPublic(state);
        _initialize(state);
        // No callbacks to DOM events (click, mousemove, etc.).
    };

    // ***************************************************************
    // Private functions start here.
    // ***************************************************************

    // function _makeFunctionsPublic(state)
    //
    //     Functions which will be accessible via 'state' object. When called, these functions will
    //     get the 'state' object as a context.
    function _makeFunctionsPublic(state) {
        state.videoPlayer.pause                       = _.bind(pause, state);
        state.videoPlayer.play                        = _.bind(play, state);
        state.videoPlayer.update                      = _.bind(update, state);
        state.videoPlayer.onSpeedChange               = _.bind(onSpeedChange, state);
        state.videoPlayer.onCaptionSeek               = _.bind(onSeek, state);
        state.videoPlayer.onSlideSeek                 = _.bind(onSeek, state);
        state.videoPlayer.onEnded                     = _.bind(onEnded, state);
        state.videoPlayer.onPause                     = _.bind(onPause, state);
        state.videoPlayer.onPlay                      = _.bind(onPlay, state);
        state.videoPlayer.onUnstarted                 = _.bind(onUnstarted, state);
        state.videoPlayer.handlePlaybackQualityChange = _.bind(handlePlaybackQualityChange, state);
        state.videoPlayer.onPlaybackQualityChange     = _.bind(onPlaybackQualityChange, state);
        state.videoPlayer.onStateChange               = _.bind(onStateChange, state);
        state.videoPlayer.onReady                     = _.bind(onReady, state);
        state.videoPlayer.updatePlayTime              = _.bind(updatePlayTime, state);
        state.videoPlayer.isPlaying                   = _.bind(isPlaying, state);
        state.videoPlayer.log                         = _.bind(log, state);
        state.videoPlayer.duration                    = _.bind(duration, state);
        state.videoPlayer.onVolumeChange              = _.bind(onVolumeChange, state);
    }

    // function _initialize(state)
    //
    //     Create any necessary DOM elements, attach them, and set their initial configuration. Also
    //     make the created DOM elements available via the 'state' object. Much easier to work this
    //     way - you don't have to do repeated jQuery element selects.
    function _initialize(state) {
        var youTubeId;

        if (state.videoType === 'youtube') {
            state.videoPlayer.PlayerState = YT.PlayerState;
            state.videoPlayer.PlayerState.UNSTARTED = -1;
        } else { // if (state.videoType === 'html5') {
            state.videoPlayer.PlayerState = HTML5Video.PlayerState;
        }

        state.videoPlayer.currentTime = 0;

        state.videoPlayer.playerVars = {
            controls: 0,
            wmode: 'transparent',
            rel: 0,
            showinfo: 0,
            enablejsapi: 1,
            modestbranding: 1
        };

        if (state.currentPlayerMode !== 'flash') {
            state.videoPlayer.playerVars.html5 = 1;
        }

        if (state.config.start) {
            state.videoPlayer.playerVars.start = state.config.start;
            state.videoPlayer.playerVars.wmode = 'window';
        }
        if (state.config.end) {
          state.videoPlayer.playerVars.end = state.config.end;
        }

        // There is a bug which prevents YouTube API to correctly set the speed
        // to 1.0 from another speed in Firefox when in HTML5 mode. There is a
        // fix which basically reloads the video at speed 1.0 when this change
        // is requested (instead of simply requesting a speed change to 1.0).
        // This has to be done only when the video is being watched in Firefox.
        // We need to figure out what browser is currently executing this code.
        //
        // TODO: Check the status of
        // http://code.google.com/p/gdata-issues/issues/detail?id=4654
        // When the YouTube team fixes the API bug, we can remove this temporary
        // bug fix.
        state.browserIsFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;

        if (state.videoType === 'html5') {
            state.videoPlayer.player = new HTML5Video.Player(state.el, {
                playerVars:   state.videoPlayer.playerVars,
                videoSources: state.html5Sources,
                events: {
                    onReady:       state.videoPlayer.onReady,
                    onStateChange: state.videoPlayer.onStateChange
                }
            });
        } else { // if (state.videoType === 'youtube') {
            if (state.currentPlayerMode === 'flash') {
                youTubeId = state.youtubeId();
            } else {
                youTubeId = state.youtubeId('1.0');
            }
            state.videoPlayer.player = new YT.Player(state.id, {
                playerVars: state.videoPlayer.playerVars,
                videoId: youTubeId,
                events: {
                    onReady: state.videoPlayer.onReady,
                    onStateChange: state.videoPlayer.onStateChange,
                    onPlaybackQualityChange: state.videoPlayer.onPlaybackQualityChange
                }
            });
        }
    }

    // function _restartUsingFlash(state)
    //
    //     When we are about to play a YouTube video in HTML5 mode and discover that we only
    //     have one available playback rate, we will switch to Flash mode. In Flash speed
    //     switching is done by reloading videos recorded at different frame rates.
    function _restartUsingFlash(state) {
        // Remove from the page current iFrame with HTML5 video.
        state.videoPlayer.player.destroy();

        // Remember for future page loads that we should use Flash mode.
        $.cookie('current_player_mode', 'flash', {
            'expires': 3650,
            'path': '/'
        });
        state.currentPlayerMode = 'flash';

        // Removed configuration option that requests the HTML5 mode.
        delete state.videoPlayer.playerVars.html5;

        // Request for the creation of a new Flash player
        state.videoPlayer.player = new YT.Player(state.id, {
            'playerVars': state.videoPlayer.playerVars,
            'videoId': state.youtubeId(),
            'events': {
                'onReady': state.videoPlayer.onReady,
                'onStateChange': state.videoPlayer.onStateChange,
                'onPlaybackQualityChange': state.videoPlayer.onPlaybackQualityChange
            }
        });
    }

    // ***************************************************************
    // Public functions start here.
    // These are available via the 'state' object. Their context ('this' keyword) is the 'state' object.
    // The magic private function that makes them available and sets up their context is makeFunctionsPublic().
    // ***************************************************************

    function pause() {
        if (this.videoPlayer.player.pauseVideo) {
            this.videoPlayer.player.pauseVideo();
        }
    }

    function play() {
        if (this.videoPlayer.player.playVideo) {
            this.videoPlayer.player.playVideo();
        }
    }

    // This function gets the video's current play position in time
    // (currentTime) and its duration.
    // It is called at a regular interval when the video is playing (see below).
    function update() {
        this.videoPlayer.currentTime = this.videoPlayer.player.getCurrentTime();

        if (isFinite(this.videoPlayer.currentTime)) {
            this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
        }
    }

    function onSpeedChange(newSpeed, updateCookie) {
        if (this.currentPlayerMode === 'flash') {
            this.videoPlayer.currentTime = Time.convert(
                this.videoPlayer.currentTime,
                parseFloat(this.speed),
                newSpeed
            );
        }
        newSpeed = parseFloat(newSpeed).toFixed(2).replace(/\.00$/, '.0');

        this.videoPlayer.log(
            'speed_change_video',
            {
                current_time: this.videoPlayer.currentTime,
                old_speed: this.speed,
                new_speed: newSpeed
            }
        );

        this.setSpeed(newSpeed, updateCookie);

        if (
            this.currentPlayerMode === 'html5' &&
            !(this.browserIsFirefox && newSpeed === '1.0' && this.videoType === 'youtube')
        ) {
            this.videoPlayer.player.setPlaybackRate(newSpeed);
        } else {
            // We request the reloading of the video in the case when YouTube is in
            // Flash player mode, or when we are in Firefox, and the new speed is 1.0.
            // The second case is necessary to avoid the bug where in Firefox speed
            // switching to 1.0 in HTML5 player mode is handled incorrectly by YouTube
            // API.
            if (this.videoPlayer.isPlaying()) {
                this.videoPlayer.player.loadVideoById(this.youtubeId(), this.videoPlayer.currentTime);
            } else {
                this.videoPlayer.player.cueVideoById(this.youtubeId(), this.videoPlayer.currentTime);
            }

            this.videoPlayer.updatePlayTime(this.videoPlayer.currentTime);
        }
    }

    // Every 200 ms, if the video is playing, we call the function update, via
    // clearInterval. This interval is called updateInterval.
    // It is created on a onPlay event. Cleared on a onPause event.
    // Reinitialized on a onSeek event.
    function onSeek(params) {
        this.videoPlayer.log(
            'seek_video',
            {
                old_time: this.videoPlayer.currentTime,
                new_time: params.time,
                type: params.type
            }
        );

        this.videoPlayer.player.seekTo(params.time, true);

        if (this.videoPlayer.isPlaying()) {
            clearInterval(this.videoPlayer.updateInterval);
            this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200);
        } else {
            this.videoPlayer.currentTime = params.time;
        }

        this.videoPlayer.updatePlayTime(params.time);
    }

    function onEnded() {
        this.trigger('videoControl.pause', null);

        if (this.config.show_captions) {
            this.trigger('videoCaption.pause', null);
        }
    }

    function onPause() {
        this.videoPlayer.log(
            'pause_video',
            {
                'currentTime': this.videoPlayer.currentTime
            }
        );

        clearInterval(this.videoPlayer.updateInterval);
        delete this.videoPlayer.updateInterval;

        this.trigger('videoControl.pause', null);

        if (this.config.show_captions) {
            this.trigger('videoCaption.pause', null);
        }
    }

    function onPlay() {
        this.videoPlayer.log(
            'play_video',
            {
                'currentTime': this.videoPlayer.currentTime
            }
        );

        if (!this.videoPlayer.updateInterval) {
            this.videoPlayer.updateInterval = setInterval(this.videoPlayer.update, 200);
        }

        this.trigger('videoControl.play', null);

        if (this.config.show_captions) {
            this.trigger('videoCaption.play', null);
        }
    }

    function onUnstarted() { }

    function handlePlaybackQualityChange(value) {
        this.videoPlayer.player.setPlaybackQuality(value);
    }

    function onPlaybackQualityChange() {
        var quality;

        quality = this.videoPlayer.player.getPlaybackQuality();

        this.trigger('videoQualityControl.onQualityChange', quality);
    }

    function onReady() {
        var availablePlaybackRates, baseSpeedSubs, _this;

        this.videoPlayer.log('load_video');

        availablePlaybackRates = this.videoPlayer.player
                                    .getAvailablePlaybackRates();

        // Because of problems with muting sound outside of range 0.25 and
        // 5.0, we should filter our available playback rates.
        // Issues:
        //   https://code.google.com/p/chromium/issues/detail?id=264341
        //   https://bugzilla.mozilla.org/show_bug.cgi?id=840745
        //   https://developer.mozilla.org/en-US/docs/DOM/HTMLMediaElement

        availablePlaybackRates = _.filter(availablePlaybackRates, function(item){
            var speed = Number(item);
            return  speed > 0.25 && speed <= 5;
        });

        if ((this.currentPlayerMode === 'html5') && (this.videoType === 'youtube')) {
            if (availablePlaybackRates.length === 1) {
                // This condition is needed in cases when Firefox version is less than 20. In those versions
                // HTML5 playback could only happen at 1 speed (no speed changing). Therefore, in this case,
                // we need to switch back to Flash.
                //
                // This might also happen in other browsers, therefore when we have 1 speed available, we fall
                // back to Flash.

                _restartUsingFlash(this);

                return;
            } else if (availablePlaybackRates.length > 1) {
                // We need to synchronize available frame rates with the ones
                // that the user specified.

                baseSpeedSubs = this.videos['1.0'];
                _this = this;
                // this.videos is a dictionary containing various frame rates
                // and their associated subs.

                // First clear the dictionary.
                $.each(this.videos, function(index, value) {
                    delete _this.videos[index];
                });
                this.speeds = [];
                // Recreate it with the supplied frame rates.
                $.each(availablePlaybackRates, function(index, value) {
                    _this.videos[value.toFixed(2).replace(/\.00$/, '.0')] = baseSpeedSubs;

                    _this.speeds.push(value.toFixed(2).replace(/\.00$/, '.0'));
                });

                this.trigger('videoSpeedControl.reRender', {'newSpeeds': this.speeds, 'currentSpeed': this.speed});

                this.setSpeed($.cookie('video_speed'));
            }
        }

        if (this.currentPlayerMode === 'html5') {
            this.videoPlayer.player.setPlaybackRate(this.speed);
        }

        if (!onTouchBasedDevice() && $('.video:first').data('autoplay') === 'True') {
            this.videoPlayer.play();
        }
    }

    function onStateChange(event) {
        switch (event.data) {
            case this.videoPlayer.PlayerState.UNSTARTED:
                this.videoPlayer.onUnstarted();
                break;
            case this.videoPlayer.PlayerState.PLAYING:
                this.videoPlayer.onPlay();
                break;
            case this.videoPlayer.PlayerState.PAUSED:
                this.videoPlayer.onPause();
                break;
            case this.videoPlayer.PlayerState.ENDED:
                this.videoPlayer.onEnded();
                break;
        }
    }

    function updatePlayTime(time) {
        var duration;

        duration = this.videoPlayer.duration();

        this.trigger('videoProgressSlider.updatePlayTime', {'time': time, 'duration': duration});
        this.trigger('videoControl.updateVcrVidTime', {'time': time, 'duration': duration});
        this.trigger('videoCaption.updatePlayTime', time);
    }

    function isPlaying() {
        return this.videoPlayer.player.getPlayerState() === this.videoPlayer.PlayerState.PLAYING;
    }

    function duration() {
        var dur;

        dur = this.videoPlayer.player.getDuration();
        if (!isFinite(dur)) {
            dur = this.getDuration();
        }

        return dur;
    }

    function log(eventName, data) {
        var logInfo;

        // Default parameters that always get logged.
        logInfo = {
            'id':   this.id,
            'code': this.youtubeId()
        };

        // If extra parameters were passed to the log.
        if (data) {
            $.each(data, function(paramName, value) {
                logInfo[paramName] = value;
            });
        }

        if (this.videoType === 'youtube') {
            logInfo.code = this.youtubeId();
        } else  if (this.videoType === 'html5') {
                logInfo.code = 'html5';
        }

        Logger.log(eventName, logInfo);
    }

    function onVolumeChange(volume) {
        this.videoPlayer.player.setVolume(volume);
    }
});

}(RequireJS.requirejs, RequireJS.require, RequireJS.define));
<|endoftext|>