<commit_before>import { basename } from 'path'

import FLACParser from 'flac-parser'
import log from 'npmlog'

import trackFromTags from '../track-from-tags.js'
import { AudioFile } from '@packard/model'
import { typeToStreamData, typeToTag, typeToMB } from './tag-maps.js'

export default function reader (info, progressGroups, onFinish, onError) {
  const path = info.path
  const name = basename(path)
  let gauge = progressGroups.get(name)
  if (!gauge) {
    gauge = log.newGroup(name)
    progressGroups.set(name, gauge)
  }

  const streamData = info.streamData = {}
  const tags = info.tags = {}
  const musicbrainzTags = info.musicbrainzTags = {}
  const parserGauge = gauge.newStream('FLAC tags: ' + name, info.stats.size)

  return parserGauge
    .pipe(new FLACParser())
    .on('data', ({ type, value }) => {
      if (typeToTag.get(type)) {
        tags[typeToTag.get(type)] = value
      } else if (typeToMB.get(type)) {
        musicbrainzTags[typeToMB.get(type)] = value
      } else if (typeToStreamData.get(type)) {
        streamData[typeToStreamData.get(type)] = value
      } else {
        gauge.warn('flac.read', 'unknown type', type, 'value', value)
      }
    })
    .on('error', onError)
    .on('finish', () => {
      parserGauge.end()
      gauge.verbose('flac.read', 'scanned', path)
      info.file = new AudioFile(path, info.stats, info.streamData)

      gauge.silly('flac.read', path, 'streamData', streamData)
      if (streamData.duration) info.duration = parseFloat(streamData.duration)

      gauge.silly('flac.read', path, 'tags', tags)
      gauge.silly('flac.read', path, 'musicbrainzTags', musicbrainzTags)

      onFinish({ track: trackFromTags(info) })
    })
}
<commit_msg>metadata: normalize FLAC tag case<commit_after>import { basename } from 'path'

import FLACParser from 'flac-parser'
import log from 'npmlog'

import trackFromTags from '../track-from-tags.js'
import { AudioFile } from '@packard/model'
import { typeToStreamData, typeToTag, typeToMB } from './tag-maps.js'

export default function reader (info, progressGroups, onFinish, onError) {
  const path = info.path
  const name = basename(path)
  let gauge = progressGroups.get(name)
  if (!gauge) {
    gauge = log.newGroup(name)
    progressGroups.set(name, gauge)
  }

  const streamData = info.streamData = {}
  const tags = info.tags = {}
  const musicbrainzTags = info.musicbrainzTags = {}
  const parserGauge = gauge.newStream('FLAC tags: ' + name, info.stats.size)

  return parserGauge
    .pipe(new FLACParser())
    .on('data', ({ type = '', value }) => {
      if (typeToTag.get(type)) {
        tags[typeToTag.get(type)] = value
      } else if (typeToMB.get(type)) {
        musicbrainzTags[typeToMB.get(type)] = value
      } else if (typeToStreamData.get(type)) {
        streamData[typeToStreamData.get(type)] = value
      // some encoders don't follow all-upper-case convention
      } else if (typeToTag.get(type.toUpperCase())) {
        tags[typeToTag.get(type.toUpperCase())] = value
      } else {
        gauge.warn('flac.read', 'unknown type', type, 'value', value)
      }
    })
    .on('error', onError)
    .on('finish', () => {
      parserGauge.end()
      gauge.verbose('flac.read', 'scanned', path)
      info.file = new AudioFile(path, info.stats, info.streamData)

      gauge.silly('flac.read', path, 'streamData', streamData)
      if (streamData.duration) info.duration = parseFloat(streamData.duration)

      gauge.silly('flac.read', path, 'tags', tags)
      gauge.silly('flac.read', path, 'musicbrainzTags', musicbrainzTags)

      onFinish({ track: trackFromTags(info) })
    })
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
({

    /**
     * Activates a single ui:dialog component by:
     *     1. setting a reference to it on the dialogManager's model.
     *     2. applying event handlers for proper interaction.
     *     3. setting a reference to those event handlers on the dialog's model (for removal later).
     *     4. applying CSS classes to display the dialog.
     *
     * @param {Aura.Component} dialog the ui:dialog component to activate
     * @param {Aura.Component} manager the ui:dialogManager component
     * @return {void}
     */
    activateDialog : function(dialog, manager) {
        var isModal         = dialog.get("v.isModal"),
            clickOutToClose = dialog.get("v.clickOutToClose"),
            autoFocus       = dialog.get("v.autoFocus"),
            handlerConfig   = this.getHandlerConfig(dialog, isModal, clickOutToClose);

        this.applyHandlers(handlerConfig);
        this.toggleDisplay(true, dialog, autoFocus, isModal, handlerConfig);
        manager.set("v._activeDialog", dialog);
        dialog.set("v._handlerConfig", handlerConfig);
    },

    /**
     * Deactivates a single ui:dialog component by:
     *     1. removing the reference to it from the dialogManager's model.
     *     2. removing event handlers.
     *     3. removing the reference to those event handlers from the dialog's model.
     *     4. removing CSS classes to hide the dialog.
     *
     * @param {Aura.Component} dialog the ui:dialog component to deactivate
     * @param {Aura.Component} manager the ui:dialogManager component
     * @return {void}
     */
    deactivateDialog : function(dialog, manager) {
        var isModal       = dialog.get("v.isModal"),
            autoFocus     = dialog.get("v.autoFocus"),
            handlerConfig = dialog.get("v._handlerConfig");

        this.removeHandlers(handlerConfig);
        this.toggleDisplay(false, dialog, autoFocus, isModal, handlerConfig);
        manager.set("v._activeDialog", null);
        dialog.set("v._handlerConfig", null);
    },

    /**
     * Applies the appropriate event handlers for proper interaction.
     *
     * @param {Object} config JS object that contains all the necessary event handlers
     * @return {void}
     */
    applyHandlers : function(config) {

        $A.util.on(document, "keydown", config.keydownHandler, false);
        $A.util.on(document, "click", config.clickHandler, false);
        $A.util.on(window, "resize", config.resizeHandler, false);

    },

    /**
     * Removes the appropriate event handlers to keep the DOM tidy.
     *
     * @param {Object} config JS object that contains all the necessary event handlers
     * @return {void}
     */
    removeHandlers : function(config) {

        $A.util.removeOn(document, "keydown", config.keydownHandler, false);
        $A.util.removeOn(document, "click", config.clickHandler, false);
        $A.util.removeOn(window, "resize", config.resizeHandler, false);

    },

    /**
     * Builds the appropriate DOM event handlers necessary to interact with the
     * active ui:dialog component, as well as references to the document's previously
     * focused element (before the dialog opens), and the first focusable element
     * inside the dialog. This config object is stored on the dialog component's
     * model so we can remove the event handlers when the dialog is finally closed.
     *
     * @param {Aura.Component} dialog the active ui:dialog component
     * @param {Boolean} isModal specifies if the active dialog is modal
     * @param {Boolean} clickOutToClose specifies if clicking outside the dialog should close it
     * @return {Object} JS config object w/ references to event handlers, as well as the elements to which we need to apply focus
     */
    getHandlerConfig : function(dialog, isModal, clickOutToClose) {

        var self          = this,
            oldFocus      = document.activeElement,
            newFocus      = this.getFirstFocusableElement(dialog),
            keydown       = function(event) { self.getKeydownHandler(dialog, isModal, newFocus, event) },
            click         = function(event) { self.getClickHandler(dialog, clickOutToClose, event) },
            resize        = function() { self.getResizeHandler(dialog, isModal) };

        return {
            oldFocus       : oldFocus,
            newFocus       : newFocus,
            keydownHandler : keydown,
            clickHandler   : click,
            resizeHandler  : resize
        };

    },

    /**
     * Constructs the handler for the DOM keydown event. Includes handlers for 1) escape key,
     * and 2) tab key (including shift+tab).
     *
     * @param {Aura.Component} dialog the active ui:dialog component
     * @param {Boolean} isModal specifies if the dialog is modal
     * @param {HTMLElement} firstFocusable the first focusable element inside the dialog
     * @param {UIEvent} event DOM keydown event
     * @return {void}
     */
    getKeydownHandler : function(dialog, isModal, firstFocusable, event) {

        if (!event) { var event = window.event; }

        var closeButton  = dialog.find("closeButton").getElement(),
            shiftPressed = event.shiftKey,
            currentFocus = document.activeElement,
            closeEvent   = $A.get("e.ui:closeDialog");

        closeEvent.setParams({ dialog : dialog, confirmClicked : false });

        switch (event.keyCode) {
            case 27: // escape key - always closes all dialogs
                $A.util.squash(event, true);
                closeEvent.fire();
                break;
            case 9: // tab key - if modal, keep focus inside the dialog
                if (isModal) {
                    if (currentFocus === closeButton && !shiftPressed) {
                        $A.util.squash(event, true);
                        firstFocusable.focus();
                    } else if (currentFocus === firstFocusable && shiftPressed) {
                        $A.util.squash(event, true);
                        closeButton.focus();
                    }
                // if not modal, close the dialog when you tab out of it
                } else {
                    if ((currentFocus === closeButton && !shiftPressed) ||
                        (currentFocus === firstFocusable && shiftPressed)) {
                        $A.util.squash(event, true);
                        closeEvent.fire();
                    }
                }
                break;
        }

    },

    /**
     * Constructs the handler for the DOM click event.
     *
     * @param {Aura.Component} dialog the ui:dialog component
     * @param {Boolean} clickOutToClose whether the dialog should be closed on click outside the dialog
     * @param {UIEvent} event the DOM click event
     * @return {void}
     */
    getClickHandler : function(dialog, clickOutToClose, event) {

        if (!event) { var event = window.event; }

        var target        = event.target || event.srcElement,
            container     = dialog.find("outer").getElement(),
            clickedInside = $A.util.contains(container, target),
            closeEvent;

        if (clickedInside) {
            return;
        } else {
            if (clickOutToClose) {
                closeEvent = $A.get("e.ui:closeDialog");
                closeEvent.setParams({
                    dialog : dialog,
                    confirmClicked : false
                });
                closeEvent.fire();
            }
        }

    },

    /**
     * Constructs the handler for the window.resize DOM event.
     *
     * @param {Aura.Component} dialog the ui:dialog component
     * @param {Boolean} isModal whether the dialog is modal or not
     * @return {void}
     */
    getResizeHandler : function(dialog, isModal) {

        if (isModal) {
            dialog.find("content").getElement().style.maxHeight = this.getContentMaxHeight(dialog) + "px";
        }

    },

    /**
     * Retrieves the first focusable element inside the dialog component. Should
     * ALWAYS return a non-null value, as the "x" (i.e. dialog close) link should always
     * be visible and positioned as the very last element in the dialog window.
     * (Having a known element as the last item in the dialog makes keyboard management
     * much easier.)
     *
     * NOTE: This method uses querySelectorAll(), which IE7 doesn't like, so IE7 will
     * always focus on the "x" link, instead of the first element.
     *
     * @param {Aura.Component} cmp the ui:dialog component
     * @return {HTMLElement} the first focusable element inside the dialog, or the "x" link for IE7
     */
    getFirstFocusableElement : function(dialog) {

        var container    = dialog.find("outer").getElement(),
            close        = dialog.find("closeButton").getElement(),
            formElements = [],
            length       = 0,
            element      = null;

        if (!container) {
            $A.assert(false, "Trying to find a focusable element in the dialog, but no container specified.");
        } else if (document.querySelectorAll) {
            // sorry IE7, you're outta luck
            formElements = container.querySelectorAll("input,button,a,textarea,select");
            length = formElements.length;
            if (length > 0) {
                for (var i=0; i<length; i++) {
                    if (!formElements[i].disabled && formElements[i].type.toLowerCase() !== "hidden") {
                        element = formElements[i];
                        break;
                    }
                }
            } else {
                // we should never get here - at a minimum, the "close" link should always be present
                $A.assert(false, "No focusable element found.");
            }
        } else {
            element = close;
        }

        return element;

    },

    /**
     * Handles the application and removal of CSS classes that control the visibility of
     * all dialog types, as well as the animation behaviour of modal dialogs. This method
     * also handles focusing on the proper element when a dialog is opened or closed.
     *
     * @param {Boolean} show specifies if the dialog should be shown (true) or hidden (false)
     * @param {Aura.Component} maskCmp the ui:dialog's "mask" component
     * @param {Aura.Component} dialog the ui:dialog component
     * @param {Boolean} autoFocus specifies if focus should automatically be applied to the first element in the dialog
     * @param {Boolean} isModal specifies if this dialog is modal
     * @param {Object} config JS object that contains references to the elements to focus
     * @return {void}
     */
    toggleDisplay : function(show, dialog, autoFocus, isModal, config) {

        var mask         = isModal ? dialog.find("mask").getElement() : null,
            outer        = dialog.find("outer").getElement(), // outer dialog wrapper
            inner        = dialog.find("content").getElement(), // inner content wrapper (i.e., the part that is scrollable)
            flickerDelay = 50,
            focusDelay   = 300,
            hideDelay    = 400;

        // if the dialog should be opened, remove the 'hidden' classes and apply the animation classes
        if (show) {
            $A.util.removeClass(outer, "hidden");
            if (isModal) {
                inner.style.maxHeight = this.getContentMaxHeight(dialog) + "px";
                $A.util.removeClass(mask, "hidden");
                // delay the application of animation classes by just a hair ... webkit/ffx rendering bug
                window.setTimeout(function() { $A.util.addClass(mask, "fadeIn"); }, flickerDelay);
                window.setTimeout(function() { $A.util.addClass(outer, "slideUp"); }, flickerDelay);
            }
            // apply proper element focus if necessary
            if ((autoFocus || isModal) && config.newFocus) {
                if (isModal) {
                    // delay focus until the modal slides into place, otherwise the scroll jumps
                    window.setTimeout(function() { config.newFocus.focus(); }, flickerDelay + focusDelay);
                } else {
                    config.newFocus.focus();
                }
            }
        // if the dialog should be closed, add the 'hidden' classes and remove the animation classes
        } else {
            if (isModal) {
                // remove the animation classes immediately, but delay adding 'hidden' back until animation completes
                $A.util.removeClass(mask, "fadeIn");
                $A.util.removeClass(outer, "slideUp");
                window.setTimeout(function() { $A.util.addClass(mask, "hidden"); }, hideDelay);
                window.setTimeout(function() { $A.util.addClass(outer, "hidden"); }, hideDelay);
            } else {
                // if not a modal, then just hide the dialog immediately
                $A.util.addClass(outer, "hidden");
            }

            // apply proper element focus if necessary
            if (config.oldFocus) {
            	try {
            		config.oldFocus.focus();
            	} catch (e) {
            		// Ignore these - invalid element or in IE7/8 trying to set focus to an invisible element
            	}
            }
        }

    },

    /**
     * Calculates the max-height of the content <div> in a modal window so
     * it doesn't extend outside the viewport.
     *
     * @return {void}
     */
    getContentMaxHeight : function(dialog) {

        var hasButtons     = dialog.get("v.buttons.length") > 0,
            cssMargin      = 10, // applied to top-level "title", "content", and "button" elements
            cssPadding     = 10, // applied to "content" <div> only
            titleHeight    = dialog.find("title").getElement().offsetHeight + cssMargin * 2, // account for top & bottom
            contentPadding = cssPadding * 2, // account for top & bottom
            buttonHeight   = hasButtons ? dialog.find("buttons").getElement().offsetHeight + cssMargin * 2 : cssMargin,
            extraMargin    = 20; // extra margin at the bottom of the viewport so you can fully see the css box-shadow effect

        // 2600px is the same value as the CSS 3D transform rule applied to the dialog
        return Math.min($A.util.getWindowSize().height, 2600) - (titleHeight + contentPadding + buttonHeight + extraMargin);

    },

    /**
     * Gets a root ui:dialog instance, even if the component to check is
     * an extension of ui:dialog.
     *
     * @param {Aura.Component} cmp The component to check
     * @return {Aura.Component} cmp The root ui:dialog component
     */
    getDialogRoot : function(cmp) {
        var type = cmp.getDef().getDescriptor().getQualifiedName();
        while (type !== "markup://ui:dialog") {
            cmp = cmp.getSuper();
            type = cmp.getDef().getDescriptor().getQualifiedName();
        }
        return cmp;
    }

})
<commit_msg>Merging origin/ryan/master-pending/dialog-handles-duplicate-events into master<commit_after>/*
 * Copyright (C) 2013 salesforce.com, inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
({

    /**
     * Activates a single ui:dialog component by:
     *     1. setting a reference to it on the dialogManager's model.
     *     2. applying event handlers for proper interaction.
     *     3. setting a reference to those event handlers on the dialog's model (for removal later).
     *     4. applying CSS classes to display the dialog.
     *
     * @param {Aura.Component} dialog the ui:dialog component to activate
     * @param {Aura.Component} manager the ui:dialogManager component
     * @return {void}
     */
    activateDialog : function(dialog, manager) {
        var oldHandlerConfig   = dialog.get("v._handlerConfig");
        if ($A.util.isUndefinedOrNull(oldHandlerConfig)) {
            var isModal         = dialog.get("v.isModal"),
                clickOutToClose = dialog.get("v.clickOutToClose"),
                autoFocus       = dialog.get("v.autoFocus"),
                handlerConfig   = this.getHandlerConfig(dialog, isModal, clickOutToClose);
            this.applyHandlers(handlerConfig);
            this.toggleDisplay(true, dialog, autoFocus, isModal, handlerConfig);
            manager.set("v._activeDialog", dialog);
            dialog.set("v._handlerConfig", handlerConfig);
        }
    },

    /**
     * Deactivates a single ui:dialog component by:
     *     1. removing the reference to it from the dialogManager's model.
     *     2. removing event handlers.
     *     3. removing the reference to those event handlers from the dialog's model.
     *     4. removing CSS classes to hide the dialog.
     *
     * @param {Aura.Component} dialog the ui:dialog component to deactivate
     * @param {Aura.Component} manager the ui:dialogManager component
     * @return {void}
     */
    deactivateDialog : function(dialog, manager) {
        var isModal       = dialog.get("v.isModal"),
            autoFocus     = dialog.get("v.autoFocus"),
            handlerConfig = dialog.get("v._handlerConfig");
        if (!$A.util.isUndefinedOrNull(handlerConfig)) {
            this.removeHandlers(handlerConfig);
            this.toggleDisplay(false, dialog, autoFocus, isModal, handlerConfig);
            manager.set("v._activeDialog", null);
            dialog.set("v._handlerConfig", null);
        }
    },

    /**
     * Applies the appropriate event handlers for proper interaction.
     *
     * @param {Object} config JS object that contains all the necessary event handlers
     * @return {void}
     */
    applyHandlers : function(config) {

        $A.util.on(document, "keydown", config.keydownHandler, false);
        $A.util.on(document, "click", config.clickHandler, false);
        $A.util.on(window, "resize", config.resizeHandler, false);

    },

    /**
     * Removes the appropriate event handlers to keep the DOM tidy.
     *
     * @param {Object} config JS object that contains all the necessary event handlers
     * @return {void}
     */
    removeHandlers : function(config) {

        $A.util.removeOn(document, "keydown", config.keydownHandler, false);
        $A.util.removeOn(document, "click", config.clickHandler, false);
        $A.util.removeOn(window, "resize", config.resizeHandler, false);

    },

    /**
     * Builds the appropriate DOM event handlers necessary to interact with the
     * active ui:dialog component, as well as references to the document's previously
     * focused element (before the dialog opens), and the first focusable element
     * inside the dialog. This config object is stored on the dialog component's
     * model so we can remove the event handlers when the dialog is finally closed.
     *
     * @param {Aura.Component} dialog the active ui:dialog component
     * @param {Boolean} isModal specifies if the active dialog is modal
     * @param {Boolean} clickOutToClose specifies if clicking outside the dialog should close it
     * @return {Object} JS config object w/ references to event handlers, as well as the elements to which we need to apply focus
     */
    getHandlerConfig : function(dialog, isModal, clickOutToClose) {

        var self          = this,
            oldFocus      = document.activeElement,
            newFocus      = this.getFirstFocusableElement(dialog),
            keydown       = function(event) { self.getKeydownHandler(dialog, isModal, newFocus, event) },
            click         = function(event) { self.getClickHandler(dialog, clickOutToClose, event) },
            resize        = function() { self.getResizeHandler(dialog, isModal) };

        return {
            oldFocus       : oldFocus,
            newFocus       : newFocus,
            keydownHandler : keydown,
            clickHandler   : click,
            resizeHandler  : resize
        };

    },

    /**
     * Constructs the handler for the DOM keydown event. Includes handlers for 1) escape key,
     * and 2) tab key (including shift+tab).
     *
     * @param {Aura.Component} dialog the active ui:dialog component
     * @param {Boolean} isModal specifies if the dialog is modal
     * @param {HTMLElement} firstFocusable the first focusable element inside the dialog
     * @param {UIEvent} event DOM keydown event
     * @return {void}
     */
    getKeydownHandler : function(dialog, isModal, firstFocusable, event) {

        if (!event) { var event = window.event; }

        var closeButton  = dialog.find("closeButton").getElement(),
            shiftPressed = event.shiftKey,
            currentFocus = document.activeElement,
            closeEvent   = $A.get("e.ui:closeDialog");

        closeEvent.setParams({ dialog : dialog, confirmClicked : false });

        switch (event.keyCode) {
            case 27: // escape key - always closes all dialogs
                $A.util.squash(event, true);
                closeEvent.fire();
                break;
            case 9: // tab key - if modal, keep focus inside the dialog
                if (isModal) {
                    if (currentFocus === closeButton && !shiftPressed) {
                        $A.util.squash(event, true);
                        firstFocusable.focus();
                    } else if (currentFocus === firstFocusable && shiftPressed) {
                        $A.util.squash(event, true);
                        closeButton.focus();
                    }
                // if not modal, close the dialog when you tab out of it
                } else {
                    if ((currentFocus === closeButton && !shiftPressed) ||
                        (currentFocus === firstFocusable && shiftPressed)) {
                        $A.util.squash(event, true);
                        closeEvent.fire();
                    }
                }
                break;
        }

    },

    /**
     * Constructs the handler for the DOM click event.
     *
     * @param {Aura.Component} dialog the ui:dialog component
     * @param {Boolean} clickOutToClose whether the dialog should be closed on click outside the dialog
     * @param {UIEvent} event the DOM click event
     * @return {void}
     */
    getClickHandler : function(dialog, clickOutToClose, event) {

        if (!event) { var event = window.event; }

        var target        = event.target || event.srcElement,
            container     = dialog.find("outer").getElement(),
            clickedInside = $A.util.contains(container, target),
            closeEvent;

        if (clickedInside) {
            return;
        } else {
            if (clickOutToClose) {
                closeEvent = $A.get("e.ui:closeDialog");
                closeEvent.setParams({
                    dialog : dialog,
                    confirmClicked : false
                });
                closeEvent.fire();
            }
        }

    },

    /**
     * Constructs the handler for the window.resize DOM event.
     *
     * @param {Aura.Component} dialog the ui:dialog component
     * @param {Boolean} isModal whether the dialog is modal or not
     * @return {void}
     */
    getResizeHandler : function(dialog, isModal) {

        if (isModal) {
            dialog.find("content").getElement().style.maxHeight = this.getContentMaxHeight(dialog) + "px";
        }

    },

    /**
     * Retrieves the first focusable element inside the dialog component. Should
     * ALWAYS return a non-null value, as the "x" (i.e. dialog close) link should always
     * be visible and positioned as the very last element in the dialog window.
     * (Having a known element as the last item in the dialog makes keyboard management
     * much easier.)
     *
     * NOTE: This method uses querySelectorAll(), which IE7 doesn't like, so IE7 will
     * always focus on the "x" link, instead of the first element.
     *
     * @param {Aura.Component} cmp the ui:dialog component
     * @return {HTMLElement} the first focusable element inside the dialog, or the "x" link for IE7
     */
    getFirstFocusableElement : function(dialog) {

        var container    = dialog.find("outer").getElement(),
            close        = dialog.find("closeButton").getElement(),
            formElements = [],
            length       = 0,
            element      = null;

        if (!container) {
            $A.assert(false, "Trying to find a focusable element in the dialog, but no container specified.");
        } else if (document.querySelectorAll) {
            // sorry IE7, you're outta luck
            formElements = container.querySelectorAll("input,button,a,textarea,select");
            length = formElements.length;
            if (length > 0) {
                for (var i=0; i<length; i++) {
                    if (!formElements[i].disabled && formElements[i].type.toLowerCase() !== "hidden") {
                        element = formElements[i];
                        break;
                    }
                }
            } else {
                // we should never get here - at a minimum, the "close" link should always be present
                $A.assert(false, "No focusable element found.");
            }
        } else {
            element = close;
        }

        return element;

    },

    /**
     * Handles the application and removal of CSS classes that control the visibility of
     * all dialog types, as well as the animation behaviour of modal dialogs. This method
     * also handles focusing on the proper element when a dialog is opened or closed.
     *
     * @param {Boolean} show specifies if the dialog should be shown (true) or hidden (false)
     * @param {Aura.Component} maskCmp the ui:dialog's "mask" component
     * @param {Aura.Component} dialog the ui:dialog component
     * @param {Boolean} autoFocus specifies if focus should automatically be applied to the first element in the dialog
     * @param {Boolean} isModal specifies if this dialog is modal
     * @param {Object} config JS object that contains references to the elements to focus
     * @return {void}
     */
    toggleDisplay : function(show, dialog, autoFocus, isModal, config) {

        var mask         = isModal ? dialog.find("mask").getElement() : null,
            outer        = dialog.find("outer").getElement(), // outer dialog wrapper
            inner        = dialog.find("content").getElement(), // inner content wrapper (i.e., the part that is scrollable)
            flickerDelay = 50,
            focusDelay   = 300,
            hideDelay    = 400;

        // if the dialog should be opened, remove the 'hidden' classes and apply the animation classes
        if (show) {
            $A.util.removeClass(outer, "hidden");
            if (isModal) {
                inner.style.maxHeight = this.getContentMaxHeight(dialog) + "px";
                $A.util.removeClass(mask, "hidden");
                // delay the application of animation classes by just a hair ... webkit/ffx rendering bug
                window.setTimeout(function() { $A.util.addClass(mask, "fadeIn"); }, flickerDelay);
                window.setTimeout(function() { $A.util.addClass(outer, "slideUp"); }, flickerDelay);
            }
            // apply proper element focus if necessary
            if ((autoFocus || isModal) && config.newFocus) {
                if (isModal) {
                    // delay focus until the modal slides into place, otherwise the scroll jumps
                    window.setTimeout(function() { config.newFocus.focus(); }, flickerDelay + focusDelay);
                } else {
                    config.newFocus.focus();
                }
            }
        // if the dialog should be closed, add the 'hidden' classes and remove the animation classes
        } else {
            if (isModal) {
                // remove the animation classes immediately, but delay adding 'hidden' back until animation completes
                $A.util.removeClass(mask, "fadeIn");
                $A.util.removeClass(outer, "slideUp");
                window.setTimeout(function() { $A.util.addClass(mask, "hidden"); }, hideDelay);
                window.setTimeout(function() { $A.util.addClass(outer, "hidden"); }, hideDelay);
            } else {
                // if not a modal, then just hide the dialog immediately
                $A.util.addClass(outer, "hidden");
            }

            // apply proper element focus if necessary
            if (config.oldFocus) {
            	try {
            		config.oldFocus.focus();
            	} catch (e) {
            		// Ignore these - invalid element or in IE7/8 trying to set focus to an invisible element
            	}
            }
        }

    },

    /**
     * Calculates the max-height of the content <div> in a modal window so
     * it doesn't extend outside the viewport.
     *
     * @return {void}
     */
    getContentMaxHeight : function(dialog) {

        var hasButtons     = dialog.get("v.buttons.length") > 0,
            cssMargin      = 10, // applied to top-level "title", "content", and "button" elements
            cssPadding     = 10, // applied to "content" <div> only
            titleHeight    = dialog.find("title").getElement().offsetHeight + cssMargin * 2, // account for top & bottom
            contentPadding = cssPadding * 2, // account for top & bottom
            buttonHeight   = hasButtons ? dialog.find("buttons").getElement().offsetHeight + cssMargin * 2 : cssMargin,
            extraMargin    = 20; // extra margin at the bottom of the viewport so you can fully see the css box-shadow effect

        // 2600px is the same value as the CSS 3D transform rule applied to the dialog
        return Math.min($A.util.getWindowSize().height, 2600) - (titleHeight + contentPadding + buttonHeight + extraMargin);

    },

    /**
     * Gets a root ui:dialog instance, even if the component to check is
     * an extension of ui:dialog.
     *
     * @param {Aura.Component} cmp The component to check
     * @return {Aura.Component} cmp The root ui:dialog component
     */
    getDialogRoot : function(cmp) {
        var type = cmp.getDef().getDescriptor().getQualifiedName();
        while (type !== "markup://ui:dialog") {
            cmp = cmp.getSuper();
            type = cmp.getDef().getDescriptor().getQualifiedName();
        }
        return cmp;
    }

})
<|endoftext|>


<commit_before>import Index from 'aircheck/routes/index';
import App from 'aircheck/app';

module("Acceptances - Index", {
  setup: function(){
    App.reset();
  }
});

test("index renders", 1, function(){
  visit('/').then(function(){
    ok(exists("h1:contains('Welcome to Aircheck')"));
  });
});

test("goToRoom action navigates to the room route", 2, function(){
  visit('/')
  .fillIn(".room-name", "Room Name")
  .click(".join").then(function() {
      var activeTransition = App.__container__.lookup('router:main').router.activeTransition;
      equal(activeTransition.targetName, 'room');
      equal(activeTransition.params.room_name, 'Room Name');
  });
});
<commit_msg>add a comment to revisit the integration test<commit_after>import Index from 'aircheck/routes/index';
import App from 'aircheck/app';

module("Acceptances - Index", {
  setup: function(){
    App.reset();
  }
});

test("index renders", 1, function(){
  visit('/').then(function(){
    ok(exists("h1:contains('Welcome to Aircheck')"));
  });
});

test("goToRoom action navigates to the room route", 2, function(){
  visit('/')
  .fillIn(".room-name", "Room Name")
  .click(".join").then(function() {
      // I dont like the coupling in this test...
      var activeTransition = App.__container__.lookup('router:main').router.activeTransition;
      equal(activeTransition.targetName, 'room');
      equal(activeTransition.params.room_name, 'Room Name');
  });
});
<|endoftext|>


<commit_before>var isTestling = typeof __testlingConsole != 'undefined';
var remoteSignaller = '//switchboard.rtc.io';
// var remoteSignaller = 'http://localhost:3000';

module.exports = {
  signallingServer: isTestling ? location.origin : remoteSignaller
};
<commit_msg>Connect to the testing signaling server<commit_after>var isTestling = typeof __testlingConsole != 'undefined';
var remoteSignaller = '//switchboard.rtc.io';
// var remoteSignaller = 'http://localhost:3000';

module.exports = {
  signallingServer: require('./signaling-server') // isTestling ? location.origin : remoteSignaller
};
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2012 Andreas Madsen
 * MIT License
 */

(function () {
  "use strict";

  var helpers = require('../helpers.js');
  var core = helpers.core('core');
  var Process = helpers.core('process');
  var streams = helpers.core('streams');

  // get and parse the options
  var options = JSON.parse(process.env.spawnOptions);

  // get the monitor module given by options.monitor
  var Monitor = require(options.monitor).Monitor;

  // create a new process object
  // the child will first be spawned when child.spawn is executed
  var child = new Process(options);

  // when useing development print output
  if (options.strategy === 'development') {
    child.pump(process);
  }

  // first create monitor object, the callback is executed when
  // the monitor constructor call this.ready
  var setupMonitor = function (emits, message) {
    var monitorOptions = options.options;
    var monitor = new Monitor(message, monitorOptions, function () {

      // emit events before starting child
      emits.forEach(function (event) {
        monitor.emit(event.name, event.state);
      });

      // spawn child when monitor is ready
      child.spawn();

      // start pumping streams to monitor
      child.pump(monitor);
    });

    // relay stop and restart to process event on monitor
    child.on('stop', function (suicide) {
      if (suicide) return;
      monitor.emit('process', 'stop');
    });
    child.on('restart', monitor.emit.bind(monitor, 'process', 'restart'));
  };

  // handle communication between daemon and pump
  if (options.strategy === 'daemon') {

    // setup IPC channel with daemon
    var ipc = new streams.LineStream(process.stdout, process.stdin);
    ipc.on('message', function (msg) {

      // watch daemon by its PID
      // callback will be executed when dead
      helpers.processWatcher(msg.daemon, function () {

        // kill child process
        child.kill();

        // when the child is dead start a new daemon
        child.on('stop', function () {
          // the restart argument will let the the daemon.js know that it was
          // restarted
          var daemon = core.spawnDaemon(options, false, ['restart']);
          daemon.spawn();

          // the streams will be closed followed by killing the this process
          // this mimics the execute.js procedure but without the extra overhead
          // of createing a new process.
          daemon.close(process.exit.bind(process, 0));
        });
      });

      // start monitor with a errroMessage if exist
      setupMonitor(msg.emit, msg.message);
    });

    // when child start or restart send new PID information to daemon
    var informDaemon = function () {
      ipc.send({cmd: 'pid', pid: child.pid});
    };
    child.on('start', informDaemon);
    child.on('restart', informDaemon);

  } else {
    setupMonitor([
      {name: 'monitor', state: 'start'},
      {name: 'process', state: 'start'}
    ], null);
  }

  // handle child death
  child.on('stop', function (suicide) {
    if (suicide) return;

    // restart child
    child.spawn(true);
  });

})();
<commit_msg>[fix] interface pump with the new monitor pid API<commit_after>/**
 * Copyright (c) 2012 Andreas Madsen
 * MIT License
 */

(function () {
  "use strict";

  var helpers = require('../helpers.js');
  var core = helpers.core('core');
  var Process = helpers.core('process');
  var streams = helpers.core('streams');

  // get and parse the options
  var options = JSON.parse(process.env.spawnOptions);

  // get the monitor module given by options.monitor
  var Monitor = require(options.monitor).Monitor;

  // create a new process object
  // the child will first be spawned when child.spawn is executed
  var child = new Process(options);

  // when useing development print output
  if (options.strategy === 'development') {
    child.pump(process);
  }

  // first create monitor object, the callback is executed when
  // the monitor constructor call this.ready
  var setupMonitor = function (emits, message, daemonPid) {
    var monitorOptions = options.options;
    var monitor = new Monitor(message, monitorOptions, daemonPid, function () {

      // emit events before starting child
      emits.forEach(function (event) {
        monitor.emit(event.name, event.state);
      });

      // spawn child when monitor is ready
      child.spawn();

      // assign pid infomation
      monitor.pid.process = child.pid;

      // start pumping streams to monitor
      child.pump(monitor);
    });

    // relay stop and restart to process event on monitor
    child.on('stop', function (suicide) {
      if (suicide) return;
      monitor.pid.child = null;
      monitor.emit('process', 'stop');
    });
    child.on('restart', function () {
      monitor.pid.child = child.pid;
      monitor.emit('process', 'restart');
    });
  };

  // handle communication between daemon and pump
  if (options.strategy === 'daemon') {

    // setup IPC channel with daemon
    var ipc = new streams.LineStream(process.stdout, process.stdin);
    ipc.on('message', function (msg) {

      // watch daemon by its PID
      // callback will be executed when dead
      helpers.processWatcher(msg.daemon, function () {

        // kill child process
        child.kill();

        // when the child is dead start a new daemon
        child.on('stop', function () {
          // the restart argument will let the the daemon.js know that it was
          // restarted
          var daemon = core.spawnDaemon(options, false, ['restart']);
          daemon.spawn();

          // the streams will be closed followed by killing the this process
          // this mimics the execute.js procedure but without the extra overhead
          // of createing a new process.
          daemon.close(process.exit.bind(process, 0));
        });
      });

      // start monitor with a errroMessage if exist
      setupMonitor(msg.emit, msg.message, msg.daemon);
    });

    // when child start or restart send new PID information to daemon
    var informDaemon = function () {
      ipc.send({cmd: 'pid', pid: child.pid});
    };
    child.on('start', informDaemon);
    child.on('restart', informDaemon);

  } else {
    setupMonitor([
      {name: 'monitor', state: 'start'},
      {name: 'process', state: 'start'}
    ], null, null);
  }

  // handle child death
  child.on('stop', function (suicide) {
    if (suicide) return;

    // restart child
    child.spawn(true);
  });

})();
<|endoftext|>


<commit_before>/*
  Copyright (C) 2016  Adrien THIERRY
  http://seraum.com
  License MIT
*/

/**
 * renderView - The unique method of easyplate :)
 * @method
 * @param  {object} viewObj The view object
 * @param  {element} parent The parent DOM element, could be empty, so return a string
 * @param  {object} dataObj The data that could be mapped to the view : each key is mapped to innerHTML element in viewObj, if dataObj[key] is an object, this object is used in recursivity
 * @param {object} option An object with rendering options
 * @return {string} Return a string of generated html
 */
function renderView(viewObj, parent, dataObj, option)
{
  var once = false;
  // for speed, no need to check if empty or not
  if(!dataObj) dataObj = {};

  // if parent is null or undefined, we stock outerHTML of rendered element in
	var viewString = "";

  for(var v in viewObj)
  {

    // if data is an array
    if(dataObj[v] && dataObj[v].constructor === Array) viewObj[v].number = dataObj[v].length;

    // number of iterations of this object
    if(viewObj[v].number === undefined) viewObj[v].number = 1;

	  // if no tag, set it to div by default
	  if(!viewObj[v].tag) viewObj[v].tag = "div";

    for(var i = 0; i < viewObj[v].number; i++)
    {
      // create obj with tag value
      var element = document.createElement(viewObj[v].tag);

      // loop on each element
      for(var a in viewObj[v].attribute)
      {
        // set each attribute for element
        element.setAttribute(a, viewObj[v].attribute[a]);
      }

      // if innerHTML, set it
      if(viewObj[v].innerHTML)
      {
        element.innerHTML = viewObj[v].innerHTML;
      }

      // if data is an aray, override innerHTML with data i
      if(dataObj[v] && dataObj[v].constructor === Array)
      {
        if(typeof dataObj[v][i] == "object")
        {
          once = true;
          renderView(viewObj[v].child, element, dataObj[v][i]);
        }
        else
        {
          if( viewObj[v].tag == "input") element.setAttribute('value', dataObj[v][i]);
          else element.innerHTML = dataObj[v][i];
        }
      }

      // else set data if string, number etc
      else if(dataObj[v] && typeof dataObj[v] != "object")
      {
        if( viewObj[v].tag == "input") element.setAttribute('value', dataObj[v]);
        else element.innerHTML = dataObj[v];
      }

      // if child, we create childs
      if(!once && viewObj[v].child)
      {
        // create each child element with parent = current scope element recursively
        if(dataObj[v] && typeof dataObj[v] == "object") renderView(viewObj[v].child, element, dataObj[v]);
        else renderView(viewObj[v].child, element, dataObj);
      }

      // here append child to parent, you can use insertBefore either etc
      if(parent) parent.appendChild(element);
      else viewString += element.outerHTML;
    }

   }

   // return viewString in all case
   return viewString;
}
<commit_msg>Updated<commit_after>/*
  Copyright (C) 2016  Adrien THIERRY
  http://seraum.com
  License MIT
*/

/**
 * renderView - The unique method of easyplate :)
 * @method
 * @param  {object} viewObj The view object
 * @param  {element} parent The parent DOM element, could be empty, so return a string
 * @param  {object} dataObj The data that could be mapped to the view : each key is mapped to innerHTML element in viewObj, if dataObj[key] is an object, this object is used in recursivity
 * @param {object} option An object with rendering options
 * @return {string} Return a string of generated html
 */
function renderView(viewObj, parent, dataObj, option)
{
  var once = false;
  // for speed, no need to check if empty or not
  if(!dataObj) dataObj = {};

  // if parent is null or undefined, we stock outerHTML of rendered element in
	var viewString = "";

  if(dataObj.constructor === Array)
  {
    // if we have an array, we loop on it
    for(var i = 0; i < dataObj.length; i++)
    {
      viewString += renderView(viewObj, parent, dataObj[i]);
    }
  }
  else if(typeof dataObj == "object")
  {
    // if we have an object, we construct our view
    for(var v in viewObj)
    {
      // if data is an array
      if(dataObj[v] && dataObj[v].constructor === Array) viewObj[v].number = dataObj[v].length;

      // number of iterations of this object
      if(viewObj[v].number === undefined) viewObj[v].number = 1;

  	  // if no tag, set it to div by default
  	  if(!viewObj[v].tag) viewObj[v].tag = "div";

      for(var i = 0; i < viewObj[v].number; i++)
      {
        // create obj with tag value
        var element = document.createElement(viewObj[v].tag);

        // loop on each element
        for(var a in viewObj[v].attribute)
        {
          // set each attribute for element
          element.setAttribute(a, viewObj[v].attribute[a]);
        }

        // if innerHTML, set it
        if(viewObj[v].innerHTML)
        {
          element.innerHTML = viewObj[v].innerHTML;
        }

        // if data is an aray, override innerHTML with data i
        if(dataObj[v] && dataObj[v].constructor === Array)
        {
          if(typeof dataObj[v][i] == "object")
          {
            //once = true;
            renderView(viewObj[v].child, element, dataObj[v][i]);
          }
          else
          {
            if( viewObj[v].tag == "input") element.setAttribute('value', dataObj[v][i]);
            else element.innerHTML = dataObj[v][i];
          }
        }

        // else set data if string, number etc
        else if(dataObj[v] && typeof dataObj[v] != "object")
        {
          if( viewObj[v].tag == "input") element.setAttribute('value', dataObj[v]);
          else element.innerHTML = dataObj[v];
        }

        // if child, we create childs
        if(!once && viewObj[v].child)
        {
          // create each child element with parent = current scope element recursively
          if(dataObj[v] && typeof dataObj[v] == "object") renderView(viewObj[v].child, element, dataObj[v]);
          else renderView(viewObj[v].child, element, dataObj);
        }

        // here append child to parent, you can use insertBefore either etc
        if(parent) parent.appendChild(element);
        else viewString += element.outerHTML;
      }

     }
   }
   else
   {
     // dataObj is bad, so we replace it by {}
     viewString += renderView(viewObj, parent, {});
   }

   // return viewString in all case
   return viewString;
}
<|endoftext|>


<commit_before>import Todo from 'appkit/models/todo';
var App;

module('Acceptances - Todos', {
  setup: function(){
    // setup fixtures
    Todo.reopenClass({
      FIXTURES: [
        {
          id: "1",
          title: 'install EAK',
          isCompleted: true
        },
        {
          id: "2",
          title: 'install additional dependencies',
          isCompleted: true
        },
        {
          id: "3",
          title: 'develop amazing things',
          isCompleted: false
        }
    ]});

    App = startApp();
  },
  teardown: function() {
    Ember.run(App, 'destroy');
  }
});

function remainingCountText(){
  return Number($('#todo-count strong').text());
}

var notCompletedSelector = "#todo-list li:not('.completed') input";
var completedSelector = "#todo-list li.completed input";

function notCompleted() {
  return $(notCompletedSelector);
}

function completed() {
  return $(completedSelector);
}

function mock(options) {
  return Ember.$.extend(true, {}, options);
}

test('todos renders', function(){
  expect(7);

  visit('/').then(function(){
    ok(exists('#new-todo'));
    ok(exists('#toggle-all'));

    var list = find('#todo-list li');
    equal(list.length, 3);

    ok(exists('#todo-count'));

    var linkList = find('#filters li');
    equal(linkList.length, 3);

    ok(exists('#clear-completed'));
    ok(exists('#info'));
  });
});

test('todos mark last completed', function(){
  expect(6);

  visit('/').then(function(){
    equal(1, notCompleted().length, 'expected 1 uncompleted');
    equal(1, remainingCountText());
    equal(2, completed().length);

    click(notCompletedSelector).then(function(){
      equal(0, notCompleted().length, 'expected 0 uncompleted');
      equal(0, remainingCountText());
      equal(3, completed().length);
    });
  });
});

test('todos mark one uncompleted', function(){
  expect(6);

  visit('/').then(function(){
    equal(1, notCompleted().length, 'expected 1 uncompleted');
    equal(1, remainingCountText());
    equal(2, completed().length);

    click(completedSelector + ':first').then(function(){
      equal(2, notCompleted().length, 'expected 0 uncompleted');
      equal(2, remainingCountText());
      equal(1, completed().length);
    });
  });
});

test('clear completed', function(){
  expect(6);

  visit('/').then(function(){
    equal(1, notCompleted().length, 'expected 1 uncompleted');
    equal(1, remainingCountText());
    equal(2, completed().length);

    click('#clear-completed').then(function(){
      equal(1, notCompleted().length, 'expected 3 uncompleted');
      equal(1, remainingCountText());
      equal(0, completed().length);
    });
  });
});
<commit_msg>test todo creation<commit_after>import Todo from 'appkit/models/todo';
var App;

module('Acceptances - Todos', {
  setup: function(){
    // setup fixtures
    Todo.reopenClass({
      FIXTURES: [
        {
          id: "1",
          title: 'install EAK',
          isCompleted: true
        },
        {
          id: "2",
          title: 'install additional dependencies',
          isCompleted: true
        },
        {
          id: "3",
          title: 'develop amazing things',
          isCompleted: false
        }
    ]});

    App = startApp();
  },
  teardown: function() {
    Ember.run(App, 'destroy');
  }
});

function remainingCountText(){
  return Number($('#todo-count strong').text());
}

var notCompletedSelector = "#todo-list li:not('.completed') input";
var completedSelector = "#todo-list li.completed input";

function notCompleted() {
  return $(notCompletedSelector);
}

function completed() {
  return $(completedSelector);
}

function mock(options) {
  return Ember.$.extend(true, {}, options);
}

function insertNewline(selector) {
  // should be improved, and likely made an official helper
  var id = $(selector).attr('id');
  var view = Ember.View.views[id];
  if (view) {
      view.insertNewline(new Ember.$.Event('kewdown'));
  } else {
    throw new Error('Could not insertNewline on: `' + selector + '`, because it was not found, or was not an ember-view');
  }
}

test('todos renders', function(){
  expect(7);

  visit('/').then(function(){
    ok(exists('#new-todo'));
    ok(exists('#toggle-all'));

    var list = find('#todo-list li');
    equal(list.length, 3);

    ok(exists('#todo-count'));

    var linkList = find('#filters li');
    equal(linkList.length, 3);

    ok(exists('#clear-completed'));
    ok(exists('#info'));
  });
});

test('todos mark last completed', function(){
  expect(6);

  visit('/').then(function(){
    equal(1, notCompleted().length, 'expected 1 uncompleted');
    equal(1, remainingCountText());
    equal(2, completed().length);

    click(notCompletedSelector).then(function(){
      equal(0, notCompleted().length, 'expected 0 uncompleted');
      equal(0, remainingCountText());
      equal(3, completed().length);
    });
  });
});

test('todos mark one uncompleted', function(){
  expect(6);

  visit('/').then(function(){
    equal(1, notCompleted().length, 'expected 1 uncompleted');
    equal(1, remainingCountText());
    equal(2, completed().length);

    click(completedSelector + ':first').then(function(){
      equal(2, notCompleted().length, 'expected 0 uncompleted');
      equal(2, remainingCountText());
      equal(1, completed().length);
    });
  });
});

test('clear completed', function(){
  expect(6);

  visit('/').then(function(){
    equal(1, notCompleted().length, 'expected 1 uncompleted');
    equal(1, remainingCountText());
    equal(2, completed().length);

    click('#clear-completed').then(function(){
      equal(1, notCompleted().length, 'expected 3 uncompleted');
      equal(1, remainingCountText());
      equal(0, completed().length);
    });
  });
});

test("create todo", function(){
  expect(4);
  visit('/').then(function(){
    fillIn('#new-todo', 'bro');
    Ember.run(null, insertNewline, '#new-todo');
    equal(2, notCompleted().length, 'expected 1 uncompleted');
    equal(2, remainingCountText());
    equal(2, completed().length);
    equal('bro', $('ul#todo-list li label:last').text());
  });
});
<|endoftext|>


<commit_before>import React from 'react';
import sinon from 'sinon';
import {mount, shallow} from 'enzyme';
import injectTapEventPlugin from 'react-tap-event-plugin';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import FloatingActionButton from 'material-ui/FloatingActionButton'
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import NavigationCheck from 'material-ui/svg-icons/navigation/check';
import { BreadcrumbStepper } from '../components/BreadcrumbStepper';
import ExportAOI, {MODE_DRAW_BBOX, MODE_NORMAL} from '../components/ExportAOI';
import ExportInfo from '../components/ExportInfo';
import ExportSummary from '../components/ExportSummary';
import isEqual from 'lodash/isEqual';
import {browserHistory} from 'react-router';

describe('BreadcrumbStepper component', () => {
    const muiTheme = getMuiTheme();
    const getProps = () => {
        return {
            aoiInfo: {geojson: {}},
            providers: providers,
            stepperNextEnabled: false,
            exportInfo: {
                exportName: '',
                datapackDescription: '',
                projectName: '',
                makePublic: false,
                providers: providers,
                area_str: '',
                layers: '',
            },
            createExportRequest: () => {},
            submitJob: (data) => {},
            getProviders: () => {},
            setNextDisabled: () => {},
            setNextEnabled: () => {},
            setExportInfoDone: () => {},
            clearAoiInfo: () => {},
            clearExportInfo: () => {}
        }
    };
    const getWrapper = (props) => {
        return shallow(<BreadcrumbStepper {...props}/>, {
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
    }

    it('should render step 1 with disabled next arrow by default', () => {
        const props = getProps();
        const wrapper = getWrapper(props);
        expect(wrapper.find(NavigationArrowBack)).toHaveLength(1);
        expect(wrapper.find(ExportAOI)).toHaveLength(1);
        expect(wrapper.childAt(0).childAt(0).childAt(0).text()).toEqual('STEP 1 OF 3:  Define Area of Interest');
        expect(wrapper.find(FloatingActionButton)).toHaveLength(1);
        expect(wrapper.find(FloatingActionButton).props().disabled).toEqual(true);
        expect(wrapper.find(NavigationArrowForward)).toHaveLength(1);
    });

    it('handleSubmit should submit a job with the correct data', () => {
        const stub = sinon.stub(browserHistory, 'push');
        let props = getProps();
        props.exportInfo.exportName = 'test name';
        props.exportInfo.datapackDescription = 'test description';
        props.exportInfo.projectName = 'test event';
        props.submitJob = new sinon.spy();
        const expectedProps = {
            name: 'test name',
            description: 'test description',
            event: 'test event',
            include_zipfile: false,
            published: false,
            provider_tasks: [{'provider': providers[0], 'formats': ['gpkg']}],
            selection: {},
            tags: [],
        }
        const handleSpy = new sinon.spy(BreadcrumbStepper.prototype, 'handleSubmit');
        const wrapper = getWrapper(props);
        wrapper.instance().handleSubmit();
        expect(handleSpy.calledOnce).toBe(true);
        expect(props.submitJob.calledOnce).toBe(true);
        expect(props.submitJob.calledWith(expectedProps)).toBe(true);
        expect(stub.calledOnce).toBe(true);
    });

    it('handleNext should increment the stepIndex', () => {
        const props = getProps();
        const stateSpy = new sinon.spy(BreadcrumbStepper.prototype, 'setState');
        const wrapper = getWrapper(props);
        expect(stateSpy.called).toBe(false);
        wrapper.instance().handleNext();
        expect(stateSpy.calledOnce).toBe(true);
        stateSpy.restore();
    });

    it('handleNext should call setExportInfoDone', () => {
        let props = getProps();
        props.setExportInfoDone = new sinon.spy();
        const wrapper = getWrapper(props);
        wrapper.setState({stepIndex: 1});
        expect(props.setExportInfoDone.called).toBe(false);
        wrapper.instance().handleNext();
        expect(props.setExportInfoDone.calledOnce).toBe(true);
    });

    it('incrementStepper should increment the stepIndex', () => {
        const props = getProps();
        const stateSpy = new sinon.spy(BreadcrumbStepper.prototype, 'setState');
        const wrapper = getWrapper(props);
        expect(stateSpy.called).toBe(false);
        wrapper.instance().incrementStepper();
        expect(stateSpy.calledOnce).toBe(true);
        expect(stateSpy.calledWith({stepIndex: 1})).toBe(true);
        stateSpy.restore();
    });

    it('handlePrev should decrement the stepIndex only when index is > 0', () => {
        const props = getProps();
        const stateSpy = new sinon.spy(BreadcrumbStepper.prototype, 'setState');
        const wrapper = getWrapper(props);
        expect(stateSpy.called).toBe(false);
        wrapper.instance().handlePrev();
        expect(stateSpy.called).toBe(false);
        wrapper.setState({stepIndex: 1});
        wrapper.instance().handlePrev();
        expect(stateSpy.called).toBe(true);
        expect(stateSpy.calledWith({stepIndex: 0})).toBe(true);
        stateSpy.restore();
    });

    it('getStepLabel should return the correct label for each stepIndex', () => {
        const props = getProps();
        const wrapper = getWrapper(props);

        let elem = mount(wrapper.instance().getStepLabel(0));
        expect(elem.text()).toEqual('STEP 1 OF 3:  Define Area of Interest');

        elem = mount(wrapper.instance().getStepLabel(1));
        expect(elem.text()).toEqual('STEP 2 OF 3:  Select Data & Formats');

        elem = mount(wrapper.instance().getStepLabel(2));
        expect(elem.text()).toEqual('STEP 3 OF 3:  Review & Submit');

        elem = mount(wrapper.instance().getStepLabel(3));
        expect(elem.text()).toEqual('STEPPER ERROR');
    });

    it('getStepContent should return the correct content for each stepIndex', () => {
        const props = getProps();
        const wrapper = getWrapper(props);

        let content = wrapper.instance().getStepContent(0);
        expect(isEqual(content, <ExportAOI/>)).toBe(true);

        content = wrapper.instance().getStepContent(1);
        expect(isEqual(content, <ExportInfo
            providers={props.providers}
            incrementStepper={wrapper.instance().incrementStepper}/>)).toBe(true);

        content = wrapper.instance().getStepContent(2);
        expect(isEqual(content, <ExportSummary/>)).toBe(true);

        content = wrapper.instance().getStepContent(3);
        expect(isEqual(content, <ExportAOI/>)).toBe(true);
    });

    it('getButtonContent should return the correct content for each stepIndex', () => {
        const props = getProps();
        const wrapper = getWrapper(props);

        let content = mount(wrapper.instance().getButtonContent(0),{
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
        expect(content.find(FloatingActionButton)).toHaveLength(1);
        expect(content.find(NavigationArrowForward)).toHaveLength(1);

        content = mount(wrapper.instance().getButtonContent(1),{
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
        expect(content.find(FloatingActionButton)).toHaveLength(1);
        expect(content.find(NavigationArrowForward)).toHaveLength(1);

        content = mount(wrapper.instance().getButtonContent(2),{
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
        expect(content.find(FloatingActionButton)).toHaveLength(1);
        expect(content.find(NavigationCheck)).toHaveLength(1);

        content = mount(wrapper.instance().getButtonContent(3));
        expect(content.find('div')).toHaveLength(1);
    })
});

const providers = [
    {
        "id": 1,
        "model_url": "http://cloud.eventkit.dev/api/providers/1",
        "type": "osm-generic",
        "created_at": "2017-03-24T17:44:22.940611Z",
        "updated_at": "2017-03-24T17:44:22.940629Z",
        "uid": "be401b02-63d3-4080-943a-0093c1b5a914",
        "name": "OpenStreetMap Data (Generic)",
        "slug": "osm-generic",
        "preview_url": "",
        "service_copyright": "",
        "service_description": "",
        "layer": null,
        "level_from": 0,
        "level_to": 10,
        "export_provider_type": 1
    }
]
<commit_msg>corrected a test that was failing in BreadcrumbStepper<commit_after>import React from 'react';
import sinon from 'sinon';
import {mount, shallow} from 'enzyme';
import injectTapEventPlugin from 'react-tap-event-plugin';
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import FloatingActionButton from 'material-ui/FloatingActionButton'
import NavigationArrowBack from 'material-ui/svg-icons/navigation/arrow-back';
import NavigationArrowForward from 'material-ui/svg-icons/navigation/arrow-forward';
import NavigationCheck from 'material-ui/svg-icons/navigation/check';
import { BreadcrumbStepper } from '../components/BreadcrumbStepper';
import ExportAOI, {MODE_DRAW_BBOX, MODE_NORMAL} from '../components/ExportAOI';
import ExportInfo from '../components/ExportInfo';
import ExportSummary from '../components/ExportSummary';
import isEqual from 'lodash/isEqual';
import {browserHistory} from 'react-router';

describe('BreadcrumbStepper component', () => {
    const muiTheme = getMuiTheme();
    const getProps = () => {
        return {
            aoiInfo: {geojson: {}},
            providers: providers,
            stepperNextEnabled: false,
            exportInfo: {
                exportName: '',
                datapackDescription: '',
                projectName: '',
                makePublic: false,
                providers: providers,
                area_str: '',
                layers: '',
            },
            createExportRequest: () => {},
            submitJob: (data) => {},
            getProviders: () => {},
            setNextDisabled: () => {},
            setNextEnabled: () => {},
            setExportInfoDone: () => {},
            clearAoiInfo: () => {},
            clearExportInfo: () => {}
        }
    };
    const getWrapper = (props) => {
        return shallow(<BreadcrumbStepper {...props}/>, {
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
    }

    it('should render step 1 with disabled next arrow by default', () => {
        const props = getProps();
        const wrapper = getWrapper(props);
        expect(wrapper.find(NavigationArrowBack)).toHaveLength(1);
        expect(wrapper.find(ExportAOI)).toHaveLength(1);
        expect(wrapper.childAt(0).childAt(0).childAt(0).text()).toEqual('STEP 1 OF 3:  Define Area of Interest');
        expect(wrapper.find(FloatingActionButton)).toHaveLength(1);
        expect(wrapper.find(FloatingActionButton).props().disabled).toEqual(true);
        expect(wrapper.find(NavigationArrowForward)).toHaveLength(1);
    });

    it('handleSubmit should submit a job with the correct data', () => {
        let props = getProps();
        props.exportInfo.exportName = 'test name';
        props.exportInfo.datapackDescription = 'test description';
        props.exportInfo.projectName = 'test event';
        props.submitJob = new sinon.spy();
        const expectedProps = {
            name: 'test name',
            description: 'test description',
            event: 'test event',
            include_zipfile: false,
            published: false,
            provider_tasks: [{'provider': providers[0], 'formats': ['gpkg']}],
            selection: {},
            tags: [],
        }
        const handleSpy = new sinon.spy(BreadcrumbStepper.prototype, 'handleSubmit');
        const wrapper = getWrapper(props);
        wrapper.instance().handleSubmit();
        expect(handleSpy.calledOnce).toBe(true);
        expect(props.submitJob.calledOnce).toBe(true);
        expect(props.submitJob.calledWith(expectedProps)).toBe(true);
    });

    it('handleNext should increment the stepIndex', () => {
        const props = getProps();
        const stateSpy = new sinon.spy(BreadcrumbStepper.prototype, 'setState');
        const wrapper = getWrapper(props);
        expect(stateSpy.called).toBe(false);
        wrapper.instance().handleNext();
        expect(stateSpy.calledOnce).toBe(true);
        stateSpy.restore();
    });

    it('handleNext should call setExportInfoDone', () => {
        let props = getProps();
        props.setExportInfoDone = new sinon.spy();
        const wrapper = getWrapper(props);
        wrapper.setState({stepIndex: 1});
        expect(props.setExportInfoDone.called).toBe(false);
        wrapper.instance().handleNext();
        expect(props.setExportInfoDone.calledOnce).toBe(true);
    });

    it('incrementStepper should increment the stepIndex', () => {
        const props = getProps();
        const stateSpy = new sinon.spy(BreadcrumbStepper.prototype, 'setState');
        const wrapper = getWrapper(props);
        expect(stateSpy.called).toBe(false);
        wrapper.instance().incrementStepper();
        expect(stateSpy.calledOnce).toBe(true);
        expect(stateSpy.calledWith({stepIndex: 1})).toBe(true);
        stateSpy.restore();
    });

    it('handlePrev should decrement the stepIndex only when index is > 0', () => {
        const props = getProps();
        const stateSpy = new sinon.spy(BreadcrumbStepper.prototype, 'setState');
        const wrapper = getWrapper(props);
        expect(stateSpy.called).toBe(false);
        wrapper.instance().handlePrev();
        expect(stateSpy.called).toBe(false);
        wrapper.setState({stepIndex: 1});
        wrapper.instance().handlePrev();
        expect(stateSpy.called).toBe(true);
        expect(stateSpy.calledWith({stepIndex: 0})).toBe(true);
        stateSpy.restore();
    });

    it('getStepLabel should return the correct label for each stepIndex', () => {
        const props = getProps();
        const wrapper = getWrapper(props);

        let elem = mount(wrapper.instance().getStepLabel(0));
        expect(elem.text()).toEqual('STEP 1 OF 3:  Define Area of Interest');

        elem = mount(wrapper.instance().getStepLabel(1));
        expect(elem.text()).toEqual('STEP 2 OF 3:  Select Data & Formats');

        elem = mount(wrapper.instance().getStepLabel(2));
        expect(elem.text()).toEqual('STEP 3 OF 3:  Review & Submit');

        elem = mount(wrapper.instance().getStepLabel(3));
        expect(elem.text()).toEqual('STEPPER ERROR');
    });

    it('getStepContent should return the correct content for each stepIndex', () => {
        const props = getProps();
        const wrapper = getWrapper(props);

        let content = wrapper.instance().getStepContent(0);
        expect(isEqual(content, <ExportAOI/>)).toBe(true);

        content = wrapper.instance().getStepContent(1);
        expect(isEqual(content, <ExportInfo
            providers={props.providers}
            incrementStepper={wrapper.instance().incrementStepper}/>)).toBe(true);

        content = wrapper.instance().getStepContent(2);
        expect(isEqual(content, <ExportSummary/>)).toBe(true);

        content = wrapper.instance().getStepContent(3);
        expect(isEqual(content, <ExportAOI/>)).toBe(true);
    });

    it('getButtonContent should return the correct content for each stepIndex', () => {
        const props = getProps();
        const wrapper = getWrapper(props);

        let content = mount(wrapper.instance().getButtonContent(0),{
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
        expect(content.find(FloatingActionButton)).toHaveLength(1);
        expect(content.find(NavigationArrowForward)).toHaveLength(1);

        content = mount(wrapper.instance().getButtonContent(1),{
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
        expect(content.find(FloatingActionButton)).toHaveLength(1);
        expect(content.find(NavigationArrowForward)).toHaveLength(1);

        content = mount(wrapper.instance().getButtonContent(2),{
            context: {muiTheme},
            childContextTypes: {
                muiTheme: React.PropTypes.object
            }
        });
        expect(content.find(FloatingActionButton)).toHaveLength(1);
        expect(content.find(NavigationCheck)).toHaveLength(1);

        content = mount(wrapper.instance().getButtonContent(3));
        expect(content.find('div')).toHaveLength(1);
    })
});

const providers = [
    {
        "id": 1,
        "model_url": "http://cloud.eventkit.dev/api/providers/1",
        "type": "osm-generic",
        "created_at": "2017-03-24T17:44:22.940611Z",
        "updated_at": "2017-03-24T17:44:22.940629Z",
        "uid": "be401b02-63d3-4080-943a-0093c1b5a914",
        "name": "OpenStreetMap Data (Generic)",
        "slug": "osm-generic",
        "preview_url": "",
        "service_copyright": "",
        "service_description": "",
        "layer": null,
        "level_from": 0,
        "level_to": 10,
        "export_provider_type": 1
    }
]
<|endoftext|>


<commit_before>// need:
// ViewportCalibratedCanvas
// proxytools
// coordinateView (well, not really, but it'd be silly not to have)
// SideSplit

///////
// utils
function objToParamStr(obj) {
    var parts = [];
    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
          if (Array.isArray(obj[i])){
            // arrays are a list of strings with escaped quotes, surrounded by []
            parts.push(encodeURIComponent(i) + "=" + encodeURIComponent("[" + obj[i].map((x)=>'\"' + x + '\"').toString() + "]"));
          } else {
            parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i]));
          }
        }
    }
    return parts.join("&");
}

function apiCall(callback, url, params){
  fetch(url + "?" + objToParamStr(params), {
    credentials: "same-origin"
  })
  .then((x)=> x.json())
  .then(function(response){
    callback(response);
  })
  .catch((x)=>console.warn(x));
}

class annotStore{
  /**
  * Annotation API Interactions
  * @param {string} id - the image id
  * @param {object} [options] - optional substitution for api urls
  */
  constructor(id, options){
    // image id
    this.id = id;
    // TODO cache??
    this.algDataUrl = options.algUrl || "api/Data/getMultipleAnnots.php";
    this.algListUrl = options.algListUrl || "api/Data/getAlgorithmsForImage.php";
  }

 /**
 * Get a list of avaliable algorithms for the image
 * @param {function} callback - callback for the result data
 */
 getAlgList(callback){
   var params = {"iid" : this.id}
   apiCall(callback, url, params);
 }

 /**
 * Get a list of avaliable algorithms for the image
 * @param {function} callback - callback for the result data
 */
 getAlgData(x1, y1, x2, y2, footprint, algs, callback){
   // sanitization/validation
   x1 = x1 < 0 ? 0 : x1;
   y1 = y1 < 0 ? 0 : y1;
   x2 = x2 < 0 ? 0 : x2;
   y2 = y2 < 0 ? 0 : y2;
   // set params object
   var params = {"iid": this.id, "x": x1, "x1": x2, "y" :y1, "y1": y2, "footprint": footprint, "algoritms": algs};
   apiCall(callback, url, params);
 }
}
// see https://github.com/birm/OSDragonDemos/blob/master/coordinated_draw_annotool.html
function drawFromList(data, context){
  data.forEach(function(annotation){
    let id = annotation.provenance.analysis.execution_id;
    let color = "red";
    let type = annotation.geometry.type;
    let coords = annotation.geometry.coordinates[0];
    if (type == "Polygon"){
      // start
      context.beginPath();
      let first = coords.splice(1,1);
      context.moveTo(first[0], first[1])
      coords.forEach(function(coord){
        let x = coord[0];
        let y = coord[1];
        context.lineTo(x,y);
        console.log(x + ", " + y)
      });
      context.stroke();
      // stop
    } else {
      console.warn("Don't know how to draw '" + type + "'");
    }
  })
}
// TODO something to tell the viewer to redraw on move/zoom/open

class annotations{
  constructor(id, viewer, context, imagingHelper, options){
    this.id = id;
    this.store = new annotStore(id, options);
    this.viewer = viewer;
    this.options = options;
    this.selection = [];
    this.context = context;
    // TODO imagingHelper needed?
  }

  // draw using current viewer state
  draw(){
    this.x1 = this.imagingHelper._viewportOrigin['x'];
    this.y1 = this.imagingHelper._viewportOrigin['y'];
    this.x2 = this.x1 + this.imagingHelper._viewportWidth;
    this.y2 = this.y1 + this.imagingHelper._viewportHeight;

    var max = new OpenSeadragon.Point(this.imagingHelper.physicalToDataX(9), this.imagingHelper.physicalToDataY(9));
    var origin = new OpenSeadragon.Point(this.imagingHelper.physicalToDataX(0), this.imagingHelper.physicalToDataY(0));
    var footprint = (max.x - origin.x) * (max.y - origin.y);

    this.store.getAlgData(x, y, x1, y1, footprint, this.selection, function(data){
      drawFromList(data, this.context);
    });
  }

  select(alg){
    if(this.selection.indexOf(alg) == -1 ){
      this.selection.push(alg);
    }
  }

  deselect(alg){
    let pos = this.selection.indexOf(alg)
    if(pos > -1 ){
      this.selection.splice(pos, 1);
    }
  }

}
<commit_msg>fix url declarations<commit_after>// need:
// ViewportCalibratedCanvas
// proxytools
// coordinateView (well, not really, but it'd be silly not to have)
// SideSplit

///////
// utils
function objToParamStr(obj) {
    var parts = [];
    for (var i in obj) {
        if (obj.hasOwnProperty(i)) {
          if (Array.isArray(obj[i])){
            // arrays are a list of strings with escaped quotes, surrounded by []
            parts.push(encodeURIComponent(i) + "=" + encodeURIComponent("[" + obj[i].map((x)=>'\"' + x + '\"').toString() + "]"));
          } else {
            parts.push(encodeURIComponent(i) + "=" + encodeURIComponent(obj[i]));
          }
        }
    }
    return parts.join("&");
}

function apiCall(callback, url, params){
  fetch(url + "?" + objToParamStr(params), {
    credentials: "same-origin"
  })
  .then((x)=> x.json())
  .then(function(response){
    callback(response);
  })
  .catch((x)=>console.warn(x));
}

class annotStore{
  /**
  * Annotation API Interactions
  * @param {string} id - the image id
  * @param {object} [options] - optional substitution for api urls
  */
  constructor(id, options){
    // image id
    this.id = id;
    // TODO cache??
    this.algDataUrl = options.algUrl || "api/Data/getMultipleAnnots.php";
    this.algListUrl = options.algListUrl || "api/Data/getAlgorithmsForImage.php";
  }

 /**
 * Get a list of avaliable algorithms for the image
 * @param {function} callback - callback for the result data
 */
 getAlgList(callback){
   var params = {"iid" : this.id}
   apiCall(callback, this.algListUrl, params);
 }

 /**
 * Get a list of avaliable algorithms for the image
 * @param {function} callback - callback for the result data
 */
 getAlgData(x1, y1, x2, y2, footprint, algs, callback){
   // sanitization/validation
   x1 = x1 < 0 ? 0 : x1;
   y1 = y1 < 0 ? 0 : y1;
   x2 = x2 < 0 ? 0 : x2;
   y2 = y2 < 0 ? 0 : y2;
   // set params object
   var params = {"iid": this.id, "x": x1, "x1": x2, "y" :y1, "y1": y2, "footprint": footprint, "algoritms": algs};
   apiCall(callback, this.algDataUrl, params);
 }
}
// see https://github.com/birm/OSDragonDemos/blob/master/coordinated_draw_annotool.html
function drawFromList(data, context){
  data.forEach(function(annotation){
    let id = annotation.provenance.analysis.execution_id;
    let color = "red";
    let type = annotation.geometry.type;
    let coords = annotation.geometry.coordinates[0];
    if (type == "Polygon"){
      // start
      context.beginPath();
      let first = coords.splice(1,1);
      context.moveTo(first[0], first[1])
      coords.forEach(function(coord){
        let x = coord[0];
        let y = coord[1];
        context.lineTo(x,y);
        console.log(x + ", " + y)
      });
      context.stroke();
      // stop
    } else {
      console.warn("Don't know how to draw '" + type + "'");
    }
  })
}
// TODO something to tell the viewer to redraw on move/zoom/open

class annotations{
  constructor(id, viewer, context, imagingHelper, options){
    this.id = id;
    this.store = new annotStore(id, options);
    this.viewer = viewer;
    this.options = options;
    this.selection = [];
    this.context = context;
    // TODO imagingHelper needed?
  }

  // draw using current viewer state
  draw(){
    this.x1 = this.imagingHelper._viewportOrigin['x'];
    this.y1 = this.imagingHelper._viewportOrigin['y'];
    this.x2 = this.x1 + this.imagingHelper._viewportWidth;
    this.y2 = this.y1 + this.imagingHelper._viewportHeight;

    var max = new OpenSeadragon.Point(this.imagingHelper.physicalToDataX(9), this.imagingHelper.physicalToDataY(9));
    var origin = new OpenSeadragon.Point(this.imagingHelper.physicalToDataX(0), this.imagingHelper.physicalToDataY(0));
    var footprint = (max.x - origin.x) * (max.y - origin.y);

    this.store.getAlgData(x, y, x1, y1, footprint, this.selection, function(data){
      drawFromList(data, this.context);
    });
  }

  select(alg){
    if(this.selection.indexOf(alg) == -1 ){
      this.selection.push(alg);
    }
  }

  deselect(alg){
    let pos = this.selection.indexOf(alg)
    if(pos > -1 ){
      this.selection.splice(pos, 1);
    }
  }

}
<|endoftext|>