<commit_before>/*global document, getComputedStyle */

import { meta, Class } from '../../core/Core.js';
import { lookupKey } from '../../dom/DOMEvent.js';
import { create as el, nearest } from '../../dom/Element.js';
import { browser } from '../../ua/UA.js';
import { ScrollView } from '../containers/ScrollView.js';
import { AbstractInputView } from './AbstractInputView.js';

import /* { property, nocache, on, observes } from */ '../../foundation/Decorators.js';

const isFirefox = browser === 'firefox';

/**
    Class: O.TextInputView

    Extends: O.AbstractInputView

    A text input control. The `value` property is two-way bindable, representing
    the input text.
*/
const TextInputView = Class({
    Name: 'TextInputView',

    Extends: AbstractInputView,

    init: function (/* ...mixins */) {
        TextInputView.parent.constructor.apply(this, arguments);
        this._settingFromInput = false;
        this._verticalBorderWidth = 0;
    },

    /**
        Property: O.TextInputView#isMultiline
        Type: Boolean
        Default: false

        If set to true, the text field will accept line breaks.

        This property *must not* be changed after the view has been rendered.
    */
    isMultiline: false,

    /**
        Property: O.TextInputView#isExpanding
        Type: Boolean
        Default: false

        If <#isMultiline> is set to true, setting <#isExpanding> to true will
        make it automatically expand vertically to fit its contents, rather than
        show a scrollbar.
    */
    isExpanding: false,

    /**
        Property: O.TextInputView#isHighlighted
        Type: Boolean
        Default: false

        If true, a `highlight` class will be added to the view's class name.
        This is a styling hook for highlighting the view, e.g. if it fails
        validation.
    */
    isHighlighted: false,

    /**
        Property: O.TextInputView#inputType
        Type: String
        Default: "text"

        The type property for the <input> DOM node (e.g. "password", "tel" etc.)

        This property *must not* be changed after the view has been rendered.
    */
    inputType: 'text',

    /**
        Property: O.TextInputView#placeholder
        Type: String
        Default: undefined

        Placeholder text to be displayed in the text input when it is empty.
    */
    placeholder: undefined,

    /**
        Property: O.TextInputView#value
        Type: String
        Default: ''

        The value currently input in the text field.
    */
    value: '',

    /**
        Property: O.TextInputView#selection
        Type: Object

        When used as a getter, this will return an object with two properties:

        start - {Number} The number of characters offset from the beginning of
                the text that the selection starts.
        end   - {Number} The number of characters offset from the beginning of
                the text that the selection ends.

        Note, if there is no selection, the start and end values will be the
        same, and give the position of the cursor.

        When used as a setter, you can give it an object as described above to
        set the selection, or if you just want to give it a cursor position, you
        can pass a number instead.

        Note, this property is *not observable* and cannot be used to monitor
        changes in selection/cursor position.

    */
    selection: function (selection) {
        const control = this._domControl;
        const isNumber = typeof selection === 'number';
        let start = selection ? (isNumber ? selection : selection.start) : 0;
        let end = selection
            ? isNumber
                ? selection
                : selection.end || start
            : start;
        if (selection !== undefined) {
            // Ensure any value changes have been drawn.
            this.redraw();
            // Firefox will throw an error if the control is not actually in the
            // document when trying to set the selection. There might be other
            // situations where it does so as well, so just using a try/catch to
            // guard against all.
            try {
                control.setSelectionRange(start, end);
            } catch (error) {}
        } else {
            // Firefox sometimes throws an error if you try to read the
            // selection. Again, probably if the control is not actually in the
            // document.
            try {
                start = control.selectionStart;
                end = control.selectionEnd;
            } catch (error) {}
        }
        return (
            selection || {
                start,
                end,
            }
        );
    }
        .property()
        .nocache(),

    /**
        Property: O.TextInputView#blurOnKeys
        Type: Object
        Default: { Escape: true }

        For each truthy value in the object, if the user is focused in the
        text view and hits the key, the focus will be removed.
    */
    blurOnKeys: { Escape: true },

    // --- Render ---

    baseClassName: 'v-TextInput',

    /**
        Property: O.TextInputView#className
        Type: String

        Overrides default in <O.View#className>. Will have the class `v-TextInput`,
        and any classes given in the <#type> property, along with the following
        other class names dependent on state:

        is-highlight - The <#isHighlighted> property is true.
        is-focused  - The <#isFocused> property is true.
        is-invalid   - The <#isValid> property is false.
        is-disabled  - The <#isDisabled> property is true.
    */
    className: function () {
        const type = this.get('type');
        return (
            'v-TextInput' +
            (this.get('isExpanding') ? ' v-TextInput--expanding' : '') +
            (this.get('isMultiline') ? ' v-TextInput--multiline' : '') +
            (this.get('isHighlighted') ? ' is-highlighted' : '') +
            (this.get('isFocused') ? ' is-focused' : '') +
            (this.get('isValid') ? '' : ' is-invalid') +
            (this.get('isDisabled') ? ' is-disabled' : '') +
            (type ? ' ' + type : '')
        );
    }.property(
        'type',
        'isExpanding',
        'isHighlighted',
        'isFocused',
        'isValid',
        'isDisabled',
    ),

    drawControl() {
        const isMultiline = this.get('isMultiline');
        return (this._domControl = el(isMultiline ? 'textarea' : 'input', {
            id: this.get('id') + '-input',
            className: this.get('baseClassName') + '-input',
            rows: isMultiline ? '1' : undefined,
            name: this.get('name'),
            type: this.get('inputType'),
            disabled: this.get('isDisabled'),
            tabIndex: this.get('tabIndex'),
            placeholder: this.get('placeholder') || undefined,
            value: this.get('value'),
        }));
    },

    // --- Keep render in sync with state ---

    /**
        Method: O.TextInputView#textNeedsRedraw

        Calls <O.View#propertyNeedsRedraw> for extra properties requiring
        redraw.
    */
    textNeedsRedraw: function (self, property, oldValue) {
        const isValue = property === 'value';
        if (!isValue || !this._settingFromInput) {
            this.propertyNeedsRedraw(self, property, oldValue);
        }
        if (isValue && this.get('isExpanding')) {
            this.propertyNeedsRedraw(self, 'textHeight', oldValue);
        }
    }.observes('value', 'isExpanding', 'placeholder'),

    /**
        Method: O.TextInputView#redrawPlaceholder

        Updates the placeholder text in the DOM when the <#placeholder> property
        changes.
    */
    redrawPlaceholder() {
        this._domControl.placeholder = this.get('placeholder');
    },

    redrawTextHeight() {
        // Firefox gets pathologically slow when resizing really large text
        // areas, so automatically turn this off in such a case.
        // 2^13 chars is an arbitrary cut off point that seems to be reasonable
        // in practice
        if (isFirefox && (this.get('value') || '').length > 8192) {
            this.set('isExpanding', false);
            return;
        }
        const control = this._domControl;
        const style = control.style;
        const scrollView = this.getParent(ScrollView);
        // Set to auto to collapse it back to one line, otherwise it would
        // never shrink if you delete text.
        style.height = 'auto';
        const scrollHeight = control.scrollHeight;
        // Presto returns 0 immediately after appending to doc.
        if (scrollHeight) {
            style.height = this._verticalBorderWidth + scrollHeight + 'px';
        }
        // Collapsing the height will mess with the scroll, so make sure we
        // reset the scroll position back to what it was.
        if (scrollView) {
            scrollView.redrawScroll();
        }
    },

    redrawIsExpanding() {
        if (this.get('isExpanding')) {
            this.redrawTextHeight();
        } else {
            this._domControl.style.height = 'auto';
            // Scroll to cursor
            if (this.get('isFocused')) {
                this.blur().focus();
            }
        }
    },

    // --- Activate ---

    selectAll() {
        return this.set('selection', {
            start: 0,
            end: this.get('value').length,
        });
    },

    copySelectionToClipboard() {
        let focused = null;
        if (!this.get('isFocused')) {
            focused = document.activeElement;
            this.focus();
        }
        let didSucceed = false;
        try {
            didSucceed = document.execCommand('copy');
        } catch (error) {}
        if (focused) {
            focused.focus();
        }
        return didSucceed;
    },

    // --- Scrolling and focus ---

    savedSelection: null,

    /**
        Method: O.TextInputView#didEnterDocument

        Overridden to restore scroll position and selection. See
        <O.View#didEnterDocument>.
    */
    didEnterDocument() {
        TextInputView.parent.didEnterDocument.call(this);
        if (this.get('isMultiline')) {
            if (this.get('isExpanding')) {
                const style = getComputedStyle(this._domControl);
                if (style.boxSizing === 'border-box') {
                    this._verticalBorderWidth =
                        parseInt(style.borderTopWidth, 10) +
                        parseInt(style.borderBottomWidth, 10);
                }
                this.redrawTextHeight();
            }
            // Restore scroll positions:
            const control = this._domControl;
            const left = this.get('scrollLeft');
            const top = this.get('scrollTop');
            if (left) {
                control.scrollLeft = left;
            }
            if (top) {
                control.scrollTop = top;
            }
            control.addEventListener('scroll', this, false);
        }
        const selection = this.get('savedSelection');
        if (selection) {
            this.set('selection', selection).focus();
            this.set('savedSelection', null);
        }
        return this;
    },

    /**
        Method: O.TextInputView#willLeaveDocument

        Overridden to save scroll position and selection. See
        <O.View#willLeaveDocument>.
    */
    willLeaveDocument() {
        // If focused, save cursor position
        if (this.get('isFocused')) {
            this.set('savedSelection', this.get('selection'));
            this.blur();
        }
        // Stop listening for scrolls:
        if (this.get('isMultiline')) {
            this._domControl.removeEventListener('scroll', this, false);
        }
        return TextInputView.parent.willLeaveDocument.call(this);
    },

    /**
        Method (private): O.TextInputView#_syncBackScrolls

        Sets the <O.View#scrollLeft> and <O.View#scrollTop> properties whenever
        the user scrolls the textarea.

        Parameters:
            event - {Event} The scroll event.
    */
    _syncBackScrolls: function (event) {
        const control = this._domControl;
        const left = control.scrollLeft;
        const top = control.scrollTop;

        this.beginPropertyChanges()
            .set('scrollLeft', left)
            .set('scrollTop', top)
            .endPropertyChanges();

        event.stopPropagation();
    }.on('scroll'),

    // --- Keep state in sync with render ---

    /**
        Method: O.TextInputView#syncBackValue

        Updates the <#value> property when the user interacts with the textarea.

        Parameters:
            event - {Event} The input event.
    */
    syncBackValue: function () {
        this._settingFromInput = true;
        this.set('value', this._domControl.value);
        this._settingFromInput = false;
    }.on('input'),

    /**
        Method (private): O.TextInputView#_onClick

        Focus and set selection to the end.

        Parameters:
            event - {Event} The click event.
    */
    _onClick: function (event) {
        if (event.target === this.get('layer')) {
            this.set('selection', this.get('value').length).focus();
        }
    }.on('click'),

    /**
        Method (private): O.TextInputView#_onKeypress

        Stop IE automatically focussing the nearest button when the user hits
        enter in single line text inputs.

        Parameters:
            event - {Event} The keypress event.
    */
    _onKeypress: function (event) {
        // If key == enter, IE will automatically focus the nearest button
        // (presumably as though it were submitting the form). Stop this
        // unless we're actually in a form.
        if (
            !this.get('isMultiline') &&
            lookupKey(event, true) === 'Enter' &&
            !nearest(this.get('layer'), 'FORM')
        ) {
            event.preventDefault();
        }
    }.on('keypress'),

    /**
        Method (private): O.TextInputView#_blurOnKey

        Blur the text area when the user hits certain keys, provided by the
        <#blurOnKeys> property.

        Parameters:
            event - {Event} The keyup event.
    */
    _blurOnKey: function (event) {
        const key = lookupKey(event, true);
        if (this.get('blurOnKeys')[key]) {
            this.blur();
        }
    }.on('keyup'),
});

/* Don't redraw when the user is typing; we handle redrawing value in
   textNeedsRedraw so remove the abstractInputView observer */
meta(TextInputView.prototype).removeObserver('value', {
    object: null,
    method: 'abstractInputNeedsRedraw',
});

export { TextInputView };
<commit_msg>Follow userDidInput pattern in TextInputView<commit_after>/*global document, getComputedStyle */

import { meta, Class } from '../../core/Core.js';
import { lookupKey } from '../../dom/DOMEvent.js';
import { create as el, nearest } from '../../dom/Element.js';
import { browser } from '../../ua/UA.js';
import { ScrollView } from '../containers/ScrollView.js';
import { AbstractInputView } from './AbstractInputView.js';

import /* { property, nocache, on, observes } from */ '../../foundation/Decorators.js';

const isFirefox = browser === 'firefox';

/**
    Class: O.TextInputView

    Extends: O.AbstractInputView

    A text input control. The `value` property is two-way bindable, representing
    the input text.
*/
const TextInputView = Class({
    Name: 'TextInputView',

    Extends: AbstractInputView,

    init: function (/* ...mixins */) {
        TextInputView.parent.constructor.apply(this, arguments);
        this._settingFromInput = false;
        this._verticalBorderWidth = 0;
    },

    /**
        Property: O.TextInputView#isMultiline
        Type: Boolean
        Default: false

        If set to true, the text field will accept line breaks.

        This property *must not* be changed after the view has been rendered.
    */
    isMultiline: false,

    /**
        Property: O.TextInputView#isExpanding
        Type: Boolean
        Default: false

        If <#isMultiline> is set to true, setting <#isExpanding> to true will
        make it automatically expand vertically to fit its contents, rather than
        show a scrollbar.
    */
    isExpanding: false,

    /**
        Property: O.TextInputView#isHighlighted
        Type: Boolean
        Default: false

        If true, a `highlight` class will be added to the view's class name.
        This is a styling hook for highlighting the view, e.g. if it fails
        validation.
    */
    isHighlighted: false,

    /**
        Property: O.TextInputView#inputType
        Type: String
        Default: "text"

        The type property for the <input> DOM node (e.g. "password", "tel" etc.)

        This property *must not* be changed after the view has been rendered.
    */
    inputType: 'text',

    /**
        Property: O.TextInputView#placeholder
        Type: String
        Default: undefined

        Placeholder text to be displayed in the text input when it is empty.
    */
    placeholder: undefined,

    /**
        Property: O.TextInputView#value
        Type: String
        Default: ''

        The value currently input in the text field.
    */
    value: '',

    /**
        Property: O.TextInputView#selection
        Type: Object

        When used as a getter, this will return an object with two properties:

        start - {Number} The number of characters offset from the beginning of
                the text that the selection starts.
        end   - {Number} The number of characters offset from the beginning of
                the text that the selection ends.

        Note, if there is no selection, the start and end values will be the
        same, and give the position of the cursor.

        When used as a setter, you can give it an object as described above to
        set the selection, or if you just want to give it a cursor position, you
        can pass a number instead.

        Note, this property is *not observable* and cannot be used to monitor
        changes in selection/cursor position.

    */
    selection: function (selection) {
        const control = this._domControl;
        const isNumber = typeof selection === 'number';
        let start = selection ? (isNumber ? selection : selection.start) : 0;
        let end = selection
            ? isNumber
                ? selection
                : selection.end || start
            : start;
        if (selection !== undefined) {
            // Ensure any value changes have been drawn.
            this.redraw();
            // Firefox will throw an error if the control is not actually in the
            // document when trying to set the selection. There might be other
            // situations where it does so as well, so just using a try/catch to
            // guard against all.
            try {
                control.setSelectionRange(start, end);
            } catch (error) {}
        } else {
            // Firefox sometimes throws an error if you try to read the
            // selection. Again, probably if the control is not actually in the
            // document.
            try {
                start = control.selectionStart;
                end = control.selectionEnd;
            } catch (error) {}
        }
        return (
            selection || {
                start,
                end,
            }
        );
    }
        .property()
        .nocache(),

    /**
        Property: O.TextInputView#blurOnKeys
        Type: Object
        Default: { Escape: true }

        For each truthy value in the object, if the user is focused in the
        text view and hits the key, the focus will be removed.
    */
    blurOnKeys: { Escape: true },

    // --- Render ---

    baseClassName: 'v-TextInput',

    /**
        Property: O.TextInputView#className
        Type: String

        Overrides default in <O.View#className>. Will have the class `v-TextInput`,
        and any classes given in the <#type> property, along with the following
        other class names dependent on state:

        is-highlight - The <#isHighlighted> property is true.
        is-focused  - The <#isFocused> property is true.
        is-invalid   - The <#isValid> property is false.
        is-disabled  - The <#isDisabled> property is true.
    */
    className: function () {
        const type = this.get('type');
        return (
            'v-TextInput' +
            (this.get('isExpanding') ? ' v-TextInput--expanding' : '') +
            (this.get('isMultiline') ? ' v-TextInput--multiline' : '') +
            (this.get('isHighlighted') ? ' is-highlighted' : '') +
            (this.get('isFocused') ? ' is-focused' : '') +
            (this.get('isValid') ? '' : ' is-invalid') +
            (this.get('isDisabled') ? ' is-disabled' : '') +
            (type ? ' ' + type : '')
        );
    }.property(
        'type',
        'isExpanding',
        'isHighlighted',
        'isFocused',
        'isValid',
        'isDisabled',
    ),

    drawControl() {
        const isMultiline = this.get('isMultiline');
        return (this._domControl = el(isMultiline ? 'textarea' : 'input', {
            id: this.get('id') + '-input',
            className: this.get('baseClassName') + '-input',
            rows: isMultiline ? '1' : undefined,
            name: this.get('name'),
            type: this.get('inputType'),
            disabled: this.get('isDisabled'),
            tabIndex: this.get('tabIndex'),
            placeholder: this.get('placeholder') || undefined,
            value: this.get('value'),
        }));
    },

    // --- Keep render in sync with state ---

    /**
        Method: O.TextInputView#textNeedsRedraw

        Calls <O.View#propertyNeedsRedraw> for extra properties requiring
        redraw.
    */
    textNeedsRedraw: function (self, property, oldValue) {
        const isValue = property === 'value';
        if (!isValue || !this._settingFromInput) {
            this.propertyNeedsRedraw(self, property, oldValue);
        }
        if (isValue && this.get('isExpanding')) {
            this.propertyNeedsRedraw(self, 'textHeight', oldValue);
        }
    }.observes('value', 'isExpanding', 'placeholder'),

    /**
        Method: O.TextInputView#redrawPlaceholder

        Updates the placeholder text in the DOM when the <#placeholder> property
        changes.
    */
    redrawPlaceholder() {
        this._domControl.placeholder = this.get('placeholder');
    },

    redrawTextHeight() {
        // Firefox gets pathologically slow when resizing really large text
        // areas, so automatically turn this off in such a case.
        // 2^13 chars is an arbitrary cut off point that seems to be reasonable
        // in practice
        if (isFirefox && (this.get('value') || '').length > 8192) {
            this.set('isExpanding', false);
            return;
        }
        const control = this._domControl;
        const style = control.style;
        const scrollView = this.getParent(ScrollView);
        // Set to auto to collapse it back to one line, otherwise it would
        // never shrink if you delete text.
        style.height = 'auto';
        const scrollHeight = control.scrollHeight;
        // Presto returns 0 immediately after appending to doc.
        if (scrollHeight) {
            style.height = this._verticalBorderWidth + scrollHeight + 'px';
        }
        // Collapsing the height will mess with the scroll, so make sure we
        // reset the scroll position back to what it was.
        if (scrollView) {
            scrollView.redrawScroll();
        }
    },

    redrawIsExpanding() {
        if (this.get('isExpanding')) {
            this.redrawTextHeight();
        } else {
            this._domControl.style.height = 'auto';
            // Scroll to cursor
            if (this.get('isFocused')) {
                this.blur().focus();
            }
        }
    },

    // --- Activate ---

    selectAll() {
        return this.set('selection', {
            start: 0,
            end: this.get('value').length,
        });
    },

    copySelectionToClipboard() {
        let focused = null;
        if (!this.get('isFocused')) {
            focused = document.activeElement;
            this.focus();
        }
        let didSucceed = false;
        try {
            didSucceed = document.execCommand('copy');
        } catch (error) {}
        if (focused) {
            focused.focus();
        }
        return didSucceed;
    },

    // --- Scrolling and focus ---

    savedSelection: null,

    /**
        Method: O.TextInputView#didEnterDocument

        Overridden to restore scroll position and selection. See
        <O.View#didEnterDocument>.
    */
    didEnterDocument() {
        TextInputView.parent.didEnterDocument.call(this);
        if (this.get('isMultiline')) {
            if (this.get('isExpanding')) {
                const style = getComputedStyle(this._domControl);
                if (style.boxSizing === 'border-box') {
                    this._verticalBorderWidth =
                        parseInt(style.borderTopWidth, 10) +
                        parseInt(style.borderBottomWidth, 10);
                }
                this.redrawTextHeight();
            }
            // Restore scroll positions:
            const control = this._domControl;
            const left = this.get('scrollLeft');
            const top = this.get('scrollTop');
            if (left) {
                control.scrollLeft = left;
            }
            if (top) {
                control.scrollTop = top;
            }
            control.addEventListener('scroll', this, false);
        }
        const selection = this.get('savedSelection');
        if (selection) {
            this.set('selection', selection).focus();
            this.set('savedSelection', null);
        }
        return this;
    },

    /**
        Method: O.TextInputView#willLeaveDocument

        Overridden to save scroll position and selection. See
        <O.View#willLeaveDocument>.
    */
    willLeaveDocument() {
        // If focused, save cursor position
        if (this.get('isFocused')) {
            this.set('savedSelection', this.get('selection'));
            this.blur();
        }
        // Stop listening for scrolls:
        if (this.get('isMultiline')) {
            this._domControl.removeEventListener('scroll', this, false);
        }
        return TextInputView.parent.willLeaveDocument.call(this);
    },

    /**
        Method (private): O.TextInputView#_syncBackScrolls

        Sets the <O.View#scrollLeft> and <O.View#scrollTop> properties whenever
        the user scrolls the textarea.

        Parameters:
            event - {Event} The scroll event.
    */
    _syncBackScrolls: function (event) {
        const control = this._domControl;
        const left = control.scrollLeft;
        const top = control.scrollTop;

        this.beginPropertyChanges()
            .set('scrollLeft', left)
            .set('scrollTop', top)
            .endPropertyChanges();

        event.stopPropagation();
    }.on('scroll'),

    // --- Keep state in sync with render ---

    /**
        Method: O.TextInputView#syncBackValue

        Updates the <#value> property when the user interacts with the textarea.

        Parameters:
            event - {Event} The input event.
    */
    syncBackValue: function () {
        this._settingFromInput = true;
        this.userDidInput(this._domControl.value);
        this._settingFromInput = false;
    }.on('input'),

    /**
        Method (private): O.TextInputView#_onClick

        Focus and set selection to the end.

        Parameters:
            event - {Event} The click event.
    */
    _onClick: function (event) {
        if (event.target === this.get('layer')) {
            this.set('selection', this.get('value').length).focus();
        }
    }.on('click'),

    /**
        Method (private): O.TextInputView#_onKeypress

        Stop IE automatically focussing the nearest button when the user hits
        enter in single line text inputs.

        Parameters:
            event - {Event} The keypress event.
    */
    _onKeypress: function (event) {
        // If key == enter, IE will automatically focus the nearest button
        // (presumably as though it were submitting the form). Stop this
        // unless we're actually in a form.
        if (
            !this.get('isMultiline') &&
            lookupKey(event, true) === 'Enter' &&
            !nearest(this.get('layer'), 'FORM')
        ) {
            event.preventDefault();
        }
    }.on('keypress'),

    /**
        Method (private): O.TextInputView#_blurOnKey

        Blur the text area when the user hits certain keys, provided by the
        <#blurOnKeys> property.

        Parameters:
            event - {Event} The keyup event.
    */
    _blurOnKey: function (event) {
        const key = lookupKey(event, true);
        if (this.get('blurOnKeys')[key]) {
            this.blur();
        }
    }.on('keyup'),
});

/* Don't redraw when the user is typing; we handle redrawing value in
   textNeedsRedraw so remove the abstractInputView observer */
meta(TextInputView.prototype).removeObserver('value', {
    object: null,
    method: 'abstractInputNeedsRedraw',
});

export { TextInputView };
<|endoftext|>


<commit_before>define( [
    'osg/Notify',
    'osg/Quat'
], function ( Notify, Quat ) {

    'use strict';

    var Oculus = function ( viewer ) {
        this._viewer = viewer;
        this._type = 'Oculus';
        this._enable = true;
        this._hmd = undefined;
        this._sensor = undefined;
        this._quat = Quat.create();
    };

    Oculus.prototype = {

        init: function () {

            var self = this;

            function vrDeviceCallback( vrDevices ) {

                var i;

                // First, find a HMD -- just use the first one we find
                for ( i = 0; i < vrDevices.length; ++i ) {
                    if ( vrDevices[ i ] instanceof window.HMDVRDevice ) {
                        self._hmd = vrDevices[ i ];
                        break;
                    }
                }

                // Then, find a sensor corresponding to the same hardwareUnitId
                for ( i = 0; i < vrDevices.length; ++i ) {
                    if ( vrDevices[ i ] instanceof window.PositionSensorVRDevice && vrDevices[ i ].hardwareUnitId === self._hmd.hardwareUnitId ) {
                        self._sensor = vrDevices[ i ];
                        break;
                    }
                }

                if ( self._hmd && self._sensor )
                    Notify.log( 'Found a HMD and Sensor' );
            }

            if ( navigator.getVRDevices ) {
                navigator.getVRDevices().then( vrDeviceCallback );
            } else if ( navigator.mozGetVRDevices ) {
                navigator.mozGetVRDevices( vrDeviceCallback );
            }

        },

        getManipulatorController: function () {
            return this._viewer.getManipulator().getControllerList()[ this._type ];
        },

        isValid: function () {
            if ( !this._enable )
                return false;

            var manipulator = this._viewer.getManipulator();
            if ( !manipulator )
                return false;

            if ( !manipulator.getControllerList()[ this._type ] )
                return false;

            if ( !this._hmd || !this._sensor )
                return false;

            return true;
        },

        update: function () {

            if ( !this.isValid() )
                return;

            var manipulatorAdapter = this.getManipulatorController();

            // update the manipulator with the rotation of the device
            if ( manipulatorAdapter.update ) {

                var quat = this._sensor.getState().orientation;

                // If no real oculus is detected, navigators (vr builds of FF and Chrome) simulate a fake oculus
                // On firefox, this fake oculus returns a wrong quaternion: [0, 0, 0, 0]
                // So we detect and set this quaternion to a neutral value: [0, 0, 0, 1]
                if ( quat.x === 0.0 && quat.y === 0.0 && quat.y === 0.0 && quat.w === 0.0 )
                    quat.w = 1.0;

                // On oculus the up vector is [0,1,0]
                // On osgjs the up vector is [0,0,1]
                this._quat[ 0 ] = quat.x;
                this._quat[ 1 ] = -quat.z;
                this._quat[ 2 ] = quat.y;
                this._quat[ 3 ] = quat.w;

                manipulatorAdapter.update( this._quat );
            }
        },

        getHmd: function() {
            return this._hmd;
        }
    };
    return Oculus;
} );
<commit_msg>Fixes the vr support<commit_after>define( [
    'osg/Notify',
    'osg/Quat'
], function ( Notify, Quat ) {

    'use strict';

    var Oculus = function ( viewer ) {
        this._viewer = viewer;
        this._type = 'Oculus';
        this._enable = true;
        this._hmd = undefined;
        this._sensor = undefined;
        this._quat = Quat.create();
    };

    Oculus.prototype = {

        init: function () {

            var self = this;

            function vrDeviceCallback( vrDevices ) {

                var i;

                // First, find a HMD -- just use the first one we find
                for ( i = 0; i < vrDevices.length; ++i ) {
                    if ( vrDevices[ i ] instanceof window.HMDVRDevice ) {
                        self._hmd = vrDevices[ i ];
                        break;
                    }
                }

                // Then, find a sensor corresponding to the same hardwareUnitId
                for ( i = 0; i < vrDevices.length; ++i ) {
                    if ( vrDevices[ i ] instanceof window.PositionSensorVRDevice && vrDevices[ i ].hardwareUnitId === self._hmd.hardwareUnitId ) {
                        self._sensor = vrDevices[ i ];
                        break;
                    }
                }

                if ( self._hmd && self._sensor )
                    Notify.log( 'Found a HMD and Sensor' );
            }

            if ( navigator.getVRDevices ) {
                navigator.getVRDevices().then( vrDeviceCallback );
            } else if ( navigator.mozGetVRDevices ) {
                navigator.mozGetVRDevices( vrDeviceCallback );
            }

        },

        getManipulatorController: function () {
            return this._viewer.getManipulator().getControllerList()[ this._type ];
        },

        isValid: function () {
            if ( !this._enable )
                return false;

            var manipulator = this._viewer.getManipulator();
            if ( !manipulator )
                return false;

            if ( !manipulator.getControllerList()[ this._type ] )
                return false;

            if ( !this._hmd || !this._sensor )
                return false;

            return true;
        },

        update: function () {

            if ( !this.isValid() )
                return;

            var manipulatorAdapter = this.getManipulatorController();

            // update the manipulator with the rotation of the device
            if ( manipulatorAdapter.update ) {

                var quat = this._sensor.getState().orientation;

                // If no real oculus is detected, navigators (vr builds of FF and Chrome) simulate a fake oculus
                // On firefox, this fake oculus returns a wrong quaternion: [0, 0, 0, 0]
                // So we detect and set this quaternion to a neutral value: [0, 0, 0, 1]
                // UPDATE : it looks like that sometimes no quaternion is returned
                if ( !quat ) {
                    Quat.init( this._quat );
                } else {
                    if ( quat.x === 0.0 && quat.y === 0.0 && quat.y === 0.0 && quat.w === 0.0 )
                        quat.w = 1.0;
                    // On oculus the up vector is [0,1,0]
                    // On osgjs the up vector is [0,0,1]
                    this._quat[ 0 ] = quat.x;
                    this._quat[ 1 ] = -quat.z;
                    this._quat[ 2 ] = quat.y;
                    this._quat[ 3 ] = quat.w;
                }

                manipulatorAdapter.update( this._quat );
            }
        },

        getHmd: function () {
            return this._hmd;
        }
    };
    return Oculus;
} );
<|endoftext|>


<commit_before>function itImplementsContracts(PromiseLibrary) {
  describe("contracts", function() {
    var PromiseClass, PromiseWrapper, getDeferred;
    var fulfilledHandler1, fulfilledHandler2, errorHandler, progressHandler, promise1, promise2;
    beforeEach(function() {
      PromiseClass = PromiseLibrary.PromiseClass;
      PromiseWrapper = PromiseLibrary.PromiseWrapper;
      getDeferred = PromiseLibrary.getDeferred;
      PromiseClass = mockPromises.getMockPromise(PromiseClass);
      fulfilledHandler1 = jasmine.createSpy("fullfilled1");
      fulfilledHandler2 = jasmine.createSpy("fullfilled2");
      errorHandler = jasmine.createSpy("error");
      progressHandler = jasmine.createSpy("progress");
      promise1 = PromiseWrapper("foo");
      promise2 = PromiseWrapper("bar");
      promise1.then(fulfilledHandler1, errorHandler, progressHandler);
      promise2.then(fulfilledHandler2);
    });
    describe("all", function() {
      it("returns a list of promise/handler objects", function() {
        expect(mockPromises.contracts.all()).toEqual([
          jasmine.objectContaining({promise: promise1, fulfilledHandler: fulfilledHandler1, errorHandler: errorHandler, progressHandler: progressHandler}),
          jasmine.objectContaining({promise: promise2, fulfilledHandler: fulfilledHandler2})
        ]);
      });
    });
    describe("forPromise", function() {
      it("returns a list of promise/handler objects only for the requested promise", function() {
        expect(mockPromises.contracts.forPromise(promise1)).toEqual([
          jasmine.objectContaining({promise: promise1, fulfilledHandler: fulfilledHandler1, errorHandler: errorHandler, progressHandler: progressHandler})
        ]);
      });
    });

    it("can be reset", function() {
      expect(mockPromises.contracts.all().length).toBeGreaterThan(0);
      mockPromises.reset();
      expect(mockPromises.contracts.all().length).toEqual(0);
    });

    describe("executeForPromise", function() {
      it("calls handlers for that promise synchronously", function() {
        var promisedValue;
        promise1.then(function(value) {
          promisedValue = value;
        });
        promise2.then(function() {
          promisedValue = "also not foo";
        });
        promisedValue = "not foo";
        mockPromises.executeForPromise(promise1);
        expect(promisedValue).toEqual("foo");
      });

      describe("failed promises", function() {
        var deferred, brokenPromise, errorSpy, successSpy;
        beforeEach(function() {
          deferred = getDeferred();
          brokenPromise = deferred.promise;
          successSpy = jasmine.createSpy("success");
          errorSpy = jasmine.createSpy("error");
          deferred.reject("fail");
        });
        it("calls the fail handler if the promise is failed", function() {
          brokenPromise.then(successSpy, errorSpy);
          mockPromises.executeForPromise(brokenPromise);
          expect(successSpy).not.toHaveBeenCalled();
          expect(errorSpy).toHaveBeenCalledWith("fail");
        });
        it("supports 'catch'", function() {
          brokenPromise.catch(errorSpy);
          mockPromises.executeForPromise(brokenPromise);
          expect(errorSpy).toHaveBeenCalledWith("fail");
        });
      });

      it("does not execute handlers more than once", function() {
        var promisedValue = "bar";
        promise1.then(function(value) {
          promisedValue += value;
        });
        mockPromises.executeForPromise(promise1);
        mockPromises.executeForPromise(promise1);
        expect(promisedValue).toEqual("barfoo");
      });

      it("works with nested promises", function() {
        var innerPromise = PromiseWrapper("foo");
        var outerPromisedValue = "not resolved";
        var innerPromisedValue = "not resolved";
        var deferred = getDeferred();
        var outerPromise = deferred.promise;
        innerPromise.then(function(value) {
          innerPromisedValue = value;
          deferred.resolve(value + "bar");
        });
        outerPromise.then(function(value) {
          outerPromisedValue = value;
        });

        mockPromises.executeForPromise(innerPromise);
        expect(innerPromisedValue).toEqual("foo");
        expect(outerPromisedValue).toEqual("not resolved");
        mockPromises.executeForPromise(outerPromise);
        expect(innerPromisedValue).toEqual("foo");
        expect(outerPromisedValue).toEqual("foobar");
      });
    });

    describe("executeForResolvedPromises", function() {
      it("executes handlers for all resolved promises", function() {
        var deferred = getDeferred();
        var unresolvedPromise = deferred.promise;
        var unresolvedSpy = jasmine.createSpy("unresolved");
        unresolvedPromise.then(unresolvedSpy);
        mockPromises.executeForResolvedPromises();
        expect(fulfilledHandler1).toHaveBeenCalled();
        expect(fulfilledHandler2).toHaveBeenCalled();
        expect(unresolvedSpy).not.toHaveBeenCalled();
      });
    });

    describe("executeForPromises", function() {
      it("works for executes the given promises", function() {
        promise1.then(fulfilledHandler1);
        promise2.then(fulfilledHandler2);
        mockPromises.executeForPromises([promise1, promise2]);
        expect(fulfilledHandler1).toHaveBeenCalled();
        expect(fulfilledHandler2).toHaveBeenCalled();
      });
    });

    describe("valueForPromise", function() {
      it("returns the value for resolved promises", function() {
        expect(mockPromises.valueForPromise(promise1)).toEqual("foo");
      });
    });
  });
}

describe("mock promises", function() {
  describe("mocking Q", function() {
    var QLibrary = {};
    beforeEach(function() {
      QLibrary.PromiseClass = Q.makePromise;
      QLibrary.PromiseWrapper = Q;
      QLibrary.getDeferred = function() {
        return Q.defer();
      };
      mockPromises.install(QLibrary.PromiseClass);
      mockPromises.reset();
    });

    afterEach(function() {
      mockPromises.uninstall();
    });

    it("does not allow normal promise resolution when mocking", function(done) {
      var promise = QLibrary.PromiseWrapper("foo");
      var promisedValue;
      promise.then(function(value) {
        promisedValue = value;
      });
      promisedValue = "not foo";
      setTimeout(function() {
        expect(promisedValue).toBe("not foo");
        done();
      }, 1);
    });

    it("can be uninstalled", function(done) {
      mockPromises.uninstall();

      var promise = QLibrary.PromiseWrapper("foo");
      var promisedValue;
      promise.then(function(value) {
        promisedValue = value;
      });
      promisedValue = "not foo";
      setTimeout(function() {
        expect(promisedValue).toBe("foo");
        done();
      }, 1);
    });

    it("maintains that then is chainable", function() {
      var promise = QLibrary.PromiseWrapper("chainThings");
      var chainedReturn = promise.then(function() {
      }).then(function() {
      });
      expect(chainedReturn).toBe(promise);
    });

    itImplementsContracts(QLibrary);
  });

  describe("for native promises", function() {
    var promise1, promise2;

    var nativeLibrary = {};
    beforeEach(function() {
      mockPromises.reset();
      Promise = mockPromises.getMockPromise(Promise);
      nativeLibrary.PromiseClass = Promise;
      nativeLibrary.PromiseWrapper = Promise.resolve
      nativeLibrary.getDeferred = function() {
        var deferred = {};
        var promise = new Promise(function(resolve, reject) {
          deferred.resolve = resolve;
          deferred.reject = reject;
        });
        deferred.promise = promise;
        return deferred;
      };

      promise1 = nativeLibrary.PromiseWrapper("foo");
      promise2 = nativeLibrary.PromiseWrapper("bar");
    });

    afterEach(function() {
      Promise = mockPromises.getOriginalPromise();
      mockPromises.uninstall();
    });

    itImplementsContracts(nativeLibrary);

    describe("Promises.all", function() {
      it("resolves when all of the promises resolve", function() {
        var allPromise = Promise.all([promise1, promise2]);
        var thenSpy = jasmine.createSpy("then");
        allPromise.then(thenSpy);
        mockPromises.executeForPromise(promise1);
        mockPromises.executeForPromise(promise2);
        mockPromises.executeForPromise(allPromise);
        expect(thenSpy).toHaveBeenCalledWith(["foo", "bar"]);
      });
    });

    describe("Promise.reject", function() {
      it("creates a rejected promise", function() {
        var rejectedPromise = Promise.reject("wrong");
        var failSpy = jasmine.createSpy("fail");
        rejectedPromise.catch(failSpy);
        mockPromises.executeForPromise(rejectedPromise);
        expect(failSpy).toHaveBeenCalledWith("wrong");
      });
    });
  });
});
<commit_msg>Add Comment<commit_after>//Tests require Jasmine 2.0+ due to the 'done' function

function itImplementsContracts(PromiseLibrary) {
  describe("contracts", function() {
    var PromiseClass, PromiseWrapper, getDeferred;
    var fulfilledHandler1, fulfilledHandler2, errorHandler, progressHandler, promise1, promise2;
    beforeEach(function() {
      PromiseClass = PromiseLibrary.PromiseClass;
      PromiseWrapper = PromiseLibrary.PromiseWrapper;
      getDeferred = PromiseLibrary.getDeferred;
      PromiseClass = mockPromises.getMockPromise(PromiseClass);
      fulfilledHandler1 = jasmine.createSpy("fullfilled1");
      fulfilledHandler2 = jasmine.createSpy("fullfilled2");
      errorHandler = jasmine.createSpy("error");
      progressHandler = jasmine.createSpy("progress");
      promise1 = PromiseWrapper("foo");
      promise2 = PromiseWrapper("bar");
      promise1.then(fulfilledHandler1, errorHandler, progressHandler);
      promise2.then(fulfilledHandler2);
    });
    describe("all", function() {
      it("returns a list of promise/handler objects", function() {
        expect(mockPromises.contracts.all()).toEqual([
          jasmine.objectContaining({promise: promise1, fulfilledHandler: fulfilledHandler1, errorHandler: errorHandler, progressHandler: progressHandler}),
          jasmine.objectContaining({promise: promise2, fulfilledHandler: fulfilledHandler2})
        ]);
      });
    });
    describe("forPromise", function() {
      it("returns a list of promise/handler objects only for the requested promise", function() {
        expect(mockPromises.contracts.forPromise(promise1)).toEqual([
          jasmine.objectContaining({promise: promise1, fulfilledHandler: fulfilledHandler1, errorHandler: errorHandler, progressHandler: progressHandler})
        ]);
      });
    });

    it("can be reset", function() {
      expect(mockPromises.contracts.all().length).toBeGreaterThan(0);
      mockPromises.reset();
      expect(mockPromises.contracts.all().length).toEqual(0);
    });

    describe("executeForPromise", function() {
      it("calls handlers for that promise synchronously", function() {
        var promisedValue;
        promise1.then(function(value) {
          promisedValue = value;
        });
        promise2.then(function() {
          promisedValue = "also not foo";
        });
        promisedValue = "not foo";
        mockPromises.executeForPromise(promise1);
        expect(promisedValue).toEqual("foo");
      });

      describe("failed promises", function() {
        var deferred, brokenPromise, errorSpy, successSpy;
        beforeEach(function() {
          deferred = getDeferred();
          brokenPromise = deferred.promise;
          successSpy = jasmine.createSpy("success");
          errorSpy = jasmine.createSpy("error");
          deferred.reject("fail");
        });
        it("calls the fail handler if the promise is failed", function() {
          brokenPromise.then(successSpy, errorSpy);
          mockPromises.executeForPromise(brokenPromise);
          expect(successSpy).not.toHaveBeenCalled();
          expect(errorSpy).toHaveBeenCalledWith("fail");
        });
        it("supports 'catch'", function() {
          brokenPromise.catch(errorSpy);
          mockPromises.executeForPromise(brokenPromise);
          expect(errorSpy).toHaveBeenCalledWith("fail");
        });
      });

      it("does not execute handlers more than once", function() {
        var promisedValue = "bar";
        promise1.then(function(value) {
          promisedValue += value;
        });
        mockPromises.executeForPromise(promise1);
        mockPromises.executeForPromise(promise1);
        expect(promisedValue).toEqual("barfoo");
      });

      it("works with nested promises", function() {
        var innerPromise = PromiseWrapper("foo");
        var outerPromisedValue = "not resolved";
        var innerPromisedValue = "not resolved";
        var deferred = getDeferred();
        var outerPromise = deferred.promise;
        innerPromise.then(function(value) {
          innerPromisedValue = value;
          deferred.resolve(value + "bar");
        });
        outerPromise.then(function(value) {
          outerPromisedValue = value;
        });

        mockPromises.executeForPromise(innerPromise);
        expect(innerPromisedValue).toEqual("foo");
        expect(outerPromisedValue).toEqual("not resolved");
        mockPromises.executeForPromise(outerPromise);
        expect(innerPromisedValue).toEqual("foo");
        expect(outerPromisedValue).toEqual("foobar");
      });
    });

    describe("executeForResolvedPromises", function() {
      it("executes handlers for all resolved promises", function() {
        var deferred = getDeferred();
        var unresolvedPromise = deferred.promise;
        var unresolvedSpy = jasmine.createSpy("unresolved");
        unresolvedPromise.then(unresolvedSpy);
        mockPromises.executeForResolvedPromises();
        expect(fulfilledHandler1).toHaveBeenCalled();
        expect(fulfilledHandler2).toHaveBeenCalled();
        expect(unresolvedSpy).not.toHaveBeenCalled();
      });
    });

    describe("executeForPromises", function() {
      it("works for executes the given promises", function() {
        promise1.then(fulfilledHandler1);
        promise2.then(fulfilledHandler2);
        mockPromises.executeForPromises([promise1, promise2]);
        expect(fulfilledHandler1).toHaveBeenCalled();
        expect(fulfilledHandler2).toHaveBeenCalled();
      });
    });

    describe("valueForPromise", function() {
      it("returns the value for resolved promises", function() {
        expect(mockPromises.valueForPromise(promise1)).toEqual("foo");
      });
    });
  });
}

describe("mock promises", function() {
  describe("mocking Q", function() {
    var QLibrary = {};
    beforeEach(function() {
      QLibrary.PromiseClass = Q.makePromise;
      QLibrary.PromiseWrapper = Q;
      QLibrary.getDeferred = function() {
        return Q.defer();
      };
      mockPromises.install(QLibrary.PromiseClass);
      mockPromises.reset();
    });

    afterEach(function() {
      mockPromises.uninstall();
    });

    it("does not allow normal promise resolution when mocking", function(done) {
      var promise = QLibrary.PromiseWrapper("foo");
      var promisedValue;
      promise.then(function(value) {
        promisedValue = value;
      });
      promisedValue = "not foo";
      setTimeout(function() {
        expect(promisedValue).toBe("not foo");
        done();
      }, 1);
    });

    it("can be uninstalled", function(done) {
      mockPromises.uninstall();

      var promise = QLibrary.PromiseWrapper("foo");
      var promisedValue;
      promise.then(function(value) {
        promisedValue = value;
      });
      promisedValue = "not foo";
      setTimeout(function() {
        expect(promisedValue).toBe("foo");
        done();
      }, 1);
    });

    it("maintains that then is chainable", function() {
      var promise = QLibrary.PromiseWrapper("chainThings");
      var chainedReturn = promise.then(function() {
      }).then(function() {
      });
      expect(chainedReturn).toBe(promise);
    });

    itImplementsContracts(QLibrary);
  });

  describe("for native promises", function() {
    var promise1, promise2;

    var nativeLibrary = {};
    beforeEach(function() {
      mockPromises.reset();
      Promise = mockPromises.getMockPromise(Promise);
      nativeLibrary.PromiseClass = Promise;
      nativeLibrary.PromiseWrapper = Promise.resolve
      nativeLibrary.getDeferred = function() {
        var deferred = {};
        var promise = new Promise(function(resolve, reject) {
          deferred.resolve = resolve;
          deferred.reject = reject;
        });
        deferred.promise = promise;
        return deferred;
      };

      promise1 = nativeLibrary.PromiseWrapper("foo");
      promise2 = nativeLibrary.PromiseWrapper("bar");
    });

    afterEach(function() {
      Promise = mockPromises.getOriginalPromise();
      mockPromises.uninstall();
    });

    itImplementsContracts(nativeLibrary);

    describe("Promises.all", function() {
      it("resolves when all of the promises resolve", function() {
        var allPromise = Promise.all([promise1, promise2]);
        var thenSpy = jasmine.createSpy("then");
        allPromise.then(thenSpy);
        mockPromises.executeForPromise(promise1);
        mockPromises.executeForPromise(promise2);
        mockPromises.executeForPromise(allPromise);
        expect(thenSpy).toHaveBeenCalledWith(["foo", "bar"]);
      });
    });

    describe("Promise.reject", function() {
      it("creates a rejected promise", function() {
        var rejectedPromise = Promise.reject("wrong");
        var failSpy = jasmine.createSpy("fail");
        rejectedPromise.catch(failSpy);
        mockPromises.executeForPromise(rejectedPromise);
        expect(failSpy).toHaveBeenCalledWith("wrong");
      });
    });
  });
});
<|endoftext|>


<commit_before>describe("Main Menu View", function() {
});
<commit_msg>Add MainMenu view specs<commit_after>describe("Main Menu View", function() {
	beforeEach(function() {
		this.server = create_fake_server();

		CapoRSS.router = {
			currentSelection: null,
			itemList: {
				moveCursor: function(direction) {}
			}
		};

		this.view = new CapoRSS.View.MainMenu({el: null});
		this.view.render();
	});

	describe("Rendering", function() {
		it("displays menu buttons", function() {
			expect(this.view.$el.find('#syncButton').length).toEqual(1);
			expect(this.view.$el.find('#subscriptionModalButton').length).toEqual(1);
			expect(this.view.$el.find('#settingsModalButton').length).toEqual(1);
			expect(this.view.$el.find('#mobilePrevItem').length).toEqual(1);
			expect(this.view.$el.find('#mobileNextItem').length).toEqual(1);
			expect(this.view.$el.find('#toggleReadVisibility').length).toEqual(1);
		});
	});

	describe("Actions", function() {
		it("initiates a sync when clicking the sync button", function() {
			this.view.$el.find('#syncButton').trigger('click');

			expect(this.server.requests.length).toEqual(1);
			expect(this.server.requests[0].method).toEqual("POST");
			expect(this.server.requests[0].url).toEqual("/sync/all");

			this.server.respond();
		});

		/*
		it("displays the subscription dialog when clicking the subscription button", function() {
		});

		it("displays the settings dialog when clicking the settings button", function() {
		});
		*/

		it("can select the next item in the item list", function() {
			sinon.spy(CapoRSS.router.itemList, 'moveCursor');

			this.view.$el.find('#mobileNextItem').trigger('click');

			expect(CapoRSS.router.itemList.moveCursor).toHaveBeenCalledOnce();
			expect(CapoRSS.router.itemList.moveCursor).toHaveBeenCalledWith('down');

			CapoRSS.router.itemList.moveCursor.restore();
		});

		it("can select the previous item in the item list", function() {
			sinon.spy(CapoRSS.router.itemList, 'moveCursor');

			this.view.$el.find('#mobilePrevItem').trigger('click');

			expect(CapoRSS.router.itemList.moveCursor).toHaveBeenCalledOnce();
			expect(CapoRSS.router.itemList.moveCursor).toHaveBeenCalledWith('up');

			CapoRSS.router.itemList.moveCursor.restore();
		});

		it("toggles the read visibility when clicking the toggleReadVisibility button", function() {
			this.view.$el.find('#toggleReadVisibility').trigger('click');

			expect(SETTINGS.show_read).toEqual(false);

			this.view.$el.find('#toggleReadVisibility').trigger('click');

			expect(SETTINGS.show_read).toEqual(true);
		});
	});
});
<|endoftext|>


<commit_before>import React, { Component, Fragment } from 'react';
import Router from 'next/router';
import NProgress from 'nprogress';
import PropTypes from 'prop-types';
import Navigation from '../Navigation';
import Footer from '../Footer';
import { version, repository } from '../../package.json';
import { initGA, logPageView } from '../../lib/analytics';
import withIntl from './withIntl';

NProgress.configure({ showSpinner: false });
Router.onRouteChangeStart = () => NProgress.start();
Router.onRouteChangeComplete = () => NProgress.done();
Router.onRouteChangeError = () => NProgress.done();

if (global.document) {
  const info = [
    `Version: ${version}`,
    `You can find the code here: https://github.com/${repository}`,
    'Thanks for stopping by 🤙',
  ];
  // eslint-disable-next-line no-console
  info.forEach(message => console.log(message));
}

class Document extends Component {
  componentDidMount = () => this.analytics();

  componentDidUpdate = () => this.analytics();

  analytics = () => {
    if (process.env.NODE_ENV === 'production') {
      if (!window.GA_INITIALIZED) {
        initGA();
        window.GA_INITIALIZED = true;
      }
      logPageView();
    }
  };

  render() {
    const { children } = this.props;
    return (
      <Fragment>
        <Navigation />
        {children}
        <Footer />
      </Fragment>
    );
  }
}

Document.propTypes = {
  children: PropTypes.node.isRequired,
};

export default withIntl(Document);
<commit_msg>remove NProgress as it's broken using _app<commit_after>import React, { Component, Fragment } from 'react';
// import Router from 'next/router';
// import NProgress from 'nprogress';
import PropTypes from 'prop-types';
import Navigation from '../Navigation';
import Footer from '../Footer';
import { version, repository } from '../../package.json';
import { initGA, logPageView } from '../../lib/analytics';
import withIntl from './withIntl';

// NProgress.configure({ showSpinner: false });
// Router.onRouteChangeStart = () => NProgress.start();
// Router.onRouteChangeComplete = () => NProgress.done();
// Router.onRouteChangeError = () => NProgress.done();

if (global.document) {
  const info = [
    `Version: ${version}`,
    `You can find the code here: https://github.com/${repository}`,
    'Thanks for stopping by 🤙',
  ];
  // eslint-disable-next-line no-console
  info.forEach(message => console.log(message));
}

class Document extends Component {
  componentDidMount = () => this.analytics();

  componentDidUpdate = () => this.analytics();

  analytics = () => {
    if (process.env.NODE_ENV === 'production') {
      if (!window.GA_INITIALIZED) {
        initGA();
        window.GA_INITIALIZED = true;
      }
      logPageView();
    }
  };

  render() {
    const { children } = this.props;
    return (
      <Fragment>
        <Navigation />
        {children}
        <Footer />
      </Fragment>
    );
  }
}

Document.propTypes = {
  children: PropTypes.node.isRequired,
};

export default withIntl(Document);
<|endoftext|>


<commit_before>import React from 'react'

export const Sharp = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="50"
    height="50"
    viewBox="0 0 81 81"
    xmlnsXlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <rect width="81" height="81" rx="3" />
    </defs>
    <g fill="none" fillRule="evenodd">
      <mask fill="white">
        <use xlinkHref="#a1" />
      </mask>
      <use fill="#616161" xlinkHref="#a1" />
      <g transform="translate(16 32)" mask="url(#b1)">
        <path
          fill="#000000"
          fillRule="nonzero"
          d="M66.0458013,46.1092762 C66.0458013,48.3193105 64.2622787,50.1077029 62.050805,50.1077029 L0.174089069,50.1077029 L0.174089069,6.16868499 C0.174089069,0.174657534 0.174089069,0.174657534 0.174089069,0.174657534 L66.0458013,0.174657534 L66.0458013,46.1092762 Z"
        />
        <g transform="translate(19.96 15.27)">
          <ellipse
            cx="7.045"
            cy="7.048"
            fill="#FF5E55"
            fillRule="nonzero"
            stroke="#E0443E"
            strokeWidth=".5"
            rx="7.045"
            ry="7.048"
          />
          <ellipse
            cx="30.526"
            cy="7.048"
            fill="#FFC02C"
            fillRule="nonzero"
            stroke="#DEA123"
            strokeWidth=".5"
            rx="7.045"
            ry="7.048"
          />
        </g>
      </g>
    </g>
  </svg>
)

export const BW = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="50"
    height="50"
    viewBox="0 0 81 81"
    xmlnsXlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <rect width="81" height="81" rx="3" />
    </defs>
    <g fill="none" fillRule="evenodd">
      <mask fill="white">
        <use xlinkHref="#a2" />
      </mask>
      <use fill="#616161" xlinkHref="#a2" />
      <g transform="translate(17 33)" mask="url(#b2)">
        <path
          fill="#000000"
          stroke="#FFFFFF"
          strokeWidth="2"
          d="M65.0458013,49.1077029 C66.0458013,49.1077029 0.174089069,49.1077029 0.174089069,49.1077029 L0.174089069,5.16868499 C0.174089069,2.41055979 2.40986586,0.174657534 5.17268563,0.174657534 L65.0458013,0.174657534 L65.0458013,49.1077029 Z"
        />
        <g fillRule="nonzero" stroke="#878787" transform="translate(18.96 14.27)">
          <ellipse cx="7.045" cy="7.048" rx="7.045" ry="7.048" />
          <ellipse cx="30.526" cy="7.048" rx="7.045" ry="7.048" />
        </g>
      </g>
    </g>
  </svg>
)

export const None = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="50"
    height="50"
    viewBox="0 0 81 81"
    xmlnsXlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <rect width="81" height="81" rx="3" />
    </defs>
    <g fill="none" fillRule="evenodd">
      <mask fill="white">
        <use xlinkHref="#a3" />
      </mask>
      <use fill="#616161" xlinkHref="#a3" />
      <g transform="translate(16 32)" mask="url(#b3)">
        <path
          fill="#000000"
          fillRule="nonzero"
          d="M66.0458013,46.1092762 C66.0458013,48.3193105 64.2622787,50.1077029 62.050805,50.1077029 L0.174089069,50.1077029 L0.174089069,6.16868499 C0.174089069,2.85738806 2.85846845,0.174657534 6.17268563,0.174657534 L66.0458013,0.174657534 L66.0458013,46.1092762 Z"
        />
        <g fillRule="nonzero" strokeWidth=".5" transform="translate(19.96 15.27)">
          <ellipse cx="7.045" cy="7.048" fill="#FF5E55" stroke="#E0443E" rx="7.045" ry="7.048" />
          <ellipse cx="30.526" cy="7.048" fill="#FFC02C" stroke="#DEA123" rx="7.045" ry="7.048" />
        </g>
      </g>
    </g>
  </svg>
)
<commit_msg>rect needs ids<commit_after>import React from 'react'

export const Sharp = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="50"
    height="50"
    viewBox="0 0 81 81"
    xmlnsXlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <rect id="a1" width="81" height="81" rx="3" />
    </defs>
    <g fill="none" fillRule="evenodd">
      <mask id="b1" fill="white">
        <use xlinkHref="#a1" />
      </mask>
      <use fill="#616161" xlinkHref="#a1" />
      <g transform="translate(16 32)" mask="url(#b1)">
        <path
          fill="#000000"
          fillRule="nonzero"
          d="M66.0458013,46.1092762 C66.0458013,48.3193105 64.2622787,50.1077029 62.050805,50.1077029 L0.174089069,50.1077029 L0.174089069,6.16868499 C0.174089069,0.174657534 0.174089069,0.174657534 0.174089069,0.174657534 L66.0458013,0.174657534 L66.0458013,46.1092762 Z"
        />
        <g transform="translate(19.96 15.27)">
          <ellipse
            cx="7.045"
            cy="7.048"
            fill="#FF5E55"
            fillRule="nonzero"
            stroke="#E0443E"
            strokeWidth=".5"
            rx="7.045"
            ry="7.048"
          />
          <ellipse
            cx="30.526"
            cy="7.048"
            fill="#FFC02C"
            fillRule="nonzero"
            stroke="#DEA123"
            strokeWidth=".5"
            rx="7.045"
            ry="7.048"
          />
        </g>
      </g>
    </g>
  </svg>
)

export const BW = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="50"
    height="50"
    viewBox="0 0 81 81"
    xmlnsXlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <rect id="a2" width="81" height="81" rx="3" />
    </defs>
    <g fill="none" fillRule="evenodd">
      <mask id="b2" fill="white">
        <use xlinkHref="#a2" />
      </mask>
      <use fill="#616161" xlinkHref="#a2" />
      <g transform="translate(17 33)" mask="url(#b2)">
        <path
          fill="#000000"
          stroke="#FFFFFF"
          strokeWidth="2"
          d="M65.0458013,49.1077029 C66.0458013,49.1077029 0.174089069,49.1077029 0.174089069,49.1077029 L0.174089069,5.16868499 C0.174089069,2.41055979 2.40986586,0.174657534 5.17268563,0.174657534 L65.0458013,0.174657534 L65.0458013,49.1077029 Z"
        />
        <g fillRule="nonzero" stroke="#878787" transform="translate(18.96 14.27)">
          <ellipse cx="7.045" cy="7.048" rx="7.045" ry="7.048" />
          <ellipse cx="30.526" cy="7.048" rx="7.045" ry="7.048" />
        </g>
      </g>
    </g>
  </svg>
)

export const None = () => (
  <svg
    xmlns="http://www.w3.org/2000/svg"
    width="50"
    height="50"
    viewBox="0 0 81 81"
    xmlnsXlink="http://www.w3.org/1999/xlink"
  >
    <defs>
      <rect id="a3" width="81" height="81" rx="3" />
    </defs>
    <g fill="none" fillRule="evenodd">
      <mask id="b3" fill="white">
        <use xlinkHref="#a3" />
      </mask>
      <use fill="#616161" xlinkHref="#a3" />
      <g transform="translate(16 32)" mask="url(#b3)">
        <path
          fill="#000000"
          fillRule="nonzero"
          d="M66.0458013,46.1092762 C66.0458013,48.3193105 64.2622787,50.1077029 62.050805,50.1077029 L0.174089069,50.1077029 L0.174089069,6.16868499 C0.174089069,2.85738806 2.85846845,0.174657534 6.17268563,0.174657534 L66.0458013,0.174657534 L66.0458013,46.1092762 Z"
        />
        <g fillRule="nonzero" strokeWidth=".5" transform="translate(19.96 15.27)">
          <ellipse cx="7.045" cy="7.048" fill="#FF5E55" stroke="#E0443E" rx="7.045" ry="7.048" />
          <ellipse cx="30.526" cy="7.048" fill="#FFC02C" stroke="#DEA123" rx="7.045" ry="7.048" />
        </g>
      </g>
    </g>
  </svg>
)
<|endoftext|>


<commit_before>import React from 'react'
import { Section } from '@components/v2/section'
import { Box, Flex } from 'blockstack-ui'
import { useHover } from 'use-events'

import ArrowUpRightIcon from 'mdi-react/ArrowUpRightIcon'

const items = [
  {
    platform: 'Web',
    description: 'No install. Recommended for most users',
    href: 'https://browser.blockstack.org/'
  },
  {
    platform: 'MacOS',
    description: 'Requires Sierra 10.12+',
    href:
      'https://github.com/blockstack/blockstack-browser/releases/download/v0.36.1/Blockstack-for-macOS-v0.36.1.dmg'
  },
  {
    platform: 'Windows',
    description: 'Requires Windows 10',
    href:
      'https://github.com/blockstack/blockstack-browser/releases/download/v0.36.1/Blockstack-for-win10-v0.36.1.msi'
  },
  {
    platform: 'Linux',
    description: 'Advanced install',
    href:
      'https://github.com/blockstack/blockstack-browser/releases/download/v0.36.1/Blockstack-for-Linux-v0.36.1.sh'
  }
]

const InstallItem = ({ platform, description, href }) => {
  const [hovered, bind] = useHover()
  return (
    <Flex
      width={1}
      pb={3}
      mb={3}
      borderBottom="1px solid"
      borderColor="sky.50"
      alignItems="center"
      justifyContent="flex-start"
      is="a"
      style={{
        textDecoration: 'none'
      }}
      href={href}
      target="_blank"
      {...bind}
    >
      <Box color={hovered ? 'blue' : 'ink'} pr={2} pt={1}>
        <ArrowUpRightIcon size={18} style={{ display: 'block' }} />
      </Box>
      <Box width="50%" pr={5} flexShink={0}>
        <Section.Title color={hovered ? 'blue' : 'ink'} is="h3">
          Browser {platform}
        </Section.Title>
      </Box>
      <Box width="50%" flexShink={0}>
        <Section.Text fontSize={1}>{description}</Section.Text>
      </Box>
    </Flex>
  )
}

const Install = (props) =>
  items.map((item, key) => <InstallItem key={key} {...item} />)

const installSection = {
  variant: 'white',
  minHeight: '400px',
  py: 8,
  panes: [
    {
      justifyContent: 'flex-start',
      panes: [
        {
          justifyContent: 'flex-start',
          text: {
            children:
              'The Blockstack Browser allows you to create and manage Blockstack IDs and explore decentralized apps.'
          }
        },
        {
          text: {
            fontSize: 2,
            children: (
              <>
                Developers can also run a full node by{' '}
                <Section.Text
                  fontSize={2}
                  is="a"
                  href="https://github.com/blockstack/blockstack-core"
                  target="_blank"
                >
                  following instructions on GitHub
                </Section.Text>
                .
              </>
            )
          }
        }
      ]
    },
    {
      children: <Install />
    }
  ]
}

export { Install, InstallItem, installSection }
<commit_msg>install mobile fixes<commit_after>import React from 'react'
import { Section } from '@components/v2/section'
import { Box, Flex } from 'blockstack-ui'
import { useHover } from 'use-events'

import ArrowUpRightIcon from 'mdi-react/ArrowUpRightIcon'
import DownloadIcon from 'mdi-react/DownloadIcon'

const items = [
  {
    platform: 'Web',
    description: 'No install. Recommended for most users',
    href: 'https://browser.blockstack.org/',
    icon: ArrowUpRightIcon
  },
  {
    platform: 'MacOS',
    description: 'Requires Sierra 10.12+',
    href:
      'https://github.com/blockstack/blockstack-browser/releases/download/v0.36.1/Blockstack-for-macOS-v0.36.1.dmg'
  },
  {
    platform: 'Windows',
    description: 'Requires Windows 10',
    href:
      'https://github.com/blockstack/blockstack-browser/releases/download/v0.36.1/Blockstack-for-win10-v0.36.1.msi'
  },
  {
    platform: 'Linux',
    description: 'Advanced install',
    href:
      'https://github.com/blockstack/blockstack-browser/releases/download/v0.36.1/Blockstack-for-Linux-v0.36.1.sh'
  }
]

const InstallItem = ({
  platform,
  description,
  icon: Icon = DownloadIcon,
  href
}) => {
  const [hovered, bind] = useHover()
  return (
    <Flex
      width={1}
      pb={3}
      mb={3}
      borderBottom="1px solid"
      borderColor="sky.50"
      alignItems="center"
      justifyContent="flex-start"
      is="a"
      style={{
        textDecoration: 'none'
      }}
      href={href}
      target="_blank"
      flexDirection={['column', 'row', 'column', 'row']}
      {...bind}
    >
      <Flex alignItems="center" minWidth={['100%', '50%', '100%', '50%']}>
        <Box
          opacity={hovered ? 1 : 0.5}
          color={hovered ? 'blue' : 'ink'}
          pr={2}
          pt={1}
        >
          <Icon size={18} style={{ display: 'block' }} />
        </Box>
        <Box pr={5} flexShink={0}>
          <Section.Title
            style={{ whiteSpace: 'nowrap' }}
            color={hovered ? 'blue' : 'ink'}
            is="h3"
          >
            Browser {platform}
          </Section.Title>
        </Box>
      </Flex>
      <Box minWidth={['100%', '50%', '100%', '50%']} flexShink={0} flexGrow={1}>
        <Section.Text fontSize={1}>{description}</Section.Text>
      </Box>
    </Flex>
  )
}

const Install = (props) =>
  items.map((item, key) => <InstallItem key={key} {...item} />)

const installSection = {
  variant: 'white',
  minHeight: '400px',
  py: 8,
  panes: [
    {
      justifyContent: 'flex-start',
      panes: [
        {
          justifyContent: 'flex-start',
          text: {
            children:
              'The Blockstack Browser allows you to create and manage Blockstack IDs and explore decentralized apps.'
          }
        },
        {
          text: {
            fontSize: 2,
            children: (
              <>
                Developers can also run a full node by{' '}
                <Section.Text
                  fontSize={2}
                  is="a"
                  href="https://github.com/blockstack/blockstack-core"
                  target="_blank"
                >
                  following instructions on GitHub
                </Section.Text>
                .
              </>
            )
          }
        }
      ]
    },
    {
      children: <Install />
    }
  ]
}

export { Install, InstallItem, installSection }
<|endoftext|>


<commit_before>// Concrete Model Editor
//
// Copyright (c) 2010 Martin Thiede
//
// Concrete is freely distributable under the terms of an MIT-style license.

Concrete.MetamodelProvider = Class.create({

	initialize: function(metamodelJson, opts) {
		this.metamodel = metamodelJson;
		this.metaclassesByName = {};
		this.metaclasses = [];
		this._extendedChecks = (opts == undefined || !(opts.extended_checks == false));
		this._resolveMetamodel();
	},

	_resolveMetamodel: function() {
		var rootClass = {name: "_Root", abstract: true, subTypes: [], superTypes: [], features: []};
		Object.extend(rootClass, Concrete.MetamodelExtension.Class);

		var datatypesByName = {};
		this.metamodel.each(function(c) {
			if (c._class == "Class") {
				if (!c.name || c.name.length == 0) throw new Error("Class name is empty");
				if (this.metaclassesByName[c.name]) throw new Error("Class name '"+c.name+"' not unique");
				if (this._extendedChecks) this._extendedClassChecks(c);
				Object.extend(c, Concrete.MetamodelExtension.Class);
				c.subTypes = [];
				if (c.abstract == undefined) c.abstract = false;
				this.metaclassesByName[c.name] = c;
				this.metaclasses.push(c);
			}
			else if (c._class == "Datatype" || c._class == "Enum") {
				if (!c.name || c.name.length == 0) throw new Error("Datatype name is empty");
				if (datatypesByName[c.name]) throw new Error("Datatype name '"+c.name+"' not unique");
				if (this._extendedChecks) this._extendedDatatypeChecks(c);
				Object.extend(c, Concrete.MetamodelExtension.Datatype);
				datatypesByName[c.name] = c;
			}
			else {
				throw new Error ("specify '_class' property, it must be one of [Datatype, Enum, Class]");
			}
		}, this);
		// default datatype in case it is not present
		if (!datatypesByName["String"]) {
			var dt = {name: "String"};
			Object.extend(dt, Concrete.MetamodelExtension.Datatype);
			datatypesByName["String"] = dt;
		}
		this.metaclasses.each(function(c) {
			if (c.superTypes) {
				if (!(c.superTypes instanceof Array)) c.superTypes = [c.superTypes];
				c.superTypes = [rootClass].concat(c.superTypes.collect(function(t) {
					var target = this.metaclassesByName[t];
					if (!target) throw new Error("Can not resolve supertype '"+t+"' in class '"+c.name+"'");
					return target;
				}, this));
			}
			else {
				c.superTypes = [rootClass];
			}
			c.superTypes.each(function(t) {
				t.subTypes.push(c);
			});
			featureNames = {};
			c.features = c.features || [];
			if (!(c.features instanceof Array)) c.features = [c.features];
			c.features.each(function(f) {
				if (!f.name || f.name.length == 0) throw new Error("Feature name is empty in class '"+c.name+"'");
				if (featureNames[f.name]) throw new Error("Feature name '"+f.name+"' not unique in class '"+c.name+"'");
				if (this._extendedChecks) this._extendedFeatureChecks(f, c);
				Object.extend(f, Concrete.MetamodelExtension.Feature);
				f.containingClass = c;
				featureNames[f.name] = true;
				f.kind = f.kind || "attribute";
				if (f.isReference() || f.isContainment()) {
					if (f.type) {
						var target = this.metaclassesByName[f.type];
						if (!target) throw new Error("Can not resolve type '"+f.type+"' in feature '"+f.name+"' class '"+c.name+"'");
						f.type = target;
					}
					else {
						f.type = rootClass;
					}
					if (f.isReference()) {
						f.upperLimit = f.upperLimit || 1;
					}
					else {
						f.upperLimit = f.upperLimit || -1;
					}
					f.lowerLimit = f.lowerLimit || 0;
				}
				else {
					var typename = f.type || "String";
					f.type = datatypesByName[typename];
					if (!f.type) throw new Error("Can not resolve datatype '"+typename+"' in feature '"+f.name+"' class '"+c.name+"'");
					f.upperLimit = f.upperLimit || 1;
					f.lowerLimit = f.lowerLimit || 0;
				}
			}, this);
		}, this);
	},

	_extendedDatatypeChecks: function(type) {
		for (p in type) {
			if (!["_class", "name", "literals"].include(p)) throw new Error("Unknown property '"+p+"' in type '"+type.name+"'");
		}
		if (type._class != "Enum" && type.literals != undefined) {
			throw new Error("Literals can only be specified for Enums");
		}
		if (type.literals != undefined) {
			if (!(type.literals instanceof Array) || !type.literals.all(function(l) { return Object.isString(l); })) throw new Error("Enum literals must be an Array of Strings in type '"+type.name+"'");
		}
		if (type._class == "Datatype") {
			if (!(["String", "Integer", "Float", "Boolean"].include(type.name))) throw new Error("Plain Datatypes (excluding Enums) must be named one of [String, Integer, Float, Boolean]");
		}
	},

	_extendedClassChecks: function(clazz) {
		for (p in clazz) {
			if (!["_class", "features", "name", "superTypes", "abstract"].include(p)) throw new Error("Unknown property '"+p+"' in class '"+clazz.name+"'");
		}
		if (clazz.abstract != undefined) {
			if (clazz.abstract != true && clazz.abstract != false) throw new Error("Abstract property must be true or false in class '"+clazz.name+"'");
		}
	},
	_extendedFeatureChecks: function(feat, clazz) {
		var loc = " in class '"+clazz.name+"', feature '"+feat.name+"'";
		for (p in feat) {
			if (!["_class", "name", "kind", "type", "lowerLimit", "upperLimit"].include(p)) throw new Error("Unknown property '"+p+"'" + loc );
			if (p == "kind") {
				if (!["attribute", "reference", "containment"].include(feat.kind)) throw new Error("Feature kind must be one of 'attribute', 'reference', 'containment'" + loc);
			}
		}
	}

});

Concrete.MetamodelExtension = {};

Concrete.MetamodelExtension.Class = {

	allSubTypes: function() {
		this.subTypes = this.subTypes || [];
		return this.subTypes.concat(this.subTypes.collect(function(t) { return t.allSubTypes(); })).flatten().uniq();
	},

	allSuperTypes: function() {
		this.superTypes = this.superTypes ||  [];
		return this.superTypes.collect(function(t) { return t.allSuperTypes(); }).concat(this.superTypes).flatten().uniq();
	},

	allFeatures: function() {
		return this.allSuperTypes().collect(function(t) { return t.features; }).concat(this.features).flatten().uniq();
	}
};

Concrete.MetamodelExtension.Datatype = {
	isEnum: function() { return this._class == "Enum"; },
	isString: function() { return this.name == "String"; },
	isInteger: function() { return this.name == "Integer"; },
	isFloat: function() { return this.name == "Float"; },
	isBoolean: function() { return this.name == "Boolean"; }
};

Concrete.MetamodelExtension.Feature = {
	isContainment: function() { return this.kind == "containment"; },
	isReference: function() { return this.kind == "reference"; },
	isAttribute: function() { return this.kind == "attribute"; }
};

<commit_msg>allowed 'documentation' as property of meta class<commit_after>// Concrete Model Editor
//
// Copyright (c) 2010 Martin Thiede
//
// Concrete is freely distributable under the terms of an MIT-style license.

Concrete.MetamodelProvider = Class.create({

	initialize: function(metamodelJson, opts) {
		this.metamodel = metamodelJson;
		this.metaclassesByName = {};
		this.metaclasses = [];
		this._extendedChecks = (opts == undefined || !(opts.extended_checks == false));
		this._resolveMetamodel();
	},

	_resolveMetamodel: function() {
		var rootClass = {name: "_Root", abstract: true, subTypes: [], superTypes: [], features: []};
		Object.extend(rootClass, Concrete.MetamodelExtension.Class);

		var datatypesByName = {};
		this.metamodel.each(function(c) {
			if (c._class == "Class") {
				if (!c.name || c.name.length == 0) throw new Error("Class name is empty");
				if (this.metaclassesByName[c.name]) throw new Error("Class name '"+c.name+"' not unique");
				if (this._extendedChecks) this._extendedClassChecks(c);
				Object.extend(c, Concrete.MetamodelExtension.Class);
				c.subTypes = [];
				if (c.abstract == undefined) c.abstract = false;
				this.metaclassesByName[c.name] = c;
				this.metaclasses.push(c);
			}
			else if (c._class == "Datatype" || c._class == "Enum") {
				if (!c.name || c.name.length == 0) throw new Error("Datatype name is empty");
				if (datatypesByName[c.name]) throw new Error("Datatype name '"+c.name+"' not unique");
				if (this._extendedChecks) this._extendedDatatypeChecks(c);
				Object.extend(c, Concrete.MetamodelExtension.Datatype);
				datatypesByName[c.name] = c;
			}
			else {
				throw new Error ("specify '_class' property, it must be one of [Datatype, Enum, Class]");
			}
		}, this);
		// default datatype in case it is not present
		if (!datatypesByName["String"]) {
			var dt = {name: "String"};
			Object.extend(dt, Concrete.MetamodelExtension.Datatype);
			datatypesByName["String"] = dt;
		}
		this.metaclasses.each(function(c) {
			if (c.superTypes) {
				if (!(c.superTypes instanceof Array)) c.superTypes = [c.superTypes];
				c.superTypes = [rootClass].concat(c.superTypes.collect(function(t) {
					var target = this.metaclassesByName[t];
					if (!target) throw new Error("Can not resolve supertype '"+t+"' in class '"+c.name+"'");
					return target;
				}, this));
			}
			else {
				c.superTypes = [rootClass];
			}
			c.superTypes.each(function(t) {
				t.subTypes.push(c);
			});
			featureNames = {};
			c.features = c.features || [];
			if (!(c.features instanceof Array)) c.features = [c.features];
			c.features.each(function(f) {
				if (!f.name || f.name.length == 0) throw new Error("Feature name is empty in class '"+c.name+"'");
				if (featureNames[f.name]) throw new Error("Feature name '"+f.name+"' not unique in class '"+c.name+"'");
				if (this._extendedChecks) this._extendedFeatureChecks(f, c);
				Object.extend(f, Concrete.MetamodelExtension.Feature);
				f.containingClass = c;
				featureNames[f.name] = true;
				f.kind = f.kind || "attribute";
				if (f.isReference() || f.isContainment()) {
					if (f.type) {
						var target = this.metaclassesByName[f.type];
						if (!target) throw new Error("Can not resolve type '"+f.type+"' in feature '"+f.name+"' class '"+c.name+"'");
						f.type = target;
					}
					else {
						f.type = rootClass;
					}
					if (f.isReference()) {
						f.upperLimit = f.upperLimit || 1;
					}
					else {
						f.upperLimit = f.upperLimit || -1;
					}
					f.lowerLimit = f.lowerLimit || 0;
				}
				else {
					var typename = f.type || "String";
					f.type = datatypesByName[typename];
					if (!f.type) throw new Error("Can not resolve datatype '"+typename+"' in feature '"+f.name+"' class '"+c.name+"'");
					f.upperLimit = f.upperLimit || 1;
					f.lowerLimit = f.lowerLimit || 0;
				}
			}, this);
		}, this);
	},

	_extendedDatatypeChecks: function(type) {
		for (p in type) {
			if (!["_class", "name", "documentation", "literals"].include(p)) throw new Error("Unknown property '"+p+"' in type '"+type.name+"'");
		}
		if (type._class != "Enum" && type.literals != undefined) {
			throw new Error("Literals can only be specified for Enums");
		}
		if (type.literals != undefined) {
			if (!(type.literals instanceof Array) || !type.literals.all(function(l) { return Object.isString(l); })) throw new Error("Enum literals must be an Array of Strings in type '"+type.name+"'");
		}
		if (type._class == "Datatype") {
			if (!(["String", "Integer", "Float", "Boolean"].include(type.name))) throw new Error("Plain Datatypes (excluding Enums) must be named one of [String, Integer, Float, Boolean]");
		}
	},

	_extendedClassChecks: function(clazz) {
		for (p in clazz) {
			if (!["_class", "features", "name", "documentation", "superTypes", "abstract"].include(p)) throw new Error("Unknown property '"+p+"' in class '"+clazz.name+"'");
		}
		if (clazz.abstract != undefined) {
			if (clazz.abstract != true && clazz.abstract != false) throw new Error("Abstract property must be true or false in class '"+clazz.name+"'");
		}
	},

	_extendedFeatureChecks: function(feat, clazz) {
		var loc = " in class '"+clazz.name+"', feature '"+feat.name+"'";
		for (p in feat) {
			if (!["_class", "name", "documentation", "kind", "type", "lowerLimit", "upperLimit"].include(p)) throw new Error("Unknown property '"+p+"'" + loc );
			if (p == "kind") {
				if (!["attribute", "reference", "containment"].include(feat.kind)) throw new Error("Feature kind must be one of 'attribute', 'reference', 'containment'" + loc);
			}
		}
	}

});

Concrete.MetamodelExtension = {};

Concrete.MetamodelExtension.Class = {

	allSubTypes: function() {
		this.subTypes = this.subTypes || [];
		return this.subTypes.concat(this.subTypes.collect(function(t) { return t.allSubTypes(); })).flatten().uniq();
	},

	allSuperTypes: function() {
		this.superTypes = this.superTypes ||  [];
		return this.superTypes.collect(function(t) { return t.allSuperTypes(); }).concat(this.superTypes).flatten().uniq();
	},

	allFeatures: function() {
		return this.allSuperTypes().collect(function(t) { return t.features; }).concat(this.features).flatten().uniq();
	}
};

Concrete.MetamodelExtension.Datatype = {
	isEnum: function() { return this._class == "Enum"; },
	isString: function() { return this.name == "String"; },
	isInteger: function() { return this.name == "Integer"; },
	isFloat: function() { return this.name == "Float"; },
	isBoolean: function() { return this.name == "Boolean"; }
};

Concrete.MetamodelExtension.Feature = {
	isContainment: function() { return this.kind == "containment"; },
	isReference: function() { return this.kind == "reference"; },
	isAttribute: function() { return this.kind == "attribute"; }
};

<|endoftext|>