<commit_before>import Ember from 'ember';
import StreamEnabled from 'ember-stream/mixins/stream-enabled';
import layout from '../templates/components/sl-menu';
import { warn } from '../utils/all';

/**
 * @module
 * @augments ember/Component
 * @augments ember-stream/mixins/stream-enabled
 */
export default Ember.Component.extend( StreamEnabled, {

    // -------------------------------------------------------------------------
    // Dependencies

    // -------------------------------------------------------------------------
    // Attributes

    /** @type {String[]} */
    classNameBindings: [
        'showingAll:show-all'
    ],

    /** @type {String[]} */
    classNames: [
        'sl-menu'
    ],

    /** @type {Object} */
    layout,

    /** @type {String} */
    tagName: 'div',

    // -------------------------------------------------------------------------
    // Actions

    /** @type {Object} */
    actions: {

        /**
         * Handle an action from a sub-menu item
         *
         * @function actions:handleAction
         * @param {String} actionName - The name of an action to pass up to the
         *        parent controller
         * @param {*} data - Any data to also pass up to the parent controller
         * @returns {undefined}
         */
        handleAction( actionName, data ) {
            this.sendAction( 'action', actionName, data );
        },

        /**
         * Trigger hiding all of the menu's sub-menus
         *
         * @function actions:hideAll
         * @returns {undefined}
         */
        hideAll() {
            this.hideAll();
        },

        /**
         * Trigger showing all the menu's sub-menus
         *
         * @function actions:showAll
         * @returns {undefined}
         */
        showAll() {
            this.showAll();
        }
    },

    // -------------------------------------------------------------------------
    // Events

    /**
     * mouseLeave event handler
     *
     * @function
     * @returns {undefined}
     */
    mouseLeave() {
        this.send( 'hideAll' );
    },

    // -------------------------------------------------------------------------
    // Properties

    /**
     * Whether to show a menu item to display all sub-menus
     *
     * @type {Boolean}
     */
    allowShowAll: false,

    /**
     * The array of menu items
     *
     * @type {?Object[]}
     */
    items: null,

    /**
     * An array of objects containing data about the selected states
     *
     * @private
     * @type {?ember/Array}
     */
    selections: null,

    /**
     * Whether to show all the menu's sub-items
     *
     * @private
     * @type {Boolean}
     */
    showingAll: false,

    // -------------------------------------------------------------------------
    // Observers

    /**
     * Initialize any computed properties that need setup
     *
     * @function
     * @returns {undefined}
     */
    initialize: Ember.on(
        'init',
        function() {
            this.set( 'selections', new Ember.A() );
        }
    ),

    /**
     * Setup the stream actions bindings
     *
     * @function
     * @returns {undefined}
     */
    setupStreamActions: Ember.on(
        'init',
        function() {
            const stream = this.get( 'stream' );

            if ( !stream ) {
                return;
            }

            stream.on( 'doAction', () => {
                this.doAction();
            });

            stream.on( 'hideAll', () => {
                this.hideAll();
            });

            stream.on( 'select', ( index ) => {
                this.select( index );
            });

            stream.on( 'selectDown', () => {
                this.selectDown();
            });

            stream.on( 'selectLeft', () => {
                this.selectLeft();
            });

            stream.on( 'selectNext', () => {
                this.selectNext();
            });

            stream.on( 'selectParent', () => {
                this.selectParent();
            });

            stream.on( 'selectPrevious', () => {
                this.selectPrevious();
            });

            stream.on( 'selectRight', () => {
                this.selectRight();
            });

            stream.on( 'selectSubMenu', () => {
                this.selectSubMenu();
            });

            stream.on( 'selectUp', () => {
                this.selectUp();
            });

            stream.on( 'showAll', () => {
                this.showAll();
            });
        }
    ),

    /**
     * Retrieve the currently selected item
     *
     * @function
     * @returns {?Object}
     */
    selectedItem: Ember.computed(
        'selections.@each.item',
        function() {
            const lastItem = this.get( 'selections.lastObject.item' );

            return lastItem ? lastItem : null;
        }
    ),

    // -------------------------------------------------------------------------
    // Methods

    /**
     * Clear the `selection` data
     *
     * @function
     * @returns {undefined}
     */
    clearSelection() {
        const selections = this.get( 'selections' );

        selections.forEach( ( selection ) => {
            Ember.set( selection.item, 'selected', false );
        });

        this.set( 'selection', null );
    },

    /**
     * Perform the currently selected item's `action`
     *
     * @function
     * @returns {undefined}
     */
    doAction() {
        const selectedItem = this.get( 'selectedItem' );

        if ( selectedItem ) {
            const action = Ember.get( selectedItem, 'action' );

            if ( action ) {
                this.sendAction( 'action', action, Ember.get( selectedItem, 'data' ) );
            }
        }
    },

    /**
     * Hide all the menu's sub-menus
     *
     * @function
     * @returns {undefined}
     */
    hideAll() {
        this.set( 'showingAll', false );
        this.clearSelection();
    },

    /**
     * Select an item by its index in the current selection context
     *
     * @function
     * @param {Number} index - The index of the item to select
     * @throws {ember/Error}
     * @returns {undefined}
     */
    select( index ) {
        const selections = this.get( 'selections' );
        const selectionsLength = selections.length;
        let item;

        if ( selectionsLength > 0 ) {
            const selection = selections.objectAt( selectionsLength - 1 );

            if ( !selection ) {
                throw new Ember.Error( 'Current selection is undefined' );
            }

            const contextItems = selectionsLength > 1 ?
                Ember.get( selections.objectAt( selectionsLength - 2 ), 'items' ) :
                this.get( 'items' );

            const currentItem = Ember.get( selection, 'item' );

            if ( !currentItem ) {
                throw new Ember.Error( 'Current item is undefined' );
            }

            item = contextItems.objectAt( index );

            if ( !item ) {
                throw new Ember.Error( `Item at index ${index} is undefined` );
            }

            Ember.set( currentItem, 'selected', false );
            Ember.set( item, 'selected', true );
            Ember.setProperties( selection, { index, item });
        } else {
            const items = this.get( 'items' );

            if ( !items ) {
                throw new Ember.Error( 'Component `items` is undefined' );
            }

            if ( items.length > 0 ) {
                index = Math.min( items.length - 1, index );
                item = items[ index ];

                Ember.set( item, 'selected', true );
                selections.pushObject({ index, item, items });
            }
        }
    },

    /**
     * Select a menu item in the "down" direction
     *
     * At the top-level of the menu, "down" corresponds to opening and selecting
     * the first child in its sub-menu.
     * Inside a sub-menu, "down" corresponds to selecting the next sibling
     * menu item.
     *
     * @function
     * @returns {undefined}
     */
    selectDown() {
        const selectionsLength = this.get( 'selections' ).length;

        if ( 1 === selectionsLength ) {
            this.selectSubMenu();
        } else if ( selectionsLength > 1 ) {
            this.selectNext();
        } else {
            this.select( 0 );
        }
    },

    /**
     * Select a menu item in the "left" direction
     *
     * At the top-level of the menu, "left" corresponds to selecting the
     * previous sibling menu item.
     * Inside a sub-menu, "left" corresponds to parsing back to the parent item
     *
     * @function
     * @returns {undefined}
     */
    selectLeft() {
        const selectionsLength = this.get( 'selections' ).length;

        if ( 1 === selectionsLength ) {
            this.selectPrevious();
        } else if ( selectionsLength > 1 ) {
            this.selectParent();
        }
    },

    /**
     * Select the next sibling in the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectNext() {
        const selections = this.get( 'selections' );

        // Select the first item from `items` if nothing is currently selected
        if ( selections.length < 1 ) {
            this.select( 0 );
            return;
        }

        const selection = selections.objectAt( selections.length - 1 );
        const currentItems = Ember.get( selection, 'items' );

        if ( !currentItems ) {
            throw new Ember.Error( 'Current selection items are undefined' );
        }

        if ( currentItems.length < 2 ) {
            warn( '`selectNext` triggered with fewer than two siblings in context' );
            return;
        }

        const currentIndex = Ember.get( selection, 'index' );

        if ( 'number' !== Ember.typeOf( currentIndex ) ) {
            throw new Ember.Error( 'Current index is not valid' );
        }

        const currentItem = Ember.get( selection, 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Current item is undefined' );
        }

        let newIndex = currentIndex + 1;

        if ( newIndex >= currentItems.length ) {
            newIndex -= currentItems.length;
        }

        const item = currentItems[ newIndex ];

        if ( !item ) {
            throw new Ember.Error( `Item with index ${newIndex} is undefined` );
        }

        Ember.set( currentItem, 'selected', false );
        Ember.set( item, 'selected', true );

        Ember.setProperties( selection, {
            index: newIndex,
            item
        });
    },

    /**
     * Select the parent menu from the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectParent() {
        const selections = this.get( 'selections' );

        if ( selections.length <= 1 ) {
            warn( '`selectParent` triggered with no parent context' );
        }

        const currentItem = Ember.get( selections.popObject(), 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Invalid last menu item' );
        }

        Ember.set( currentItem, 'selected', false );
    },

    /**
     * Select the previous sibling in the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectPrevious() {
        const selections = this.get( 'selections' );

        if ( selections.length < 1 ) {
            warn( '`selectPrevious` triggered with no siblings' );
            return;
        }

        const selection = selections.objectAt( selections.length - 1 );
        const currentItems = Ember.get( selection, 'items' );

        if ( !currentItems ) {
            throw new Ember.Error( 'Current items are undefined' );
        }

        if ( currentItems.length < 2 ) {
            warn( '`selectPrevious` triggered with no siblings in context' );
            return;
        }

        const currentIndex = Ember.get( selection, 'index' );

        if ( 'number' !== Ember.typeOf( currentIndex ) ) {
            throw new Ember.Error( 'Current index is not valid' );
        }

        const currentItem = Ember.get( selection, 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Current item is undefined' );
        }

        let newIndex = currentIndex - 1;

        if ( newIndex < 0 ) {
            newIndex += currentItems.length;
        }

        const item = currentItems[ newIndex ];

        if ( !item ) {
            throw new Ember.Error( `Item with index ${newIndex} is undefined` );
        }

        Ember.set( currentItem, 'selected', false );
        Ember.set( item, 'selected', true );

        Ember.setProperties( selection, {
            index: newIndex,
            item
        });
    },

    /**
     * Select a menu item in the "right" direction
     *
     * When at the top-level of the menu, "right" corresponds to the next
     * sibling item.
     * When inside a sub-menu, "right" corresponds to entering its sub-menu, if
     * it has one.
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectRight() {
        const selections = this.get( 'selections' );

        if ( 1 === selections.length ) {
            this.selectNext();
        } else if ( selections.length > 1 ) {
            this.selectSubMenu();
        }
    },

    /**
     * Select the sub-menu in the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectSubMenu() {
        const selections = this.get( 'selections' );

        if ( selections.length < 1 ) {
            return;
        }

        const selection = selections.get( selections.length - 1 );

        if ( !selection ) {
            throw new Ember.Error( 'Last item of `selection` is invalid' );
        }

        const currentItem = Ember.get( selection, 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Last selection menu item is invalid' );
        }

        const items = Ember.get( currentItem, 'items' );

        if ( !items ) {
            return;
        }

        const index = 0;
        const item = items[ index ];

        if ( !item ) {
            throw new Ember.Error( 'First item in selected sub-menu is undefined' );
        }

        Ember.set( item, 'selected', true );

        selections.pushObject({
            index,
            item,
            items
        });
    },

    /**
     * Select a menu item in the "up" direction
     *
     * When at the top level, "up" corresponds to no action.
     * When in the first sub-menu and on the first item, "up" corresponds to
     * selecting the top level.
     * When in any other sub-menu, "up" corresponds to selecting the previous
     * sibling menu item.
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectUp() {
        const selections = this.get( 'selections' );
        const selectionsLength = selections.length;

        if ( selectionsLength < 1 ) {
            return;
        }

        if ( 2 === selectionsLength ) {
            const selection = selections.get( 1 );

            if ( 0 === Ember.get( selection, 'index' ) ) {
                this.selectParent();
                return;
            }
        }

        this.selectPrevious();
    },

    /**
     * Trigger the showAll menu-item
     *
     * @function
     * @returns {undefined}
     */
    showAll() {
        this.set( 'showingAll', true );
    }

});
<commit_msg>Fix logic for handling selecting siblings by index<commit_after>import Ember from 'ember';
import StreamEnabled from 'ember-stream/mixins/stream-enabled';
import layout from '../templates/components/sl-menu';
import { warn } from '../utils/all';

/**
 * @module
 * @augments ember/Component
 * @augments ember-stream/mixins/stream-enabled
 */
export default Ember.Component.extend( StreamEnabled, {

    // -------------------------------------------------------------------------
    // Dependencies

    // -------------------------------------------------------------------------
    // Attributes

    /** @type {String[]} */
    classNameBindings: [
        'showingAll:show-all'
    ],

    /** @type {String[]} */
    classNames: [
        'sl-menu'
    ],

    /** @type {Object} */
    layout,

    /** @type {String} */
    tagName: 'div',

    // -------------------------------------------------------------------------
    // Actions

    /** @type {Object} */
    actions: {

        /**
         * Handle an action from a sub-menu item
         *
         * @function actions:handleAction
         * @param {String} actionName - The name of an action to pass up to the
         *        parent controller
         * @param {*} data - Any data to also pass up to the parent controller
         * @returns {undefined}
         */
        handleAction( actionName, data ) {
            this.sendAction( 'action', actionName, data );
        },

        /**
         * Trigger hiding all of the menu's sub-menus
         *
         * @function actions:hideAll
         * @returns {undefined}
         */
        hideAll() {
            this.hideAll();
        },

        /**
         * Trigger showing all the menu's sub-menus
         *
         * @function actions:showAll
         * @returns {undefined}
         */
        showAll() {
            this.showAll();
        }
    },

    // -------------------------------------------------------------------------
    // Events

    /**
     * mouseLeave event handler
     *
     * @function
     * @returns {undefined}
     */
    mouseLeave() {
        this.send( 'hideAll' );
    },

    // -------------------------------------------------------------------------
    // Properties

    /**
     * Whether to show a menu item to display all sub-menus
     *
     * @type {Boolean}
     */
    allowShowAll: false,

    /**
     * The array of menu items
     *
     * @type {?Object[]}
     */
    items: null,

    /**
     * An array of objects containing data about the selected states
     *
     * @private
     * @type {?ember/Array}
     */
    selections: null,

    /**
     * Whether to show all the menu's sub-items
     *
     * @private
     * @type {Boolean}
     */
    showingAll: false,

    // -------------------------------------------------------------------------
    // Observers

    /**
     * Initialize any computed properties that need setup
     *
     * @function
     * @returns {undefined}
     */
    initialize: Ember.on(
        'init',
        function() {
            this.set( 'selections', new Ember.A() );
        }
    ),

    /**
     * Setup the stream actions bindings
     *
     * @function
     * @returns {undefined}
     */
    setupStreamActions: Ember.on(
        'init',
        function() {
            const stream = this.get( 'stream' );

            if ( !stream ) {
                return;
            }

            stream.on( 'doAction', () => {
                this.doAction();
            });

            stream.on( 'hideAll', () => {
                this.hideAll();
            });

            stream.on( 'select', ( index ) => {
                this.select( index );
            });

            stream.on( 'selectDown', () => {
                this.selectDown();
            });

            stream.on( 'selectLeft', () => {
                this.selectLeft();
            });

            stream.on( 'selectNext', () => {
                this.selectNext();
            });

            stream.on( 'selectParent', () => {
                this.selectParent();
            });

            stream.on( 'selectPrevious', () => {
                this.selectPrevious();
            });

            stream.on( 'selectRight', () => {
                this.selectRight();
            });

            stream.on( 'selectSubMenu', () => {
                this.selectSubMenu();
            });

            stream.on( 'selectUp', () => {
                this.selectUp();
            });

            stream.on( 'showAll', () => {
                this.showAll();
            });
        }
    ),

    /**
     * Retrieve the currently selected item
     *
     * @function
     * @returns {?Object}
     */
    selectedItem: Ember.computed(
        'selections.@each.item',
        function() {
            const lastItem = this.get( 'selections.lastObject.item' );

            return lastItem ? lastItem : null;
        }
    ),

    // -------------------------------------------------------------------------
    // Methods

    /**
     * Clear the `selection` data
     *
     * @function
     * @returns {undefined}
     */
    clearSelection() {
        const selections = this.get( 'selections' );

        selections.forEach( ( selection ) => {
            Ember.set( selection.item, 'selected', false );
        });

        this.set( 'selection', null );
    },

    /**
     * Perform the currently selected item's `action`
     *
     * @function
     * @returns {undefined}
     */
    doAction() {
        const selectedItem = this.get( 'selectedItem' );

        if ( selectedItem ) {
            const action = Ember.get( selectedItem, 'action' );

            if ( action ) {
                this.sendAction( 'action', action, Ember.get( selectedItem, 'data' ) );
            }
        }
    },

    /**
     * Hide all the menu's sub-menus
     *
     * @function
     * @returns {undefined}
     */
    hideAll() {
        this.set( 'showingAll', false );
        this.clearSelection();
    },

    /**
     * Select an item by its index in the current selection context
     *
     * @function
     * @param {Number} index - The index of the item to select
     * @throws {ember/Error}
     * @returns {undefined}
     */
    select( index ) {
        const selections = this.get( 'selections' );
        const selectionsLength = selections.length;
        let item;

        if ( selectionsLength > 0 ) {
            const selection = selections.objectAt( selectionsLength - 1 );

            if ( !selection ) {
                throw new Ember.Error( 'Current selection is undefined' );
            }

            const contextItems = selectionsLength > 1 ?
                Ember.get( selection, 'items' ) :
                this.get( 'items' );

            const currentItem = Ember.get( selection, 'item' );

            if ( !currentItem ) {
                throw new Ember.Error( 'Current item is undefined' );
            }

            item = contextItems.objectAt( index );

            if ( !item ) {
                throw new Ember.Error( `Item at index ${index} is undefined` );
            }

            Ember.set( currentItem, 'selected', false );
            Ember.set( item, 'selected', true );
            Ember.setProperties( selection, { index, item });
        } else {
            const items = this.get( 'items' );

            if ( !items ) {
                throw new Ember.Error( 'Component `items` is undefined' );
            }

            if ( items.length > 0 ) {
                index = Math.min( items.length - 1, index );
                item = items[ index ];

                Ember.set( item, 'selected', true );
                selections.pushObject({ index, item, items });
            }
        }
    },

    /**
     * Select a menu item in the "down" direction
     *
     * At the top-level of the menu, "down" corresponds to opening and selecting
     * the first child in its sub-menu.
     * Inside a sub-menu, "down" corresponds to selecting the next sibling
     * menu item.
     *
     * @function
     * @returns {undefined}
     */
    selectDown() {
        const selectionsLength = this.get( 'selections' ).length;

        if ( 1 === selectionsLength ) {
            this.selectSubMenu();
        } else if ( selectionsLength > 1 ) {
            this.selectNext();
        } else {
            this.select( 0 );
        }
    },

    /**
     * Select a menu item in the "left" direction
     *
     * At the top-level of the menu, "left" corresponds to selecting the
     * previous sibling menu item.
     * Inside a sub-menu, "left" corresponds to parsing back to the parent item
     *
     * @function
     * @returns {undefined}
     */
    selectLeft() {
        const selectionsLength = this.get( 'selections' ).length;

        if ( 1 === selectionsLength ) {
            this.selectPrevious();
        } else if ( selectionsLength > 1 ) {
            this.selectParent();
        }
    },

    /**
     * Select the next sibling in the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectNext() {
        const selections = this.get( 'selections' );

        // Select the first item from `items` if nothing is currently selected
        if ( selections.length < 1 ) {
            this.select( 0 );
            return;
        }

        const selection = selections.objectAt( selections.length - 1 );
        const currentItems = Ember.get( selection, 'items' );

        if ( !currentItems ) {
            throw new Ember.Error( 'Current selection items are undefined' );
        }

        if ( currentItems.length < 2 ) {
            warn( '`selectNext` triggered with fewer than two siblings in context' );
            return;
        }

        const currentIndex = Ember.get( selection, 'index' );

        if ( 'number' !== Ember.typeOf( currentIndex ) ) {
            throw new Ember.Error( 'Current index is not valid' );
        }

        const currentItem = Ember.get( selection, 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Current item is undefined' );
        }

        let newIndex = currentIndex + 1;

        if ( newIndex >= currentItems.length ) {
            newIndex -= currentItems.length;
        }

        const item = currentItems[ newIndex ];

        if ( !item ) {
            throw new Ember.Error( `Item with index ${newIndex} is undefined` );
        }

        Ember.set( currentItem, 'selected', false );
        Ember.set( item, 'selected', true );

        Ember.setProperties( selection, {
            index: newIndex,
            item
        });
    },

    /**
     * Select the parent menu from the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectParent() {
        const selections = this.get( 'selections' );

        if ( selections.length <= 1 ) {
            warn( '`selectParent` triggered with no parent context' );
        }

        const currentItem = Ember.get( selections.popObject(), 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Invalid last menu item' );
        }

        Ember.set( currentItem, 'selected', false );
    },

    /**
     * Select the previous sibling in the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectPrevious() {
        const selections = this.get( 'selections' );

        if ( selections.length < 1 ) {
            warn( '`selectPrevious` triggered with no siblings' );
            return;
        }

        const selection = selections.objectAt( selections.length - 1 );
        const currentItems = Ember.get( selection, 'items' );

        if ( !currentItems ) {
            throw new Ember.Error( 'Current items are undefined' );
        }

        if ( currentItems.length < 2 ) {
            warn( '`selectPrevious` triggered with no siblings in context' );
            return;
        }

        const currentIndex = Ember.get( selection, 'index' );

        if ( 'number' !== Ember.typeOf( currentIndex ) ) {
            throw new Ember.Error( 'Current index is not valid' );
        }

        const currentItem = Ember.get( selection, 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Current item is undefined' );
        }

        let newIndex = currentIndex - 1;

        if ( newIndex < 0 ) {
            newIndex += currentItems.length;
        }

        const item = currentItems[ newIndex ];

        if ( !item ) {
            throw new Ember.Error( `Item with index ${newIndex} is undefined` );
        }

        Ember.set( currentItem, 'selected', false );
        Ember.set( item, 'selected', true );

        Ember.setProperties( selection, {
            index: newIndex,
            item
        });
    },

    /**
     * Select a menu item in the "right" direction
     *
     * When at the top-level of the menu, "right" corresponds to the next
     * sibling item.
     * When inside a sub-menu, "right" corresponds to entering its sub-menu, if
     * it has one.
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectRight() {
        const selections = this.get( 'selections' );

        if ( 1 === selections.length ) {
            this.selectNext();
        } else if ( selections.length > 1 ) {
            this.selectSubMenu();
        }
    },

    /**
     * Select the sub-menu in the current context
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectSubMenu() {
        const selections = this.get( 'selections' );

        if ( selections.length < 1 ) {
            return;
        }

        const selection = selections.get( selections.length - 1 );

        if ( !selection ) {
            throw new Ember.Error( 'Last item of `selection` is invalid' );
        }

        const currentItem = Ember.get( selection, 'item' );

        if ( !currentItem ) {
            throw new Ember.Error( 'Last selection menu item is invalid' );
        }

        const items = Ember.get( currentItem, 'items' );

        if ( !items ) {
            return;
        }

        const index = 0;
        const item = items[ index ];

        if ( !item ) {
            throw new Ember.Error( 'First item in selected sub-menu is undefined' );
        }

        Ember.set( item, 'selected', true );

        selections.pushObject({
            index,
            item,
            items
        });
    },

    /**
     * Select a menu item in the "up" direction
     *
     * When at the top level, "up" corresponds to no action.
     * When in the first sub-menu and on the first item, "up" corresponds to
     * selecting the top level.
     * When in any other sub-menu, "up" corresponds to selecting the previous
     * sibling menu item.
     *
     * @function
     * @throws {ember/Error}
     * @returns {undefined}
     */
    selectUp() {
        const selections = this.get( 'selections' );
        const selectionsLength = selections.length;

        if ( selectionsLength < 1 ) {
            return;
        }

        if ( 2 === selectionsLength ) {
            const selection = selections.get( 1 );

            if ( 0 === Ember.get( selection, 'index' ) ) {
                this.selectParent();
                return;
            }
        }

        this.selectPrevious();
    },

    /**
     * Trigger the showAll menu-item
     *
     * @function
     * @returns {undefined}
     */
    showAll() {
        this.set( 'showingAll', true );
    }

});
<|endoftext|>


<commit_before>import { createLogic } from 'redux-logic';
import history from 'fm3/history';
import refModals from 'fm3/refModals';
import allTips from 'fm3/tips/index.json';
import * as at from 'fm3/actionTypes';

const tipKeys = allTips.map(([key]) => key);

let lastActionType;

export const urlLogic = createLogic({
  type: [
    at.MAP_LOAD_STATE, at.MAP_REFOCUS, /^ROUTE_PLANNER_/, at.SET_TOOL, at.CLEAR_MAP, at.MAP_RESET,
    at.TRACK_VIEWER_SET_TRACK_UID, at.TRACK_VIEWER_COLORIZE_TRACK_BY, at.TRACK_VIEWER_DOWNLOAD_TRACK,
    at.GALLERY_REQUEST_IMAGE, at.GALLERY_CLEAR, at.GALLERY_SHOW_FILTER, at.GALLERY_HIDE_FILTER, at.GALLERY_SHOW_UPLOAD_MODAL, at.GALLERY_HIDE_UPLOAD_MODAL,
    at.CHANGESETS_SET_DAYS, at.CHANGESETS_SET_AUTHOR_NAME,
    /^INFO_POINT_.*/, /^DISTANCE_MEASUREMENT_.*/, /^AREA_MEASUREMENT_.*/,
    at.ELEVATION_MEASUREMENT_SET_POINT,
    at.GALLERY_SET_FILTER, at.SET_ACTIVE_MODAL, /^TIPS_.*/,
    at.AUTH_CHOOSE_LOGIN_METHOD, at.AUTH_LOGIN_CLOSE, /^AUTH_LOGIN_WITH_.*/,
    /^OSM_LOAD_.*/, at.ENABLE_UPDATING_URL,
  ],
  process({ getState, action }, dispatch, done) {
    const {
      map,
      routePlanner,
      trackViewer,
      gallery,
      infoPoint,
      changesets,
      distanceMeasurement,
      areaMeasurement,
      elevationMeasurement,
      gallery: { filter: galleryFilter },
      main,
      tips,
      auth,
    } = getState();

    if (!main.urlUpdatingEnabled) {
      done();
      return;
    }

    const queryParts = [
      `map=${map.zoom}/${serializePoint({ lat: map.lat, lon: map.lon })}`,
      `layers=${map.mapType}${map.overlays.join('')}`,
    ];

    if (main.tool) {
      queryParts.push(`tool=${main.tool}`);
    }

    if (routePlanner.start || routePlanner.finish || routePlanner.midpoints.length) {
      queryParts.push(`points=${[routePlanner.start, ...routePlanner.midpoints, routePlanner.finish].map(point => serializePoint(point)).join(',')}`);

      if (routePlanner.transportType) {
        queryParts.push(`transport=${routePlanner.transportType}`);
      }
    }

    if (trackViewer.trackUID) {
      queryParts.push(`track-uid=${trackViewer.trackUID}`);
    }

    if (trackViewer.gpxUrl) {
      queryParts.push(`gpx-url=${trackViewer.gpxUrl}`);
    }

    if (trackViewer.osmNodeId) {
      queryParts.push(`osm-node=${trackViewer.osmNodeId}`);
    }

    if (trackViewer.osmWayId) {
      queryParts.push(`osm-way=${trackViewer.osmWayId}`);
    }

    if (trackViewer.osmRelationId) {
      queryParts.push(`osm-relation=${trackViewer.osmRelationId}`);
    }

    if (trackViewer.colorizeTrackBy) {
      queryParts.push(`track-colorize-by=${trackViewer.colorizeTrackBy}`);
    }

    if (gallery.activeImageId) {
      queryParts.push(`image=${gallery.activeImageId}`);
    }

    if (changesets.days) {
      queryParts.push(`changesets-days=${changesets.days}`);
    }

    if (changesets.authorName) {
      queryParts.push(`changesets-author=${encodeURIComponent(changesets.authorName)}`);
    }

    if (infoPoint.points.length) {
      queryParts.push(...infoPoint.points.map(point => `info-point=${serializePoint(point)}${point.label ? `,${encodeURIComponent(point.label)}` : ''}`));
    }

    if (distanceMeasurement.points && distanceMeasurement.points.length) {
      queryParts.push(`distance-measurement-points=${distanceMeasurement.points
        .map(point => serializePoint(point)).join(',')}`);
    }

    if (areaMeasurement.points && areaMeasurement.points.length) {
      queryParts.push(`area-measurement-points=${areaMeasurement.points
        .map(point => serializePoint(point)).join(',')}`);
    }

    if (elevationMeasurement.point) {
      queryParts.push(`elevation-measurement-point=${serializePoint(elevationMeasurement.point)}`);
    }

    if (galleryFilter.userId) {
      queryParts.push(`gallery-user-id=${galleryFilter.userId}`);
    }

    if (galleryFilter.tag) {
      queryParts.push(`gallery-tag=${encodeURIComponent(galleryFilter.tag)}`);
    }

    if (galleryFilter.ratingFrom) {
      queryParts.push(`gallery-rating-from=${galleryFilter.ratingFrom}`);
    }

    if (galleryFilter.ratingTo) {
      queryParts.push(`gallery-rating-to=${galleryFilter.ratingTo}`);
    }

    if (galleryFilter.takenAtFrom) {
      queryParts.push(`gallery-taken-at-from=${galleryFilter.takenAtFrom.toISOString().replace(/T.*/, '')}`);
    }

    if (galleryFilter.takenAtTo) {
      queryParts.push(`gallery-taken-at-to=${galleryFilter.takenAtTo.toISOString().replace(/T.*/, '')}`);
    }

    if (galleryFilter.createdAtFrom) {
      queryParts.push(`gallery-created-at-from=${galleryFilter.createdAtFrom.toISOString().replace(/T.*/, '')}`);
    }

    if (galleryFilter.createdAtTo) {
      queryParts.push(`gallery-created-at-to=${galleryFilter.createdAtTo.toISOString().replace(/T.*/, '')}`);
    }

    if (main.activeModal && refModals.includes(main.activeModal)) {
      queryParts.push(`show=${main.activeModal}`);
    }

    if (gallery.showFilter) {
      queryParts.push('show=gallery-filter');
    }

    if (gallery.showUploadModal) {
      queryParts.push('show=gallery-upload');
    }

    if (auth.chooseLoginMethod) {
      queryParts.push('show=login');
    }

    if (main.activeModal === 'tips' && tips.tip && tipKeys.includes(tips.tip)) {
      queryParts.push(`tip=${tips.tip}`);
    }

    const search = `?${queryParts.join('&')}`;

    if (window.location.search !== !search && !(action.meta && action.meta.isLocationChange)) {
      const method = [
        at.MAP_REFOCUS,
        at.DISTANCE_MEASUREMENT_UPDATE_POINT,
        at.AREA_MEASUREMENT_UPDATE_POINT,
      ].includes(lastActionType) ? 'replace' : 'push';
      history[method]({ pathname: '/', search });
      lastActionType = action.type;
    }

    done();
  },
});

function serializePoint(point) {
  return point ? `${point.lat.toFixed(6)}/${point.lon.toFixed(6)}` : '';
}

export default urlLogic;
<commit_msg>fixed URL updating<commit_after>import { createLogic } from 'redux-logic';
import history from 'fm3/history';
import refModals from 'fm3/refModals';
import allTips from 'fm3/tips/index.json';
import * as at from 'fm3/actionTypes';

const tipKeys = allTips.map(([key]) => key);

let lastActionType;

export const urlLogic = createLogic({
  type: [
    at.MAP_LOAD_STATE, at.MAP_REFOCUS, /^ROUTE_PLANNER_/, at.SET_TOOL, at.CLEAR_MAP, at.MAP_RESET,
    at.TRACK_VIEWER_SET_TRACK_UID, at.TRACK_VIEWER_COLORIZE_TRACK_BY, at.TRACK_VIEWER_DOWNLOAD_TRACK,
    at.GALLERY_REQUEST_IMAGE, at.GALLERY_CLEAR, at.GALLERY_SHOW_FILTER, at.GALLERY_HIDE_FILTER, at.GALLERY_SHOW_UPLOAD_MODAL, at.GALLERY_HIDE_UPLOAD_MODAL,
    at.CHANGESETS_SET_DAYS, at.CHANGESETS_SET_AUTHOR_NAME,
    /^INFO_POINT_.*/, /^DISTANCE_MEASUREMENT_.*/, /^AREA_MEASUREMENT_.*/,
    at.ELEVATION_MEASUREMENT_SET_POINT,
    at.GALLERY_SET_FILTER, at.SET_ACTIVE_MODAL, /^TIPS_.*/,
    at.AUTH_CHOOSE_LOGIN_METHOD, at.AUTH_LOGIN_CLOSE, /^AUTH_LOGIN_WITH_.*/,
    /^OSM_LOAD_.*/, at.ENABLE_UPDATING_URL,
  ],
  process({ getState, action }, dispatch, done) {
    const {
      map,
      routePlanner,
      trackViewer,
      gallery,
      infoPoint,
      changesets,
      distanceMeasurement,
      areaMeasurement,
      elevationMeasurement,
      gallery: { filter: galleryFilter },
      main,
      tips,
      auth,
    } = getState();

    if (!main.urlUpdatingEnabled) {
      done();
      return;
    }

    const queryParts = [
      `map=${map.zoom}/${serializePoint({ lat: map.lat, lon: map.lon })}`,
      `layers=${map.mapType}${map.overlays.join('')}`,
    ];

    if (main.tool) {
      queryParts.push(`tool=${main.tool}`);
    }

    if (routePlanner.start || routePlanner.finish || routePlanner.midpoints.length) {
      queryParts.push(`points=${[routePlanner.start, ...routePlanner.midpoints, routePlanner.finish].map(point => serializePoint(point)).join(',')}`);

      if (routePlanner.transportType) {
        queryParts.push(`transport=${routePlanner.transportType}`);
      }
    }

    if (trackViewer.trackUID) {
      queryParts.push(`track-uid=${trackViewer.trackUID}`);
    }

    if (trackViewer.gpxUrl) {
      queryParts.push(`gpx-url=${trackViewer.gpxUrl}`);
    }

    if (trackViewer.osmNodeId) {
      queryParts.push(`osm-node=${trackViewer.osmNodeId}`);
    }

    if (trackViewer.osmWayId) {
      queryParts.push(`osm-way=${trackViewer.osmWayId}`);
    }

    if (trackViewer.osmRelationId) {
      queryParts.push(`osm-relation=${trackViewer.osmRelationId}`);
    }

    if (trackViewer.colorizeTrackBy) {
      queryParts.push(`track-colorize-by=${trackViewer.colorizeTrackBy}`);
    }

    if (gallery.activeImageId) {
      queryParts.push(`image=${gallery.activeImageId}`);
    }

    if (changesets.days) {
      queryParts.push(`changesets-days=${changesets.days}`);
    }

    if (changesets.authorName) {
      queryParts.push(`changesets-author=${encodeURIComponent(changesets.authorName)}`);
    }

    if (infoPoint.points.length) {
      queryParts.push(...infoPoint.points.map(point => `info-point=${serializePoint(point)}${point.label ? `,${encodeURIComponent(point.label)}` : ''}`));
    }

    if (distanceMeasurement.points && distanceMeasurement.points.length) {
      queryParts.push(`distance-measurement-points=${distanceMeasurement.points
        .map(point => serializePoint(point)).join(',')}`);
    }

    if (areaMeasurement.points && areaMeasurement.points.length) {
      queryParts.push(`area-measurement-points=${areaMeasurement.points
        .map(point => serializePoint(point)).join(',')}`);
    }

    if (elevationMeasurement.point) {
      queryParts.push(`elevation-measurement-point=${serializePoint(elevationMeasurement.point)}`);
    }

    if (galleryFilter.userId) {
      queryParts.push(`gallery-user-id=${galleryFilter.userId}`);
    }

    if (galleryFilter.tag) {
      queryParts.push(`gallery-tag=${encodeURIComponent(galleryFilter.tag)}`);
    }

    if (galleryFilter.ratingFrom) {
      queryParts.push(`gallery-rating-from=${galleryFilter.ratingFrom}`);
    }

    if (galleryFilter.ratingTo) {
      queryParts.push(`gallery-rating-to=${galleryFilter.ratingTo}`);
    }

    if (galleryFilter.takenAtFrom) {
      queryParts.push(`gallery-taken-at-from=${galleryFilter.takenAtFrom.toISOString().replace(/T.*/, '')}`);
    }

    if (galleryFilter.takenAtTo) {
      queryParts.push(`gallery-taken-at-to=${galleryFilter.takenAtTo.toISOString().replace(/T.*/, '')}`);
    }

    if (galleryFilter.createdAtFrom) {
      queryParts.push(`gallery-created-at-from=${galleryFilter.createdAtFrom.toISOString().replace(/T.*/, '')}`);
    }

    if (galleryFilter.createdAtTo) {
      queryParts.push(`gallery-created-at-to=${galleryFilter.createdAtTo.toISOString().replace(/T.*/, '')}`);
    }

    if (main.activeModal && refModals.includes(main.activeModal)) {
      queryParts.push(`show=${main.activeModal}`);
    }

    if (gallery.showFilter) {
      queryParts.push('show=gallery-filter');
    }

    if (gallery.showUploadModal) {
      queryParts.push('show=gallery-upload');
    }

    if (auth.chooseLoginMethod) {
      queryParts.push('show=login');
    }

    if (main.activeModal === 'tips' && tips.tip && tipKeys.includes(tips.tip)) {
      queryParts.push(`tip=${tips.tip}`);
    }

    const search = `?${queryParts.join('&')}`;

    if (window.location.search !== search && !(action.meta && action.meta.isLocationChange)) {
      const method = [
        at.MAP_REFOCUS,
        at.DISTANCE_MEASUREMENT_UPDATE_POINT,
        at.AREA_MEASUREMENT_UPDATE_POINT,
      ].includes(lastActionType) ? 'replace' : 'push';
      history[method]({ pathname: '/', search });
      lastActionType = action.type;
    }

    done();
  },
});

function serializePoint(point) {
  return point ? `${point.lat.toFixed(6)}/${point.lon.toFixed(6)}` : '';
}

export default urlLogic;
<|endoftext|>


<commit_before>/**
  @module ember-flexberry-gis
*/

import Ember from 'ember';
import { getBounds } from 'ember-flexberry-gis/utils/get-bounds-from-polygon';

/**
  Mixin containing additional logic for layer and layer-like models.
  @class LayerModelMixin
  @extends Ember.Mixin
*/
export default Ember.Mixin.create({
  /**
    Injected local-storage service.

    @property localStorageService
    @type Ember.Service
  */
  localStorageService: Ember.inject.service('local-storage'),

  archTime: null,

  /**
    Object with layer's settings.
    @property settingsAsObject
    @type Object
    @default null
    @readOnly
  */
  settingsAsObject: null,

  /**
    Observes changes in 'settings' property and comutes related 'settingsAsObject' property.
    Computation implemented with observer, because sometimes browser doesn't recompute settingsAsObject,
    when it's computed property.

    @method _settingsDidChange
    @private
  */
  _settingsDidChange: Ember.on('init', Ember.observer('settings', function() {
    let stringToDeserialize = this.get('settings');
    let settingsAsObject = {};

    if (!Ember.isBlank(stringToDeserialize)) {
      try {
        let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
        let defaultSettings = layerClassFactory.createSettings();
        settingsAsObject = Ember.$.extend(true, defaultSettings, JSON.parse(stringToDeserialize));
      } catch (e) {
        Ember.Logger.error(`Computation of 'settingsAsObject' property for '${this.get('name')}' layer has been failed: ${e}`);
      }
    }

    this.set('settingsAsObject', settingsAsObject);

    // Some layer properties can be stored in local-storage, so resulting layer model must be extended with them.
    this._applyLayerPropertiesFromLocalStorage();
  })),

  /**
    Applies layer properties stored in local-storage.

    @method _applyLayerPropertiesFromLocalStorage
    @private
  */
  _applyLayerPropertiesFromLocalStorage() {
    let mapId = this.get('map.id');
    let layerId = this.get('id');
    let localStorageLayer = Ember.isBlank(mapId) || Ember.isBlank(layerId) ?
      null :
      this.get('localStorageService').getFromStorage('layers', mapId).findBy('id', layerId);
    if (!Ember.isNone(localStorageLayer)) {
      // Remove id to avoid explicit merge.
      delete localStorageLayer.id;

      // Apply properties to layer model.
      for (let propertyName in localStorageLayer) {
        if (!localStorageLayer.hasOwnProperty(propertyName)) {
          continue;
        }

        let value = Ember.get(localStorageLayer, propertyName);
        value = typeof value === 'object' ? Ember.$.extend(true, this.get(propertyName), value) : value;

        this.set(propertyName, value);
      }
    }
  },

  /**
    Flag: indicates whether layer can be identified.
    @property canBeIdentified
    @type Boolean
    @readOnly
  */
  canBeIdentified: Ember.computed('isDeleted', 'type', 'settingsAsObject.identifySettings.canBeIdentified', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let identifyOperationIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('identify');
    let identifyOperationIsAvailableForLayerInstance = this.get('settingsAsObject.identifySettings.canBeIdentified') !== false;

    return identifyOperationIsAvailableForLayerClass && identifyOperationIsAvailableForLayerInstance;
  }),

  /**
    Flag: indicates whether 'search' operation is available for this layer.
    @property canBeSearched
    @type Boolean
    @readOnly
  */
  canBeSearched: Ember.computed('isDeleted', 'type', 'settingsAsObject.searchSettings.canBeSearched', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let searchOperationIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('search');
    let searchOperationIsAvailableForLayerInstance = this.get('settingsAsObject.searchSettings.canBeSearched') !== false;

    return searchOperationIsAvailableForLayerClass && searchOperationIsAvailableForLayerInstance;
  }),

  /**
    Flag: indicates whether 'context search' operation is available for this layer.
    @property canBeContextSearched
    @type Boolean
    @readOnly
  */
  canBeContextSearched: Ember.computed('isDeleted', 'type', 'settingsAsObject.searchSettings.canBeContextSearched', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let searchOperationIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('search');
    let searchOperationIsAvailableForLayerInstance = this.get('settingsAsObject.searchSettings.canBeContextSearched') !== false;

    return searchOperationIsAvailableForLayerClass && searchOperationIsAvailableForLayerInstance;
  }),

  /**
   Checks whether layer should be shown on minimap.
    @property showOnMinimap
    @type {Boolean} Flag: indicates whether layer should be shown on minimap.
    @readOnly
  */
  showOnMinimap: Ember.computed('settingsAsObject.showOnMinimap', function () {
    return this.get('settingsAsObject.showOnMinimap');
  }),

  /**
    Flag: layer's whether layer's legend can be displayed.
    @property hasLegend
    @type Boolean
    @readOnly
  */
  legendCanBeDisplayed: Ember.computed('isDeleted', 'type', 'settingsAsObject.legendSettings.legendCanBeDisplayed', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let legendIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('legend');
    let legendIsAvailableForLayerInstance = this.get('settingsAsObject.legendSettings.legendCanBeDisplayed') !== false;

    return legendIsAvailableForLayerClass && legendIsAvailableForLayerInstance;
  }),

  /**
    Collection of nested layers.
    @property layers
    @return {Array} collection of child layers
  */
  layers: Ember.computed('map', 'map.mapLayer', function () {
    try {
      let layers = this.get('map.mapLayer');
      if (layers) {
        let id = this.get('id');
        if (!Ember.isBlank(id)) {
          return layers.filterBy('parent.id', id);
        }
      }
    } catch (e) {
      Ember.Logger.error(`Computation of 'layers' property for '${this.get('name')}' layer has been failed: ${e}`);
    }

    return Ember.A();
  }),

  /**
    Layer's latLngBounds.
    @property bounds
    @readonly
    @return L.LatLngBounds this layer's latLngBounds.
  */
  bounds: Ember.computed(function () {
    let layers = this.get('layers').filterBy('visibility', true);
    let type = this.get('type');

    let layerBounds;

    if (type === 'group' && layers.length > 0) {
      let earthBounds = L.latLngBounds([
        [-90, -180],
        [90, 180]
      ]);

      for (let layer of layers) {
        if (layerBounds && layerBounds.equals(earthBounds)) {
          break;
        }

        let bounds = layer.get('bounds');
        layerBounds = layerBounds ? layerBounds.extend(bounds) : L.latLngBounds(bounds);
      }
    } else {
      let boundingBox = this.get('boundingBox');
      let bounds = getBounds(boundingBox);
      layerBounds = L.latLngBounds([bounds.minLat, bounds.minLng], [bounds.maxLat, bounds.maxLng]);
    }

    return layerBounds;
  }).volatile()
});
<commit_msg>read from local storage when all maplayer data is loaded (#586)<commit_after>/**
  @module ember-flexberry-gis
*/

import Ember from 'ember';
import { getBounds } from 'ember-flexberry-gis/utils/get-bounds-from-polygon';

/**
  Mixin containing additional logic for layer and layer-like models.
  @class LayerModelMixin
  @extends Ember.Mixin
*/
export default Ember.Mixin.create({
  /**
    Injected local-storage service.

    @property localStorageService
    @type Ember.Service
  */
  localStorageService: Ember.inject.service('local-storage'),

  archTime: null,

  /**
    Object with layer's settings.
    @property settingsAsObject
    @type Object
    @default null
    @readOnly
  */
  settingsAsObject: null,

  /**
    Observes changes in 'settings' property and comutes related 'settingsAsObject' property.
    Computation implemented with observer, because sometimes browser doesn't recompute settingsAsObject,
    when it's computed property.

    @method _settingsDidChange
    @private
  */
  _settingsDidChange: Ember.on('init', Ember.observer('settings', 'map', 'map.mapLayer', function() {
    let stringToDeserialize = this.get('settings');
    let settingsAsObject = {};

    if (!Ember.isBlank(stringToDeserialize)) {
      try {
        let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
        let defaultSettings = layerClassFactory.createSettings();
        settingsAsObject = Ember.$.extend(true, defaultSettings, JSON.parse(stringToDeserialize));
      } catch (e) {
        Ember.Logger.error(`Computation of 'settingsAsObject' property for '${this.get('name')}' layer has been failed: ${e}`);
      }
    }

    this.set('settingsAsObject', settingsAsObject);

    // Some layer properties can be stored in local-storage, so resulting layer model must be extended with them.
    this._applyLayerPropertiesFromLocalStorage();
  })),

  /**
    Applies layer properties stored in local-storage.

    @method _applyLayerPropertiesFromLocalStorage
    @private
  */
  _applyLayerPropertiesFromLocalStorage() {
    let mapId = this.get('map.id');
    let layerId = this.get('id');
    let localStorageLayer = Ember.isBlank(mapId) || Ember.isBlank(layerId) || Ember.isNone(this.get('localStorageService')) ?
      null :
      this.get('localStorageService').getFromStorage('layers', mapId).findBy('id', layerId);
    if (!Ember.isNone(localStorageLayer)) {
      // Remove id to avoid explicit merge.
      delete localStorageLayer.id;

      // Apply properties to layer model.
      for (let propertyName in localStorageLayer) {
        if (!localStorageLayer.hasOwnProperty(propertyName)) {
          continue;
        }

        let value = Ember.get(localStorageLayer, propertyName);
        value = typeof value === 'object' ? Ember.$.extend(true, this.get(propertyName), value) : value;

        this.set(propertyName, value);
      }
    }
  },

  /**
    Flag: indicates whether layer can be identified.
    @property canBeIdentified
    @type Boolean
    @readOnly
  */
  canBeIdentified: Ember.computed('isDeleted', 'type', 'settingsAsObject.identifySettings.canBeIdentified', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let identifyOperationIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('identify');
    let identifyOperationIsAvailableForLayerInstance = this.get('settingsAsObject.identifySettings.canBeIdentified') !== false;

    return identifyOperationIsAvailableForLayerClass && identifyOperationIsAvailableForLayerInstance;
  }),

  /**
    Flag: indicates whether 'search' operation is available for this layer.
    @property canBeSearched
    @type Boolean
    @readOnly
  */
  canBeSearched: Ember.computed('isDeleted', 'type', 'settingsAsObject.searchSettings.canBeSearched', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let searchOperationIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('search');
    let searchOperationIsAvailableForLayerInstance = this.get('settingsAsObject.searchSettings.canBeSearched') !== false;

    return searchOperationIsAvailableForLayerClass && searchOperationIsAvailableForLayerInstance;
  }),

  /**
    Flag: indicates whether 'context search' operation is available for this layer.
    @property canBeContextSearched
    @type Boolean
    @readOnly
  */
  canBeContextSearched: Ember.computed('isDeleted', 'type', 'settingsAsObject.searchSettings.canBeContextSearched', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let searchOperationIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('search');
    let searchOperationIsAvailableForLayerInstance = this.get('settingsAsObject.searchSettings.canBeContextSearched') !== false;

    return searchOperationIsAvailableForLayerClass && searchOperationIsAvailableForLayerInstance;
  }),

  /**
   Checks whether layer should be shown on minimap.
    @property showOnMinimap
    @type {Boolean} Flag: indicates whether layer should be shown on minimap.
    @readOnly
  */
  showOnMinimap: Ember.computed('settingsAsObject.showOnMinimap', function () {
    return this.get('settingsAsObject.showOnMinimap');
  }),

  /**
    Flag: layer's whether layer's legend can be displayed.
    @property hasLegend
    @type Boolean
    @readOnly
  */
  legendCanBeDisplayed: Ember.computed('isDeleted', 'type', 'settingsAsObject.legendSettings.legendCanBeDisplayed', function () {
    if (this.get('isDeleted')) {
      return false;
    }

    let layerClassFactory = Ember.getOwner(this).knownForType('layer', this.get('type'));
    let legendIsAvailableForLayerClass = Ember.A(Ember.get(layerClassFactory, 'operations') || []).contains('legend');
    let legendIsAvailableForLayerInstance = this.get('settingsAsObject.legendSettings.legendCanBeDisplayed') !== false;

    return legendIsAvailableForLayerClass && legendIsAvailableForLayerInstance;
  }),

  /**
    Collection of nested layers.
    @property layers
    @return {Array} collection of child layers
  */
  layers: Ember.computed('map', 'map.mapLayer', function () {
    try {
      let layers = this.get('map.mapLayer');
      if (layers) {
        let id = this.get('id');
        if (!Ember.isBlank(id)) {
          return layers.filterBy('parent.id', id);
        }
      }
    } catch (e) {
      Ember.Logger.error(`Computation of 'layers' property for '${this.get('name')}' layer has been failed: ${e}`);
    }

    return Ember.A();
  }),

  /**
    Layer's latLngBounds.
    @property bounds
    @readonly
    @return L.LatLngBounds this layer's latLngBounds.
  */
  bounds: Ember.computed(function () {
    let layers = this.get('layers').filterBy('visibility', true);
    let type = this.get('type');

    let layerBounds;

    if (type === 'group' && layers.length > 0) {
      let earthBounds = L.latLngBounds([
        [-90, -180],
        [90, 180]
      ]);

      for (let layer of layers) {
        if (layerBounds && layerBounds.equals(earthBounds)) {
          break;
        }

        let bounds = layer.get('bounds');
        layerBounds = layerBounds ? layerBounds.extend(bounds) : L.latLngBounds(bounds);
      }
    } else {
      let boundingBox = this.get('boundingBox');
      let bounds = getBounds(boundingBox);
      layerBounds = L.latLngBounds([bounds.minLat, bounds.minLng], [bounds.maxLat, bounds.maxLng]);
    }

    return layerBounds;
  }).volatile()
});
<|endoftext|>


<commit_before>import { guidFor } from '@ember/object/internals';
import Service, { inject as service } from '@ember/service';
import { assign } from '@ember/polyfills';
import { A } from '@ember/array';
import { get } from '@ember/object';
import { gte } from 'ember-compatibility-helpers';

//TODO: consider polyfilled Set
const VALID_HEAD_TAGS = A([
  'base',
  'link',
  'meta',
  'script',
  'noscript',
  'title'
]);

export default Service.extend({
  headData: service(),

  // crawl up the active route stack and collect head tags
  collectHeadTags() {
    let tags = {};
    let currentHandlerInfos;

    if (gte('3.6.0-beta.1')) {
      currentHandlerInfos = this.get('router.targetState.routerJsState.routeInfos');
    } else {
      currentHandlerInfos = this.get('router._routerMicrolib.currentHandlerInfos');
      if (!currentHandlerInfos) {
        currentHandlerInfos = this.get('router.router.currentHandlerInfos');
      }
    }

    let handlerInfos = A(currentHandlerInfos);
    handlerInfos.forEach((handlerInfo) => {
      if (gte('3.6.0-beta.1')) {
        assign(tags, this._extractHeadTagsFromRoute(handlerInfo.route));
      } else {
        assign(tags, this._extractHeadTagsFromRoute(handlerInfo.handler));
      }
    });
    let tagArray = A(Object.keys(tags)).map((id) => tags[id]);
    this.set('headData.headTags', A(tagArray));
  },

  _extractHeadTagsFromRoute(route) {
    let headTags = get(route, 'headTags');
    if (!headTags) {
      return {};
    }
    if (typeof headTags === 'function') {
      headTags = headTags.apply(route);
    } else if (typeof headTags !== 'object') {
      // not recognized construct
      return {};
    }
    // convert headTags to object
    return this._buildTags(headTags);
  },

  // ensure all tags have a tagId and build object keyed by id
  _buildTags(headTagsArray) {
    let tagMap = {};
    A(headTagsArray).forEach(function(tagDefinition) {
      if(!VALID_HEAD_TAGS.includes(tagDefinition.type)) {
        return;
      }
      let tagId = tagDefinition.tagId;
      if (!tagId) {
        tagId = guidFor(tagDefinition);
      }
      tagMap[tagId] = tagDefinition;
    });
    return tagMap;
  }
});
<commit_msg>Ember Engines: problem with extraction head tags from undefined route<commit_after>import { guidFor } from '@ember/object/internals';
import Service, { inject as service } from '@ember/service';
import { assign } from '@ember/polyfills';
import { A } from '@ember/array';
import { get } from '@ember/object';
import { gte } from 'ember-compatibility-helpers';

//TODO: consider polyfilled Set
const VALID_HEAD_TAGS = A([
  'base',
  'link',
  'meta',
  'script',
  'noscript',
  'title'
]);

export default Service.extend({
  headData: service(),

  // crawl up the active route stack and collect head tags
  collectHeadTags() {
    let tags = {};
    let currentHandlerInfos;

    if (gte('3.6.0-beta.1')) {
      currentHandlerInfos = this.get('router.targetState.routerJsState.routeInfos');
    } else {
      currentHandlerInfos = this.get('router._routerMicrolib.currentHandlerInfos');
      if (!currentHandlerInfos) {
        currentHandlerInfos = this.get('router.router.currentHandlerInfos');
      }
    }

    let handlerInfos = A(currentHandlerInfos);
    handlerInfos.forEach((handlerInfo) => {
      if (gte('3.6.0-beta.1')) {
        assign(tags, this._extractHeadTagsFromRoute(handlerInfo.route));
      } else {
        assign(tags, this._extractHeadTagsFromRoute(handlerInfo.handler));
      }
    });
    let tagArray = A(Object.keys(tags)).map((id) => tags[id]);
    this.set('headData.headTags', A(tagArray));
  },

  _extractHeadTagsFromRoute(route) {
    if (!route) {
      return {};
    }

    let headTags = get(route, 'headTags');
    if (!headTags) {
      return {};
    }
    if (typeof headTags === 'function') {
      headTags = headTags.apply(route);
    } else if (typeof headTags !== 'object') {
      // not recognized construct
      return {};
    }
    // convert headTags to object
    return this._buildTags(headTags);
  },

  // ensure all tags have a tagId and build object keyed by id
  _buildTags(headTagsArray) {
    let tagMap = {};
    A(headTagsArray).forEach(function(tagDefinition) {
      if(!VALID_HEAD_TAGS.includes(tagDefinition.type)) {
        return;
      }
      let tagId = tagDefinition.tagId;
      if (!tagId) {
        tagId = guidFor(tagDefinition);
      }
      tagMap[tagId] = tagDefinition;
    });
    return tagMap;
  }
});
<|endoftext|>


<commit_before>/**
 * Converts degrees to radians.
 *
 * @category Math
 * @param {Number} degrees A number representing the degrees to convert.
 * @throws {TypeError} If `degrees` is not a number.
 * @return {Number} The result in radiands, after conversion.
 * @example
 *
 * degreesToRadians(0);
 * // -> 0
 *
 * degreesToRadians(90);
 * // -> 1.5707963267948966 (π / 2)
 *
 * degreesToRadians(180);
 * // -> 3.141592653589793 (π)
 *
 * degreesToRadians(270);
 * // -> 4.71238898038469 (3π / 2)
 *
 * degreesToRadians(360);
 * // -> 6.283185307179586 (2π)
 */
function degreesToRadians(degrees) {
    'use strict';

    if (typeof degrees !== 'number') {
        throw new TypeError('"degrees" must be a number');
    }

    return degrees * Math.PI / 180;
}

console.log(degreesToRadians(360));
<commit_msg>[ci skip] remove log<commit_after>/**
 * Converts degrees to radians.
 *
 * @category Math
 * @param {Number} degrees A number representing the degrees to convert.
 * @throws {TypeError} If `degrees` is not a number.
 * @return {Number} The result in radiands, after conversion.
 * @example
 *
 * degreesToRadians(0);
 * // -> 0
 *
 * degreesToRadians(90);
 * // -> 1.5707963267948966 (π / 2)
 *
 * degreesToRadians(180);
 * // -> 3.141592653589793 (π)
 *
 * degreesToRadians(270);
 * // -> 4.71238898038469 (3π / 2)
 *
 * degreesToRadians(360);
 * // -> 6.283185307179586 (2π)
 */
function degreesToRadians(degrees) {
    'use strict';

    if (typeof degrees !== 'number') {
        throw new TypeError('"degrees" must be a number');
    }

    return degrees * Math.PI / 180;
}
<|endoftext|>


<commit_before>/*!

   hlsjs engine plugin for Flowplayer HTML5

   Copyright (c) 2015, Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.min.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   requires:
   - Flowplayer HTML5 version 6.x or greater
   - hls.js https://github.com/dailymotion/hls.js

*/

(function () {
    var win = window,
        engineName = "hlsjs",
        extend = flowplayer.extend,

        engineImpl = function hlsjsEngine(player, root) {
            var bean = flowplayer.bean,
                common = flowplayer.common,
                videoTag,
                hls;

                engine = {
                    engineName: engineName,

                    pick: function (sources) {
                        var i,
                            source;

                        for (i = 0; i < sources.length; i = i + 1) {
                            source = sources[i];
                            if (/mpegurl/i.test(source.type) && (!source.engine || source.engine == "hlsjs")) {
                                return source;
                            }
                        }
                    },

                    load: function (video) {
                        common.removeNode(common.findDirect("video", root)[0] || common.find(".fp-player > video", root)[0]);
                        videoTag = common.createElement("video");

                        bean.on(videoTag, "play", function () {
                            player.trigger('resume', [player]);
                        });
                        bean.on(videoTag, "pause", function () {
                            player.trigger('pause', [player]);
                        });
                        bean.on(videoTag, "timeupdate", function () {
                            player.trigger('progress', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "loadeddata", function () {
                            extend(video, {
                                duration: videoTag.duration,
                                seekable: videoTag.seekable.end(null),
                                width: videoTag.videoWidth,
                                height: videoTag.videoHeight,
                                url: videoTag.currentSrc
                            });
                            player.trigger('ready', [player, video]);

                            // fix timing for poster class
                            var poster = "is-poster";
                            if (common.hasClass(root, poster)) {
                                player.on("stop.hlsjs", function () {
                                    setTimeout(function () {
                                        common.addClass(root, poster);
                                        bean.one(videoTag, "play.hlsjs", function () {
                                            common.removeClass(root, poster);
                                        });
                                    }, 0);
                                });
                            }

                            if (player.conf.autoplay) {
                                // let the fp API take care of autoplay
                                videoTag.play();
                            }
                        });
                        bean.on(videoTag, "seeked", function () {
                            player.trigger('seek', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "progress", function (e) {
                            try {
                                var buffered = videoTag.buffered,
                                    buffer = buffered.end(null), // first loaded buffer
                                    ct = videoTag.currentTime,
                                    buffend = 0,
                                    i;

                                // buffered.end(null) will not always return the current buffer
                                // so we cycle through the time ranges to obtain it
                                if (ct) {
                                    for (i = 1; i < buffered.length; i = i + 1) {
                                        buffend = buffered.end(i);

                                        if (buffend >= ct && buffered.start(i) <= ct) {
                                            buffer = buffend;
                                        }
                                    }
                                }
                                video.buffer = buffer;
                            } catch (ignored) {}
                            player.trigger('buffer', [player, e]);
                        });
                        bean.on(videoTag, "ended", function () {
                            player.trigger('finish', [player]);
                        });
                        bean.on(videoTag, "volumechange", function () {
                            player.trigger('volume', [player, videoTag.volume]);
                        });

                        videoTag.className = 'fp-engine hlsjs-engine';
                        common.prepend(common.find(".fp-player", root)[0], videoTag);

                        var conf = extend({}, player.conf.hlsjs, player.conf.clip.hlsjs);
                        hls = new Hls(conf);

                        hls.on(Hls.Events.MSE_ATTACHED, function () {
                            hls.loadSource(video.src);

                        }).on(Hls.Events.ERROR, function (e, data) {
                            var fperr,
                                errtypes = Hls.ErrorTypes,
                                errobj;

                            if (data.fatal) {
                                // try recovery?
                                switch (data.type) {
                                case Hls.ErrorTypes.NETWORK_ERROR:
                                    // in theory should be 3 (Network error)
                                    fperr = 4;
                                    break;
                                case Hls.ErrorTypes.MEDIA_ERROR:
                                    fperr = 3;
                                    break;
                                default:
                                    fperr = 5;
                                    break;
                                }
                                errobj = {code: fperr};
                                if (fperr > 2) {
                                    errobj.video = extend(video, {
                                        src: video.src,
                                        url: video.src
                                    });
                                }
                                player.trigger('error', [player, errobj]);
                            }
                            /* TODO: */
                            // log non fatals
                        });

                        player.on("error unload", function (e) {
                            if (e.type == "error" || player.conf.splash) {
                                hls.destroy();

                                // why does unload not do this already?
                                common.find('video.fp-engine', root).forEach(common.removeNode);
                            }
                        });

                        hls.attachVideo(videoTag);
                    },

                    resume: function () {
                        videoTag.play();
                    },

                    pause: function () {
                        videoTag.pause();
                    },

                    seek: function (time) {
                        videoTag.currentTime = time;
                    },

                    volume: function (level) {
                        if (videoTag) {
                            videoTag.volume = level;
                        }
                    },

                    speed: function (val) {
                        videoTag.playbackRate = val;
                        player.trigger('speed', [player, val]);
                    },

                    unload: function () {
                        player.trigger('unload', [player]);
                    }
                };

            return engine;
        };

    if (Hls.isSupported()) {
        // only load engine if it can be used
        engineImpl.engineName = engineName; // must be exposed
        engineImpl.canPlay = function (type, conf) {
            return /mpegurl/i.test(type);
        };

        // put on top of engine stack
        // so hlsjs is tested before html5 video hls and flash hls
        flowplayer.engines.unshift(engineImpl);
    }

}());
<commit_msg>Update engine config object at earliest opportunity<commit_after>/*!

   hlsjs engine plugin for Flowplayer HTML5

   Copyright (c) 2015, Flowplayer Oy

   Released under the MIT License:
   http://www.opensource.org/licenses/mit-license.php

   Includes hls.min.js
   Copyright (c) 2015 Dailymotion (http://www.dailymotion.com)
   https://github.com/dailymotion/hls.js/blob/master/LICENSE

   requires:
   - Flowplayer HTML5 version 6.x or greater
   - hls.js https://github.com/dailymotion/hls.js

*/

(function () {
    var win = window,
        engineName = "hlsjs",
        hlsconf,
        extend = flowplayer.extend,

        engineImpl = function hlsjsEngine(player, root) {
            var bean = flowplayer.bean,
                common = flowplayer.common,
                videoTag,
                hls;

                engine = {
                    engineName: engineName,

                    pick: function (sources) {
                        var i,
                            source;

                        for (i = 0; i < sources.length; i = i + 1) {
                            source = sources[i];
                            if (/mpegurl/i.test(source.type) && (!source.engine || source.engine == "hlsjs")) {
                                return source;
                            }
                        }
                    },

                    load: function (video) {
                        common.removeNode(common.findDirect("video", root)[0] || common.find(".fp-player > video", root)[0]);
                        videoTag = common.createElement("video");

                        bean.on(videoTag, "play", function () {
                            player.trigger('resume', [player]);
                        });
                        bean.on(videoTag, "pause", function () {
                            player.trigger('pause', [player]);
                        });
                        bean.on(videoTag, "timeupdate", function () {
                            player.trigger('progress', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "loadeddata", function () {
                            extend(video, {
                                duration: videoTag.duration,
                                seekable: videoTag.seekable.end(null),
                                width: videoTag.videoWidth,
                                height: videoTag.videoHeight,
                                url: videoTag.currentSrc
                            });
                            player.trigger('ready', [player, video]);

                            // fix timing for poster class
                            var poster = "is-poster";
                            if (common.hasClass(root, poster)) {
                                player.on("stop.hlsjs", function () {
                                    setTimeout(function () {
                                        common.addClass(root, poster);
                                        bean.one(videoTag, "play.hlsjs", function () {
                                            common.removeClass(root, poster);
                                        });
                                    }, 0);
                                });
                            }

                            if (player.conf.autoplay) {
                                // let the fp API take care of autoplay
                                videoTag.play();
                            }
                        });
                        bean.on(videoTag, "seeked", function () {
                            player.trigger('seek', [player, videoTag.currentTime]);
                        });
                        bean.on(videoTag, "progress", function (e) {
                            try {
                                var buffered = videoTag.buffered,
                                    buffer = buffered.end(null), // first loaded buffer
                                    ct = videoTag.currentTime,
                                    buffend = 0,
                                    i;

                                // buffered.end(null) will not always return the current buffer
                                // so we cycle through the time ranges to obtain it
                                if (ct) {
                                    for (i = 1; i < buffered.length; i = i + 1) {
                                        buffend = buffered.end(i);

                                        if (buffend >= ct && buffered.start(i) <= ct) {
                                            buffer = buffend;
                                        }
                                    }
                                }
                                video.buffer = buffer;
                            } catch (ignored) {}
                            player.trigger('buffer', [player, e]);
                        });
                        bean.on(videoTag, "ended", function () {
                            player.trigger('finish', [player]);
                        });
                        bean.on(videoTag, "volumechange", function () {
                            player.trigger('volume', [player, videoTag.volume]);
                        });

                        videoTag.className = 'fp-engine hlsjs-engine';
                        common.prepend(common.find(".fp-player", root)[0], videoTag);

                        hls = new Hls(hlsconf);

                        hls.on(Hls.Events.MSE_ATTACHED, function () {
                            hls.loadSource(video.src);

                        }).on(Hls.Events.ERROR, function (e, data) {
                            var fperr,
                                errtypes = Hls.ErrorTypes,
                                errobj;

                            if (data.fatal) {
                                // try recovery?
                                switch (data.type) {
                                case Hls.ErrorTypes.NETWORK_ERROR:
                                    // in theory should be 3 (Network error)
                                    fperr = 4;
                                    break;
                                case Hls.ErrorTypes.MEDIA_ERROR:
                                    fperr = 3;
                                    break;
                                default:
                                    fperr = 5;
                                    break;
                                }
                                errobj = {code: fperr};
                                if (fperr > 2) {
                                    errobj.video = extend(video, {
                                        src: video.src,
                                        url: video.src
                                    });
                                }
                                player.trigger('error', [player, errobj]);
                            }
                            /* TODO: */
                            // log non fatals
                        });

                        player.on("error unload", function (e) {
                            if (e.type == "error" || player.conf.splash) {
                                hls.destroy();

                                // why does unload not do this already?
                                common.find('video.fp-engine', root).forEach(common.removeNode);
                            }
                        });

                        hls.attachVideo(videoTag);
                    },

                    resume: function () {
                        videoTag.play();
                    },

                    pause: function () {
                        videoTag.pause();
                    },

                    seek: function (time) {
                        videoTag.currentTime = time;
                    },

                    volume: function (level) {
                        if (videoTag) {
                            videoTag.volume = level;
                        }
                    },

                    speed: function (val) {
                        videoTag.playbackRate = val;
                        player.trigger('speed', [player, val]);
                    },

                    unload: function () {
                        player.trigger('unload', [player]);
                    }
                };

            return engine;
        };

    if (Hls.isSupported()) {
        // only load engine if it can be used
        engineImpl.engineName = engineName; // must be exposed
        engineImpl.canPlay = function (type, conf) {
            // merge hlsjs clip config at earliest opportunity
            hlsconf = extend({}, conf.hlsjs, conf.clip.hlsjs);

            return /mpegurl/i.test(type);
        };

        // put on top of engine stack
        // so hlsjs is tested before html5 video hls and flash hls
        flowplayer.engines.unshift(engineImpl);
    }

}());
<|endoftext|>


<commit_before>;
(function () {
  if (typeof HyperbolicCanvas === "undefined") {
    window.HyperbolicCanvas = {};
  }

  var Point = window.HyperbolicCanvas.Point = function (x, y, inverse) {
    this.x = x;
    this.y = inverse ? 1 - y : y;
  };

  Point.prototype.scale = function (canvas, inverse) {
    return canvas.scalePoint(this, inverse);
  };

  var PI = Math.PI;
  var TAU = window.HyperbolicCanvas.TAU = 2 * PI;
  if (typeof Math.TAU === "undefined") {
    Math.TAU = TAU;
  }

  var Canvas = window.HyperbolicCanvas.Canvas = function (el) {
    this.el = el;
    this.appendChildren();
    this.resize();

    this.ctx = this.canvas.getContext('2d');

    if (this.hasClass('hyperbolic-canvas-autoresize')) {
      this.sensor = new ResizeSensor(this.el, this.resize.bind(this));
    }
  };

  /*
  * DOM modifications
  */
  Canvas.prototype.appendChildren = function () {
    this.viewport = document.createElement('div');
    this.canvas = document.createElement('canvas');

    this.viewport.className = 'viewport';
    this.viewport.style.height = this.viewport.style.width = '100%';
    this.canvas.style.display = 'block';
    this.canvas.width = this.canvas.height = 0;

    this.el.appendChild(this.viewport);
    this.viewport.appendChild(this.canvas);
  };

  Canvas.prototype.hasClass = function (className) {
    if (this.el.classList) {
      return this.el.classList.contains(className);
    } else {
      return new RegExp('(^| )' + className + '( |$)', 'gi').test(this.el.className);
    }
  };

  Canvas.prototype.resize = function () {
    this.viewport.style["max-width"] = this.viewport.style["max-height"] = null;

    var w = this.viewport.clientWidth;
    var h = this.viewport.clientHeight;
    this.diameter = w > h ? h : w;
    this.radius =  this.diameter / 2;

    this.canvas.width = this.canvas.height = this.diameter;
    this.viewport.style["max-width"] = this.viewport.style["max-height"] = "" + (this.diameter) + "px";
    this.canvas.style["border-radius"] = "" + Math.floor(this.radius) + "px";
  };

 /*
 * returns a Point object with attributes x and y, relative to canvas center
 *
 * can be called with (number, number, boolean) or (array, boolean)
 */
  Canvas.prototype.pointAt = function (x, y, inverse) {
    // this should allow function to be called with (array, boolean) as well
    if (typeof x === typeof []) {
      cartesian = y;
      y = x[1];
      x = x[0];
    }
    return new Point(x / this.radius - 1, y / this.radius - 1, inverse);
  };

  Canvas.prototype.scalePoint = function (point, inverse) {
    var x = (point.x + 1) * this.radius;
    var y = (point.y + 1) * this.radius;
    return [x, inverse ? this.diameter - y : y];
  };

  Canvas.prototype.drawLineThroughIdealPoints = function (t1, t2) {
    // TODO special case where t1 + t2 === TAU
    //      tangent lines are parallel, and never intersect
    //      hyp === 1 / 0 === Infinity === bad
    //
    //      just draw a straight line?  Does the line exist?
    var diff = t1 > t2 ? t1 - t2 : t2 - t1;
    var halfDiff = diff / 2;
    var farSide = Math.sin(halfDiff); // ?
    var hyp = 1 / Math.sin((PI / 2) - halfDiff);
    // hyp is distance to center of circle whose arc is the line in question

  };

  Canvas.prototype.drawNGon = function (n, center, radius, rotation) {
    // TODO make sure math is correct
    if (n < 3){
      return false;
    }
    var direction = rotation || 0;
    var increment = TAU / n;
    var vertices = [];
    for (var i = 0; i < n; i++) {
      vertices.push(this.findDistantPoint(center, radius, direction));
      direction += increment;
    }
    return this.drawPolygon(vertices);
  };

  Canvas.prototype.drawPolygon = function (vertices) {
    // TODO using drawLine to make polygons does not allow application of fillStyle
    //      arcTo instead?
    var n = vertices.length;
    if (n < 3){
      return false;
    }
    for (var i = 0; i < n; i++) {
      this.drawLine(vertices[i], vertices[(i + 1) % n];
    }
    return true;
  };

  Canvas.prototype.findDistantPoint = function (point, distance, direction) {
    // TODO return the Point relative to point
  };

  Canvas.prototype.distanceFromCenter = function (p) {
    // TODO "edge cases" (ha!) in which a point sufficiently close to boundary is NaN due to floating point math
    var r = this.euclideanDistance(p, new Point(0, 0));
    // TODO is this the right base to use?  is base related to curvature?
    var base = 1.1;
    return Math.log((1 + r) / (1 - r)) / Math.log(base);
  };

  Canvas.prototype.euclideanDistance = function (p1, p2) {
    return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
  };

  window.HyperbolicCanvas.ready = function () {
    var canvases = window.HyperbolicCanvas.canvases = [];
    Array.prototype.forEach.call(document.getElementsByClassName("hyperbolic-canvas"), function (el) {
      canvases.push(new Canvas(el));
    });
  }

  // TODO ask someone if this is the right thing to do.  ready function must wait until ResizeSensor is loaded.
  if (document.readyState != 'loading'){
    window.HyperbolicCanvas.ready();
  } else {
    document.addEventListener('DOMContentLoaded', window.HyperbolicCanvas.ready);
  }
})();
<commit_msg>add calculation of circle center from 3 points<commit_after>;
(function () {
  if (typeof HyperbolicCanvas === "undefined") {
    window.HyperbolicCanvas = {};
  }

  window.HyperbolicCanvas.ready = function () {
    var canvases = window.HyperbolicCanvas.canvases = [];
    Array.prototype.forEach.call(document.getElementsByClassName("hyperbolic-canvas"), function (el) {
      canvases.push(new Canvas(el));
    });
  };

  // Math convenience variables
  var PI = Math.PI;
  var TAU = window.HyperbolicCanvas.TAU = 2 * PI;
  if (typeof Math.TAU === "undefined") {
    Math.TAU = TAU;
  }

  /*
  * Point object relative to the unit circle.
  * Y axis is NOT inverted
  */
  var Point = window.HyperbolicCanvas.Point = function (x, y, inverse) {
    this.x = x;
    this.y = inverse ? 1 - y : y;
  };

  Point.prototype.equals = function (otherPoint) {
    return this.x === otherPoint.x && this.y === otherPoint.y;
  };

  Point.prototype.scale = function (canvas, inverse) {
    return canvas.scalePoint(this, inverse);
  };

  var Canvas = window.HyperbolicCanvas.Canvas = function (el) {
    this.el = el;
    this.appendChildren();
    this.resize();

    this.ctx = this.canvas.getContext('2d');

    if (this.hasClass('hyperbolic-canvas-autoresize')) {
      this.sensor = new ResizeSensor(this.el, this.resize.bind(this));
    }
  };

  /*
  * DOM modifications
  */
  Canvas.prototype.appendChildren = function () {
    this.viewport = document.createElement('div');
    this.canvas = document.createElement('canvas');

    this.viewport.className = 'viewport';
    this.viewport.style.height = this.viewport.style.width = '100%';
    this.canvas.style.display = 'block';
    this.canvas.width = this.canvas.height = 0;

    this.el.appendChild(this.viewport);
    this.viewport.appendChild(this.canvas);
  };

  Canvas.prototype.hasClass = function (className) {
    if (this.el.classList) {
      return this.el.classList.contains(className);
    } else {
      return new RegExp('(^| )' + className + '( |$)', 'gi').test(this.el.className);
    }
  };

  Canvas.prototype.resize = function () {
    this.viewport.style["max-width"] = this.viewport.style["max-height"] = null;

    var w = this.viewport.clientWidth;
    var h = this.viewport.clientHeight;
    this.diameter = w > h ? h : w;
    this.radius =  this.diameter / 2;

    this.canvas.width = this.canvas.height = this.diameter;
    this.viewport.style["max-width"] = this.viewport.style["max-height"] = "" + (this.diameter) + "px";
    this.canvas.style["border-radius"] = "" + Math.floor(this.radius) + "px";
  };

 /*
 * returns a Point object with attributes x and y, relative to canvas center
 *
 * can be called with (number, number, boolean) or (array, boolean)
 */
  Canvas.prototype.pointAt = function (x, y, inverse) {
    // this should allow function to be called with (array, boolean) as well
    if (typeof x === typeof []) {
      cartesian = y;
      y = x[1];
      x = x[0];
    }
    return new Point(x / this.radius - 1, y / this.radius - 1, inverse);
  };

  Canvas.prototype.scalePoint = function (point, inverse) {
    var x = (point.x + 1) * this.radius;
    var y = (point.y + 1) * this.radius;
    return [x, inverse ? this.diameter - y : y];
  };

  Canvas.prototype.drawLineThroughIdealPoints = function (t1, t2) {
    // TODO special case where t1 + t2 === TAU
    //      tangent lines are parallel, and never intersect
    //      hyp === 1 / 0 === Infinity === bad
    //
    //      just draw a straight line?  Does the line exist?
    var diff = t1 > t2 ? t1 - t2 : t2 - t1;
    var halfDiff = diff / 2;
    var farSide = Math.sin(halfDiff); // ?
    var hyp = 1 / Math.sin((PI / 2) - halfDiff);
    // hyp is distance to center of circle whose arc is the line in question

  };

  Canvas.prototype.drawLine = function (p1, p2, infinite) {
    // TODO draw line through any 2 points, and extend to border if infinite === true

    // find slope of line from center to p1
    // find perpindicular line through p1
    // perpindicular line intersects unit circle at X and Y
    // tangents of unit circle at X and Y meet at c

    //get point c

    //circle
    var center = this.centerOfCircleFromThreePoints(p1, p2, c);

    // if infinite === true
    // find ideal points
    // or just draw whole circle... it has border radius
  };

  Canvas.prototype.drawNGon = function (n, center, radius, rotation) {
    // TODO make sure math is correct
    if (n < 3){
      return false;
    }
    var direction = rotation || 0;
    var increment = TAU / n;
    var vertices = [];
    for (var i = 0; i < n; i++) {
      vertices.push(this.findDistantPoint(center, radius, direction));
      direction += increment;
    }
    return this.drawPolygon(vertices);
  };

  Canvas.prototype.drawPolygon = function (vertices) {
    // TODO using drawLine to make polygons does not allow application of fillStyle
    //      arcTo instead?
    var n = vertices.length;
    if (n < 3){
      return false;
    }
    for (var i = 0; i < n; i++) {
      this.drawLine(vertices[i], vertices[(i + 1) % n]);
    }
    return true;
  };

  Canvas.prototype.findDistantPoint = function (point, distance, direction) {
    // TODO return the Point relative to point
  };

  Canvas.prototype.distanceFromCenter = function (p) {
    // TODO "edge cases" (ha!) in which a point sufficiently close to boundary is NaN due to floating point math
    var r = this.euclideanDistance(p, new Point(0, 0));
    // TODO is this the right base to use?  is base related to curvature?
    var base = 1.1;
    return Math.log((1 + r) / (1 - r)) / Math.log(base);
  };

  Canvas.prototype.euclideanDistance = function (p1, p2) {
    return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
  };

  Canvas.prototype.hyperbolicDistance = function (p1, p2) {
    // TODO distance between 2 points
    // rename to "distance" ?

  };

  Canvas.prototype.centerOfCircleFromThreePoints = function (p1, p2, p3) {
    // TODO make someone else check this
    if (p1.equals(p2) || p1.equals(p3) || p2.equals(p3)) {
      // points are not unique
      // TODO throw exception ?
      return false;
    }
    var x, y;
    // center points of chords
    var c1 = new Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2);
    var c2 = new Point((p3.x + p2.x) / 2, (p3.y + p2.y) / 2);
    // slopes of chord bisectors
    var m1 = -1 * (p1.x - p2.x) / (p1.y - p2.y);
    var m2 = -1 * (p3.x - p2.x) / (p3.y - p2.y);

    if (m1 === m2) {
      // points are all in a line
      // TODO throw exception ?
      return false;
    }

    if (m1 === Infinity || m1 === -Infinity) {
      x = c1.x;
    } else if (m2 === Infinity || m2 === -Infinity) {
      x = c2.x;
    }

    if (m1 === 0) {
      y = c1.y;
    } else if (m2 === 0) {
      y = c2.y;
    }

    x = x || (c1.x * m1 - c2.x * m2 + c2.y - c1.y) / (m1 - m2);
    if (m1 === Infinity || m1 === -Infinity) {
      y = y || m2 * (x - c2.x) + c2.y;
    } else {
      y = y || m1 * (x - c1.x) + c1.y;
    }
    return new Point(x, y);
  };

  // TODO ask someone if this is the right thing to do.  ready function must wait until ResizeSensor is loaded.
  if (document.readyState != 'loading'){
    window.HyperbolicCanvas.ready();
  } else {
    document.addEventListener('DOMContentLoaded', window.HyperbolicCanvas.ready);
  }

  // window.circleTest = function () {
  //   var canvas = HyperbolicCanvas.Canvas.prototype;
  //   var zero = new Point(0,0);
  //   for (var i = 0; i < 1000; i++) {
  //     var points = [];
  //     for (var j = 0; j < 3; j++) {
  //       var x = 1 - Math.random() * 2;
  //       var y = Math.sqrt( 1 - x * x);
  //       y = Math.random() > .5 ? y : y * -1;
  //       points.push(new Point(x, y));
  //     }
  //     var center = canvas.centerOfCircleFromThreePoints(points[0], points[1], points[2]);
  //     if (center === false) {
  //       return false;
  //     }
  //     console.log(i);
  //     console.log(points[0])
  //     console.log("d:" + canvas.euclideanDistance(points[0], center));
  //     console.log(points[1])
  //     console.log("d:" + canvas.euclideanDistance(points[1], center));
  //     console.log(points[2])
  //     console.log("d:" + canvas.euclideanDistance(points[2], center));
  //     console.log(center);
  //   }
  //   return true;
  // };
})();
<|endoftext|>


<commit_before>/**
	AS - Auth Server Library
	@description
		If access token exists on cookies,
		automatically get information from
		authentication server then pass it to
		next middleware. Accessible on req.user
	@author Raven Lagrimas
*/

var as_helper = require(__dirname + '/../helpers/auth_server'),
	config = require(__dirname + '/../config/config');

module.exports = function () {
	return function (req, res, next) {
		var set_user = function (err, data) {
				if (err) {
					res.clearCookie('access_token');
					return next(err);
				}
				req.user = data.user_data;
				req.user_id = req.user._id;
				req.user_data = data.user_data[config.app_id + '_data'] || {};
				req.is_admin = req.user_data.admin ? 1 : (req.user.admin ? 1 : 0);
				req.access_token = req.signedCookies.access_token;

				console.dir(req.user_id);
				console.dir(req.access_token);
				console.dir(req.user_data);

				next();
			};

		if (req.signedCookies.access_token) {
			as_helper.get_info({
				access_token : req.signedCookies.access_token,
				self : true
			}, set_user);
		}
		else {
			next();
		}
	};
};
<commit_msg>Changed field name on as.js<commit_after>/**
	AS - Auth Server Library
	@description
		If access token exists on cookies,
		automatically get information from
		authentication server then pass it to
		next middleware. Accessible on req.user
	@author Raven Lagrimas
*/

var as_helper = require(__dirname + '/../helpers/auth_server'),
	config = require(__dirname + '/../config/config');

module.exports = function () {
	return function (req, res, next) {
		var set_user = function (err, data) {
				if (err) {
					res.clearCookie('access_token');
					return next(err);
				}
				req.user = data.user_data;
				req.user_id = req.user._id;
				req.user_data = data.user_data['data_' + config.app_id] || {};
				req.is_admin = req.user_data.admin ? 1 : (req.user.admin ? 1 : 0);
				req.access_token = req.signedCookies.access_token;

				console.dir(req.user_id);
				console.dir(req.access_token);
				console.dir(req.user_data);

				next();
			};

		if (req.signedCookies.access_token) {
			as_helper.get_info({
				access_token : req.signedCookies.access_token,
				self : true
			}, set_user);
		}
		else {
			next();
		}
	};
};
<|endoftext|>