<commit_before>/*
 * This file is part of the Fxp package.
 *
 * (c) François Pluchino <francois.pluchino@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

import pluginify from '@fxp/jquery-pluginify';
import BasePlugin from '@fxp/jquery-pluginify/js/plugin';
import {
    getNativeScrollWidth,
    mobileCheck,
    initScroller,
    destroyScroller,
    lockBodyScroll,
    unlockBodyScroll
} from "./utils/scrollbar";
import {changeTransition, isOverMinWidth} from "./utils/css";
import {initWithLocalStorage, setLocalStorage} from "./utils/storage";
import {onResizeWindow} from "./utils/window";
import {prefixedEvent, triggerEvent} from "./utils/events";
import {keyboardAction, closeExternal, closeOnSelect, cleanCloseDelay} from "./utils/actions";
import {destroyHammer, initHammer, onEndTransition} from "./utils/touch";
import {addClassToggles, removeClassToggles, doDetachToggle} from "./utils/toggle";

/**
 * Left position.
 */
export const POSITION_LEFT  = 'left';

/**
 * Right position.
 */
export const POSITION_RIGHT = 'right';

/**
 * Not force toggle.
 */
export const FORCE_TOGGLE_NO = false;

/**
 * Force toggle.
 */
export const FORCE_TOGGLE = true;

/**
 * Always force toggle.
 */
export const FORCE_TOGGLE_ALWAYS = 'always';

/**
 * Defaults options.
 */
const DEFAULTS = {
    classWrapper:       'sidebar-wrapper',
    classContainer:     'container-main',
    classOpen:          'sidebar-open',
    classLocked:        'sidebar-locked',
    classForceOpen:     'sidebar-force-open',
    classOnDragging:    'sidebar-dragging',
    classObfuscator:     'sidebar-obfuscator',
    forceToggle:        FORCE_TOGGLE_NO,
    locked:             false,
    position:           POSITION_LEFT,
    minLockWidth:       992,
    toggleId:           null,
    toggleOpenOnHover:  false,
    toggleOnClick:      false,
    saveConfig:         false,
    storageLockedKey:   'fxp/sidebar/locked',
    clickableSwipe:     false,
    draggable:          true,
    closeOnSelect:      true,
    closeOnSelectDelay: 0.5,
    resetScrollDelay:   0.3,
    itemSelector:       '.sidebar-menu a',
    useScroller:        true,
    scrollerScrollbar:  undefined,
    scroller:           {
        contentSelector: '.sidebar-menu',
        scrollerStickyHeader: true,
        stickyOptions: {
            selector: '> .sidebar-menu > .sidebar-group > span'
        }
    },
    hammer:             {},
    disabledKeyboard:   false,
    keyboardEvent:      {
        ctrlKey:  true,
        shiftKey: false,
        altKey:   true,
        keyCode:  'S'.charCodeAt(0)
    }
};

/**
 * Sidebar class.
 */
export default class Sidebar extends BasePlugin
{
    /**
     * Constructor.
     *
     * @param {HTMLElement} element The DOM element
     * @param {object}      options The options
     */
    constructor(element, options = {}) {
        super(element, $.extend(true, {}, DEFAULTS, options));

        let self = this,
            isOver,
            ua = navigator.userAgent.toLowerCase();

        this.eventType = 'click';
        this.nativeScrollWidth = getNativeScrollWidth();
        this.$toggles = $([]);
        this.$wrapper = $('

');
        this.$container = $('> .' + this.options.classContainer, this.$element.parent());
        this.$swipe = null;
        this.$obfuscator = $('

');
        this.$body = $('body');
        this.enabled = !this.$element.hasClass('sidebar-disabled');
        this.hammer = null;
        this.dragStartPosition = null;
        this.mouseDragEnd = null;
        this.dragDirection = null;
        this.closeDelay = null;
        this.resetScrolling = null;
        this.originalBodyPad = null;
        this.originalBodyOverflowY = null;
        this.fixDragClick = ua.indexOf('firefox') > -1 || ua.indexOf('edge') > -1 || ua.indexOf('msie') > -1;

        this.$element.before(this.$wrapper);
        this.$wrapper.append(this.$element);
        this.$wrapper.append(this.$obfuscator);
        this.$element.attr('data-sidebar', 'true');

        if (null !== this.options.toggleId) {
            this.attachToggle('#' + this.options.toggleId);
        }

        if (undefined !== this.options.scrollerStickyHeader) {
            this.options.scroller.scrollerStickyHeader = this.options.scrollerStickyHeader;
            delete this.options.scrollerStickyHeader;
        }

        if (undefined !== this.options.scrollerScrollbar) {
            this.options.scroller.scrollbar = this.options.scrollerScrollbar;
            delete this.options.scrollerScrollbar;
        }

        if (this.$element.hasClass(this.options.classLocked)) {
            this.options.locked = true;
        }

        if (POSITION_RIGHT !== this.options.position) {
            this.options.position = POSITION_LEFT;

        } else {
            this.$element.addClass('sidebar-right');
        }

        if (this.$element.hasClass('sidebar-right')) {
            this.options.position = POSITION_RIGHT;
        }

        if (this.options.position === POSITION_RIGHT) {
            this.options.keyboardEvent.shiftKey = true;
        }

        if (!this.enabled) {
            this.$element
                .removeClass(this.options.classLocked)
                .removeClass(this.options.classForceOpen)
                .removeClass(this.options.classOpen)
                .removeClass(this.options.classOpen + '-init');
        }

        if (this.options.locked) {
            this.options.forceToggle = FORCE_TOGGLE_ALWAYS;
            changeTransition(this.$element, 'none');

            if (this.enabled) {
                this.$element
                    .addClass(this.options.classLocked)
                    .addClass(this.options.classForceOpen)
                    .addClass(this.options.classOpen + '-init');

                this.$container.addClass('container-force-open-' + this.options.position);
            }
        }

        isOver = isOverMinWidth(this);

        $(window).on('keyup.fxp.sidebar' + this.guid, null, this, keyboardAction);
        $(window).on('resize.fxp.sidebar' + this.guid, null, this, onResizeWindow);

        if (this.$element.hasClass(this.options.classOpen + '-init')) {
            if (isOver) {
                this.$element.addClass(this.options.classOpen);

            } else {
                this.$element
                    .removeClass(this.options.classOpen)
                    .removeClass(this.options.classForceOpen)
                ;
            }

            this.$element.removeClass(this.options.classOpen + '-init');
        }

        if (this.$element.hasClass(this.options.classOpen) && !isOver) {
            lockBodyScroll(this);
        } else {
            unlockBodyScroll(this);
        }

        if (this.options.closeOnSelect) {
            this.$element.on(this.eventType + '.fxp.sidebar' + this.guid, this.options.itemSelector, this, closeOnSelect);
        }

        this.$element.on(prefixedEvent('TransitionEnd', '.fxp.sidebar' + this.guid), null, this, onEndTransition);
        this.$obfuscator.on(this.eventType + '.fxp.sidebar' + this.guid, null, this, closeExternal);

        initScroller(this);
        initHammer(this);
        initWithLocalStorage(this);

        window.setTimeout(function () {
            changeTransition(self.$element, '');
            self.$element.addClass('sidebar-ready');
            triggerEvent('ready', self);
        }, 0);
    }

    /**
     * Get sidebar position.
     *
     * @returns {string} The position (left or right)
     */
    getPosition() {
        return this.options.position;
    }

    /**
     * Checks if sidebar is locked (always open).
     *
     * @returns {boolean}
     */
    isLocked() {
        return this.options.locked;
    }

    /**
     * Checks if sidebar is locked (always open).
     *
     * @returns {boolean}
     */
    isOpen() {
        return this.$element.hasClass(this.options.classOpen);
    }

    /**
     * Checks if sidebar is fully opened.
     *
     * @return {boolean}
     */
    isFullyOpened() {
        return this.$element.hasClass(this.options.classForceOpen);
    }

    /**
     * Checks if sidebar is closable.
     *
     * @return {boolean}
     */
    isClosable() {
        return this.enabled && this.isOpen() && !isOverMinWidth(this);
    }

    /**
     * Force open the sidebar.
     */
    forceOpen() {
        if (!this.enabled || (this.isOpen() && this.isFullyOpened())) {
            return;
        }

        cleanCloseDelay(this);
        this.$element.addClass(this.options.classForceOpen);
        this.$container.addClass('container-force-open-' + this.options.position);
        addClassToggles(this, this.options.classForceOpen + '-toggle');

        triggerEvent('force-open', this);
        this.open();
    }

    /**
     * Force close the sidebar.
     */
    forceClose() {
        if (!this.enabled || !this.isOpen() || (this.isLocked() && isOverMinWidth(this))) {
            return;
        }

        cleanCloseDelay(this);
        removeClassToggles(this, this.options.classForceOpen + '-toggle');
        this.$container.removeClass('container-force-open-' + this.options.position);
        this.$element.removeClass(this.options.classForceOpen);

        triggerEvent('force-close', this);
        this.close();
    }

    /**
     * Open the sidebar.
     */
    open() {
        if (!this.enabled || this.isOpen()) {
            return;
        }

        triggerEvent('open', this);
        cleanCloseDelay(this);
        $('[data-sidebar=true]').sidebar('forceClose');
        this.$element.addClass(this.options.classOpen);
    }

    /**
     * Close open the sidebar.
     *
     * @this Sidebar
     */
    close() {
        if (!this.enabled || !this.isOpen() || (this.isFullyOpened() && isOverMinWidth(this))) {
            return;
        }

        triggerEvent('close', this);
        cleanCloseDelay(this);
        this.$element.removeClass(this.options.classOpen);
    }

    /**
     * Toggle the sidebar ("close, "open", "force open").
     *
     * @param {jQuery.Event|Event} [event]
     *
     * @typedef {Sidebar} Event.data The sidebar instance
     */
    toggle(event) {
        let self = (undefined !== event) ? event.data : this;

        if (!self.enabled) {
            return;
        }

        if (undefined !== event) {
            event.stopPropagation();
            event.preventDefault();
        }

        triggerEvent('toggle', this);

        if (self.options.toggleOnClick) {
            self.options.locked = !self.options.locked;
            setLocalStorage(self, self.options.storageLockedKey, self.options.locked);
        }

        if (self.isOpen()) {
            if (self.isFullyOpened()) {
                self.forceClose();

            } else if (isOverMinWidth(self) && $.inArray(self.options.forceToggle, [FORCE_TOGGLE, FORCE_TOGGLE_ALWAYS]) >= 0) {
                self.forceOpen();

            } else {
                self.close();
            }

        } else if (isOverMinWidth(self) && FORCE_TOGGLE_ALWAYS === self.options.forceToggle) {
            self.forceOpen();

        } else {
            self.open();
        }
    }

    /**
     * Refresh the scroller.
     */
    refresh() {
        triggerEvent('refresh', this);

        if ($.fn.scroller && this.options.useScroller) {
            this.$element.scroller('refresh');
        }
    }

    /**
     * Attach a toggle button.
     *
     * @param {string|HTMLElement|object|jQuery} $toggle
     */
    attachToggle($toggle) {
        $toggle = $($toggle);

        if (!this.enabled) {
            $toggle.addClass('disabled');
        }

        if (this.isLocked()) {
            $toggle.addClass(this.options.classLocked + '-toggle');
        } else {
            $toggle.removeClass(this.options.classLocked + '-toggle');
        }

        if (this.isFullyOpened()) {
            $toggle.addClass(this.options.classForceOpen + '-toggle');
        } else {
            $toggle.removeClass(this.options.classForceOpen + '-toggle');
        }

        if (this.isOpen()) {
            $toggle.addClass(this.options.classOpen + '-toggle');
        } else {
            $toggle.removeClass(this.options.classOpen + '-toggle');
        }

        $toggle.on(this.eventType + '.fxp.sidebar' + this.guid, null, this, this.toggle);

        if (!mobileCheck() && this.options.toggleOpenOnHover) {
            $toggle.on('mouseover.fxp.sidebar' + this.guid, $.proxy(this.open, this));
        }

        this.$toggles.push($toggle);
    }

    /**
     * Detach a toggle button.
     *
     * @param {string|HTMLElement|object|jQuery} $toggle
     */
    detachToggle($toggle) {
        let size = this.$toggles.length,
            i;

        $toggle = $($toggle);

        for (i = 0; i < size; ++i) {
            if (this.$toggles[i][0] === $toggle[0]) {
                doDetachToggle(this, this.$toggles[i]);
                this.$toggles.splice(i, 1);
                break;
            }
        }
    }

    /**
     * Detach a toggle button.
     */
    detachToggles() {
        let size = this.$toggles.length,
            i;

        for (i = 0; i < size; ++i) {
            doDetachToggle(this, this.$toggles[i]);
        }

        this.$toggles.splice(0, size);
    }

    /**
     * Checks if sidebar is enabled.
     *
     * @returns {boolean}
     */
    isEnabled() {
        return this.enabled;
    }

    /**
     * Disable the sidebar.
     */
    disable() {
        let prevIsLocked = this.isLocked();

        if (!this.enabled) {
            return;
        }

        triggerEvent('disable', this);
        this.options.locked = false;
        this.forceClose();
        this.options.locked = prevIsLocked;
        this.$element.addClass('sidebar-disabled');
        addClassToggles(this, 'disabled');

        if (this.isLocked()) {
            addClassToggles(this, this.options.classLocked + '-toggle-disabled');
        }

        this.enabled = false;
    }

    /**
     * Enable the sidebar.
     */
    enable() {
        if (this.enabled) {
            return;
        }

        triggerEvent('enable', this);
        this.enabled = true;
        this.$element.removeClass('sidebar-disabled');

        if (isOverMinWidth(this) && FORCE_TOGGLE_ALWAYS === this.options.forceToggle) {
            this.forceOpen();
        }

        removeClassToggles(this, 'disabled');

        if (this.isLocked()) {
            removeClassToggles(this, this.options.classLocked + '-toggle-disabled');
        }
    }

    /**
     * Destroy the instance.
     */
    destroy() {
        cleanCloseDelay(this);
        this.detachToggles();
        this.forceClose();
        $(window).off('keyup.fxp.sidebar' + this.guid, keyboardAction);
        $(window).off('resize.fxp.sidebar' + this.guid, onResizeWindow);
        this.$element.off(this.eventType + '.fxp.sidebar' + this.guid, this.options.itemSelector, closeOnSelect);
        this.$element.off(prefixedEvent('TransitionEnd', '.fxp.sidebar' + this.guid), onEndTransition);
        this.$obfuscator.off(this.eventType + '.fxp.sidebar' + this.guid, closeExternal);

        destroyHammer(this);
        destroyScroller(this);
        unlockBodyScroll(this);

        this.$wrapper.before(this.$element);
        this.$wrapper.remove();

        this.$element.removeClass('sidebar-ready');

        super.destroy();
    }
}

pluginify('sidebar', 'fxp.sidebar', Sidebar, true, '[data-sidebar="true"]');
<commit_msg>Use default options and locales properties of base plugin class<commit_after>/*
 * This file is part of the Fxp package.
 *
 * (c) François Pluchino <francois.pluchino@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

import pluginify from '@fxp/jquery-pluginify';
import BasePlugin from '@fxp/jquery-pluginify/js/plugin';
import {
    getNativeScrollWidth,
    mobileCheck,
    initScroller,
    destroyScroller,
    lockBodyScroll,
    unlockBodyScroll
} from "./utils/scrollbar";
import {changeTransition, isOverMinWidth} from "./utils/css";
import {initWithLocalStorage, setLocalStorage} from "./utils/storage";
import {onResizeWindow} from "./utils/window";
import {prefixedEvent, triggerEvent} from "./utils/events";
import {keyboardAction, closeExternal, closeOnSelect, cleanCloseDelay} from "./utils/actions";
import {destroyHammer, initHammer, onEndTransition} from "./utils/touch";
import {addClassToggles, removeClassToggles, doDetachToggle} from "./utils/toggle";

/**
 * Left position.
 */
export const POSITION_LEFT  = 'left';

/**
 * Right position.
 */
export const POSITION_RIGHT = 'right';

/**
 * Not force toggle.
 */
export const FORCE_TOGGLE_NO = false;

/**
 * Force toggle.
 */
export const FORCE_TOGGLE = true;

/**
 * Always force toggle.
 */
export const FORCE_TOGGLE_ALWAYS = 'always';

/**
 * Sidebar class.
 */
export default class Sidebar extends BasePlugin
{
    /**
     * Constructor.
     *
     * @param {HTMLElement} element The DOM element
     * @param {object}      options The options
     */
    constructor(element, options = {}) {
        super(element, $.extend(true, {}, Sidebar.defaultOptions, options));

        let self = this,
            isOver,
            ua = navigator.userAgent.toLowerCase();

        this.eventType = 'click';
        this.nativeScrollWidth = getNativeScrollWidth();
        this.$toggles = $([]);
        this.$wrapper = $('

');
        this.$container = $('> .' + this.options.classContainer, this.$element.parent());
        this.$swipe = null;
        this.$obfuscator = $('

');
        this.$body = $('body');
        this.enabled = !this.$element.hasClass('sidebar-disabled');
        this.hammer = null;
        this.dragStartPosition = null;
        this.mouseDragEnd = null;
        this.dragDirection = null;
        this.closeDelay = null;
        this.resetScrolling = null;
        this.originalBodyPad = null;
        this.originalBodyOverflowY = null;
        this.fixDragClick = ua.indexOf('firefox') > -1 || ua.indexOf('edge') > -1 || ua.indexOf('msie') > -1;

        this.$element.before(this.$wrapper);
        this.$wrapper.append(this.$element);
        this.$wrapper.append(this.$obfuscator);
        this.$element.attr('data-sidebar', 'true');

        if (null !== this.options.toggleId) {
            this.attachToggle('#' + this.options.toggleId);
        }

        if (undefined !== this.options.scrollerStickyHeader) {
            this.options.scroller.scrollerStickyHeader = this.options.scrollerStickyHeader;
            delete this.options.scrollerStickyHeader;
        }

        if (undefined !== this.options.scrollerScrollbar) {
            this.options.scroller.scrollbar = this.options.scrollerScrollbar;
            delete this.options.scrollerScrollbar;
        }

        if (this.$element.hasClass(this.options.classLocked)) {
            this.options.locked = true;
        }

        if (POSITION_RIGHT !== this.options.position) {
            this.options.position = POSITION_LEFT;

        } else {
            this.$element.addClass('sidebar-right');
        }

        if (this.$element.hasClass('sidebar-right')) {
            this.options.position = POSITION_RIGHT;
        }

        if (this.options.position === POSITION_RIGHT) {
            this.options.keyboardEvent.shiftKey = true;
        }

        if (!this.enabled) {
            this.$element
                .removeClass(this.options.classLocked)
                .removeClass(this.options.classForceOpen)
                .removeClass(this.options.classOpen)
                .removeClass(this.options.classOpen + '-init');
        }

        if (this.options.locked) {
            this.options.forceToggle = FORCE_TOGGLE_ALWAYS;
            changeTransition(this.$element, 'none');

            if (this.enabled) {
                this.$element
                    .addClass(this.options.classLocked)
                    .addClass(this.options.classForceOpen)
                    .addClass(this.options.classOpen + '-init');

                this.$container.addClass('container-force-open-' + this.options.position);
            }
        }

        isOver = isOverMinWidth(this);

        $(window).on('keyup.fxp.sidebar' + this.guid, null, this, keyboardAction);
        $(window).on('resize.fxp.sidebar' + this.guid, null, this, onResizeWindow);

        if (this.$element.hasClass(this.options.classOpen + '-init')) {
            if (isOver) {
                this.$element.addClass(this.options.classOpen);

            } else {
                this.$element
                    .removeClass(this.options.classOpen)
                    .removeClass(this.options.classForceOpen)
                ;
            }

            this.$element.removeClass(this.options.classOpen + '-init');
        }

        if (this.$element.hasClass(this.options.classOpen) && !isOver) {
            lockBodyScroll(this);
        } else {
            unlockBodyScroll(this);
        }

        if (this.options.closeOnSelect) {
            this.$element.on(this.eventType + '.fxp.sidebar' + this.guid, this.options.itemSelector, this, closeOnSelect);
        }

        this.$element.on(prefixedEvent('TransitionEnd', '.fxp.sidebar' + this.guid), null, this, onEndTransition);
        this.$obfuscator.on(this.eventType + '.fxp.sidebar' + this.guid, null, this, closeExternal);

        initScroller(this);
        initHammer(this);
        initWithLocalStorage(this);

        window.setTimeout(function () {
            changeTransition(self.$element, '');
            self.$element.addClass('sidebar-ready');
            triggerEvent('ready', self);
        }, 0);
    }

    /**
     * Get sidebar position.
     *
     * @returns {string} The position (left or right)
     */
    getPosition() {
        return this.options.position;
    }

    /**
     * Checks if sidebar is locked (always open).
     *
     * @returns {boolean}
     */
    isLocked() {
        return this.options.locked;
    }

    /**
     * Checks if sidebar is locked (always open).
     *
     * @returns {boolean}
     */
    isOpen() {
        return this.$element.hasClass(this.options.classOpen);
    }

    /**
     * Checks if sidebar is fully opened.
     *
     * @return {boolean}
     */
    isFullyOpened() {
        return this.$element.hasClass(this.options.classForceOpen);
    }

    /**
     * Checks if sidebar is closable.
     *
     * @return {boolean}
     */
    isClosable() {
        return this.enabled && this.isOpen() && !isOverMinWidth(this);
    }

    /**
     * Force open the sidebar.
     */
    forceOpen() {
        if (!this.enabled || (this.isOpen() && this.isFullyOpened())) {
            return;
        }

        cleanCloseDelay(this);
        this.$element.addClass(this.options.classForceOpen);
        this.$container.addClass('container-force-open-' + this.options.position);
        addClassToggles(this, this.options.classForceOpen + '-toggle');

        triggerEvent('force-open', this);
        this.open();
    }

    /**
     * Force close the sidebar.
     */
    forceClose() {
        if (!this.enabled || !this.isOpen() || (this.isLocked() && isOverMinWidth(this))) {
            return;
        }

        cleanCloseDelay(this);
        removeClassToggles(this, this.options.classForceOpen + '-toggle');
        this.$container.removeClass('container-force-open-' + this.options.position);
        this.$element.removeClass(this.options.classForceOpen);

        triggerEvent('force-close', this);
        this.close();
    }

    /**
     * Open the sidebar.
     */
    open() {
        if (!this.enabled || this.isOpen()) {
            return;
        }

        triggerEvent('open', this);
        cleanCloseDelay(this);
        $('[data-sidebar=true]').sidebar('forceClose');
        this.$element.addClass(this.options.classOpen);
    }

    /**
     * Close open the sidebar.
     *
     * @this Sidebar
     */
    close() {
        if (!this.enabled || !this.isOpen() || (this.isFullyOpened() && isOverMinWidth(this))) {
            return;
        }

        triggerEvent('close', this);
        cleanCloseDelay(this);
        this.$element.removeClass(this.options.classOpen);
    }

    /**
     * Toggle the sidebar ("close, "open", "force open").
     *
     * @param {jQuery.Event|Event} [event]
     *
     * @typedef {Sidebar} Event.data The sidebar instance
     */
    toggle(event) {
        let self = (undefined !== event) ? event.data : this;

        if (!self.enabled) {
            return;
        }

        if (undefined !== event) {
            event.stopPropagation();
            event.preventDefault();
        }

        triggerEvent('toggle', this);

        if (self.options.toggleOnClick) {
            self.options.locked = !self.options.locked;
            setLocalStorage(self, self.options.storageLockedKey, self.options.locked);
        }

        if (self.isOpen()) {
            if (self.isFullyOpened()) {
                self.forceClose();

            } else if (isOverMinWidth(self) && $.inArray(self.options.forceToggle, [FORCE_TOGGLE, FORCE_TOGGLE_ALWAYS]) >= 0) {
                self.forceOpen();

            } else {
                self.close();
            }

        } else if (isOverMinWidth(self) && FORCE_TOGGLE_ALWAYS === self.options.forceToggle) {
            self.forceOpen();

        } else {
            self.open();
        }
    }

    /**
     * Refresh the scroller.
     */
    refresh() {
        triggerEvent('refresh', this);

        if ($.fn.scroller && this.options.useScroller) {
            this.$element.scroller('refresh');
        }
    }

    /**
     * Attach a toggle button.
     *
     * @param {string|HTMLElement|object|jQuery} $toggle
     */
    attachToggle($toggle) {
        $toggle = $($toggle);

        if (!this.enabled) {
            $toggle.addClass('disabled');
        }

        if (this.isLocked()) {
            $toggle.addClass(this.options.classLocked + '-toggle');
        } else {
            $toggle.removeClass(this.options.classLocked + '-toggle');
        }

        if (this.isFullyOpened()) {
            $toggle.addClass(this.options.classForceOpen + '-toggle');
        } else {
            $toggle.removeClass(this.options.classForceOpen + '-toggle');
        }

        if (this.isOpen()) {
            $toggle.addClass(this.options.classOpen + '-toggle');
        } else {
            $toggle.removeClass(this.options.classOpen + '-toggle');
        }

        $toggle.on(this.eventType + '.fxp.sidebar' + this.guid, null, this, this.toggle);

        if (!mobileCheck() && this.options.toggleOpenOnHover) {
            $toggle.on('mouseover.fxp.sidebar' + this.guid, $.proxy(this.open, this));
        }

        this.$toggles.push($toggle);
    }

    /**
     * Detach a toggle button.
     *
     * @param {string|HTMLElement|object|jQuery} $toggle
     */
    detachToggle($toggle) {
        let size = this.$toggles.length,
            i;

        $toggle = $($toggle);

        for (i = 0; i < size; ++i) {
            if (this.$toggles[i][0] === $toggle[0]) {
                doDetachToggle(this, this.$toggles[i]);
                this.$toggles.splice(i, 1);
                break;
            }
        }
    }

    /**
     * Detach a toggle button.
     */
    detachToggles() {
        let size = this.$toggles.length,
            i;

        for (i = 0; i < size; ++i) {
            doDetachToggle(this, this.$toggles[i]);
        }

        this.$toggles.splice(0, size);
    }

    /**
     * Checks if sidebar is enabled.
     *
     * @returns {boolean}
     */
    isEnabled() {
        return this.enabled;
    }

    /**
     * Disable the sidebar.
     */
    disable() {
        let prevIsLocked = this.isLocked();

        if (!this.enabled) {
            return;
        }

        triggerEvent('disable', this);
        this.options.locked = false;
        this.forceClose();
        this.options.locked = prevIsLocked;
        this.$element.addClass('sidebar-disabled');
        addClassToggles(this, 'disabled');

        if (this.isLocked()) {
            addClassToggles(this, this.options.classLocked + '-toggle-disabled');
        }

        this.enabled = false;
    }

    /**
     * Enable the sidebar.
     */
    enable() {
        if (this.enabled) {
            return;
        }

        triggerEvent('enable', this);
        this.enabled = true;
        this.$element.removeClass('sidebar-disabled');

        if (isOverMinWidth(this) && FORCE_TOGGLE_ALWAYS === this.options.forceToggle) {
            this.forceOpen();
        }

        removeClassToggles(this, 'disabled');

        if (this.isLocked()) {
            removeClassToggles(this, this.options.classLocked + '-toggle-disabled');
        }
    }

    /**
     * Destroy the instance.
     */
    destroy() {
        cleanCloseDelay(this);
        this.detachToggles();
        this.forceClose();
        $(window).off('keyup.fxp.sidebar' + this.guid, keyboardAction);
        $(window).off('resize.fxp.sidebar' + this.guid, onResizeWindow);
        this.$element.off(this.eventType + '.fxp.sidebar' + this.guid, this.options.itemSelector, closeOnSelect);
        this.$element.off(prefixedEvent('TransitionEnd', '.fxp.sidebar' + this.guid), onEndTransition);
        this.$obfuscator.off(this.eventType + '.fxp.sidebar' + this.guid, closeExternal);

        destroyHammer(this);
        destroyScroller(this);
        unlockBodyScroll(this);

        this.$wrapper.before(this.$element);
        this.$wrapper.remove();

        this.$element.removeClass('sidebar-ready');

        super.destroy();
    }
}

/**
 * Defaults options.
 */
Sidebar.defaultOptions = {
    classWrapper:       'sidebar-wrapper',
    classContainer:     'container-main',
    classOpen:          'sidebar-open',
    classLocked:        'sidebar-locked',
    classForceOpen:     'sidebar-force-open',
    classOnDragging:    'sidebar-dragging',
    classObfuscator:     'sidebar-obfuscator',
    forceToggle:        FORCE_TOGGLE_NO,
    locked:             false,
    position:           POSITION_LEFT,
    minLockWidth:       992,
    toggleId:           null,
    toggleOpenOnHover:  false,
    toggleOnClick:      false,
    saveConfig:         false,
    storageLockedKey:   'fxp/sidebar/locked',
    clickableSwipe:     false,
    draggable:          true,
    closeOnSelect:      true,
    closeOnSelectDelay: 0.5,
    resetScrollDelay:   0.3,
    itemSelector:       '.sidebar-menu a',
    useScroller:        true,
    scrollerScrollbar:  undefined,
    scroller:           {
        contentSelector: '.sidebar-menu',
        scrollerStickyHeader: true,
        stickyOptions: {
            selector: '> .sidebar-menu > .sidebar-group > span'
        }
    },
    hammer:             {},
    disabledKeyboard:   false,
    keyboardEvent:      {
        ctrlKey:  true,
        shiftKey: false,
        altKey:   true,
        keyCode:  'S'.charCodeAt(0)
    }
};

pluginify('sidebar', 'fxp.sidebar', Sidebar, true, '[data-sidebar="true"]');
<|endoftext|>


<commit_before>
ObjectDimensionsHelper = function (options) {
  BaseHelper.call( this );
  var options = options || {};
  var color = this.color = options.color || 0x000000;
  var mesh = options.mesh || this.parent || null;

  this.textBgColor= options.textBgColor!== undefined ? options.textBgColor : "#fff";
  this.textColor  = options.textColor!== undefined ? options.textColor : "#000";
  this.labelType  = options.labelType!== undefined ? options.labelType : "flat";
}

ObjectDimensionsHelper.prototype = Object.create( BaseHelper.prototype );
ObjectDimensionsHelper.prototype.constructor = ObjectDimensionsHelper;

ObjectDimensionsHelper.prototype.attach = function(mesh){
  var color = this.color;
  var mesh = this.mesh = mesh;
  var lineMat = new THREE.MeshBasicMaterial({color: color, wireframe: true, shading:THREE.FlatShading});
  /*mesh.updateMatrixWorld();
  var matrixWorld = new THREE.Vector3();
  matrixWorld.setFromMatrixPosition( mesh.matrixWorld );
  this.position.copy( matrixWorld );*/

  this.getBounds(mesh);
  var delta = this.computeMiddlePoint(mesh);

  var width = this.width;
  var length = this.length;
  var height = this.height;
  //console.log("w",width,"l",length,"h",height,delta);

  var baseCubeGeom = new THREE.BoxGeometry(this.length, this.width,this.height)
  this.meshBoundingBox = new THREE.Mesh(baseCubeGeom,new THREE.MeshBasicMaterial({wireframe:true,color:0xff0000}))
  //this.add( this.meshBoundingBox )

  var baseOutlineGeometry = new THREE.Geometry();
  baseOutlineGeometry.vertices.push(new THREE.Vector3(-this.length/2, -this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(this.length/2, -this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(this.length/2, this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(-this.length/2, this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(-this.length/2, -this.width/2, 0));
  baseOutlineGeometry.computeLineDistances();

  var baseOutline = new THREE.Object3D();

  var dashMaterial = new THREE.LineDashedMaterial( { color: 0x000000, dashSize: 2.5,
  gapSize: 2, depthTest: false,linewidth:1, opacity:0.2,transparent:true} );
  var baseOutlineBack = new THREE.Line( baseOutlineGeometry, dashMaterial, THREE.Lines );
  baseOutlineBack.renderDepth = 1e20
  baseOutlineBack.position.copy( new THREE.Vector3(delta.x,delta.y,delta.z-this.height/2) );

  var dashMaterial2 = new THREE.LineDashedMaterial( { color: 0x000000, dashSize: 2.5,
   gapSize: 2, depthTest: true,linewidth:1} );
  var baseOutlineFront = baseOutlineBack.clone();
  baseOutlineFront.material = dashMaterial2;

  baseOutline.add(baseOutlineBack);
  baseOutline.add(baseOutlineFront);

  this.baseOutline = baseOutline;
  this.add(baseOutline);

  var widthArrowPos = new THREE.Vector3(delta.x+this.length/2,delta.y,delta.z-this.height/2);
  var lengthArrowPos = new THREE.Vector3( delta.x, delta.y+this.width/2, delta.z-this.height/2)
  var heightArrowPos = new THREE.Vector3( delta.x-this.length/2,delta.y+this.width/2,delta.z)
  //console.log("width", this.width, "length", this.length, "height", this.height,"delta",delta, "widthArrowPos", widthArrowPos)
  var sideLength =10;

  //length, sideLength, position, direction, color, text, textSize,
  var widthArrow  = new SizeHelper( {length:this.width,sideLength:sideLength,
  position:widthArrowPos,direction:new THREE.Vector3(0,1,0),
  textBgColor:this.textBgColor, textColor:this.textColor, labelType:this.labelType  });
  var lengthArrow = new SizeHelper( {length:this.length,sideLength:sideLength,
  position:lengthArrowPos,direction:new THREE.Vector3(-1,0,0),
  textBgColor:this.textBgColor, textColor:this.textColor, labelType:this.labelType  });
  var heightArrow = new SizeHelper( {length:this.height,
  sideLength:sideLength,position:heightArrowPos,direction:new THREE.Vector3(0,0,1),
  textBgColor:this.textBgColor, textColor:this.textColor, labelType:this.labelType });

  widthArrow.set();
  lengthArrow.set();
  heightArrow.set();

  this.arrows = new THREE.Object3D();
  this.arrows.add( widthArrow );
  this.arrows.add( lengthArrow );
  this.arrows.add( heightArrow );

  this.add( this.arrows );
}

ObjectDimensionsHelper.prototype.detach = function(mesh){
  this.mesh = null;
  //this.remove( this.meshBoundingBox );
  this.remove( this.baseOutline );
  this.remove( this.arrows );
}

ObjectDimensionsHelper.prototype.computeMiddlePoint=function(mesh)
{
  var middle  = new THREE.Vector3()
  middle.x  = ( mesh.boundingBox.max.x + mesh.boundingBox.min.x ) / 2;
  middle.y  = ( mesh.boundingBox.max.y + mesh.boundingBox.min.y ) / 2;
  middle.z  = ( mesh.boundingBox.max.z + mesh.boundingBox.min.z ) / 2;
  //console.log("mid",geometry.boundingBox.max.z,geometry.boundingBox.min.z, geometry.boundingBox.max.z+geometry.boundingBox.min.z)
  return middle;
}

ObjectDimensionsHelper.prototype.getBounds=function(mesh)
{
  var bbox = new THREE.Box3().setFromObject( mesh );

  var length = ( (bbox.max.x-bbox.min.x).toFixed(2) )/1; // division by one to coerce to number
  var width  = ( (bbox.max.y-bbox.min.y).toFixed(2) )/1;
  var height = ( (bbox.max.z-bbox.min.z).toFixed(2) )/1;

  this.width = width;
  this.height = height;
  this.length = length;

  return [length,width, height];
}

<commit_msg>- added an "update" method and system to make dimensionsHelpers "follow" the object they are attached to  * needs fine tuning  * seems a bit buggy when used with cloned objects ?<commit_after>
ObjectDimensionsHelper = function (options) {
  BaseHelper.call( this );
  var options = options || {};
  var color = this.color = options.color || 0x000000;
  var mesh = options.mesh || this.parent || null;

  this.textBgColor= options.textBgColor!== undefined ? options.textBgColor : "#fff";
  this.textColor  = options.textColor!== undefined ? options.textColor : "#000";
  this.labelType  = options.labelType!== undefined ? options.labelType : "flat";
}

ObjectDimensionsHelper.prototype = Object.create( BaseHelper.prototype );
ObjectDimensionsHelper.prototype.constructor = ObjectDimensionsHelper;

ObjectDimensionsHelper.prototype.attach = function(mesh){
  var color = this.color;
  var mesh = this.mesh = mesh;
  var lineMat = new THREE.MeshBasicMaterial({color: color, wireframe: true, shading:THREE.FlatShading});
  /*mesh.updateMatrixWorld();
  var matrixWorld = new THREE.Vector3();
  matrixWorld.setFromMatrixPosition( mesh.matrixWorld );
  this.position.copy( matrixWorld );*/

  this.getBounds(mesh);
  var delta = this.computeMiddlePoint(mesh);

  var width = this.width;
  var length = this.length;
  var height = this.height;
  //console.log("w",width,"l",length,"h",height,delta);

  var baseCubeGeom = new THREE.BoxGeometry(this.length, this.width,this.height)
  this.meshBoundingBox = new THREE.Mesh(baseCubeGeom,new THREE.MeshBasicMaterial({wireframe:true,color:0xff0000}))
  //this.add( this.meshBoundingBox )

  var baseOutlineGeometry = new THREE.Geometry();
  baseOutlineGeometry.vertices.push(new THREE.Vector3(-this.length/2, -this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(this.length/2, -this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(this.length/2, this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(-this.length/2, this.width/2, 0));
  baseOutlineGeometry.vertices.push(new THREE.Vector3(-this.length/2, -this.width/2, 0));
  baseOutlineGeometry.computeLineDistances();

  var baseOutline = new THREE.Object3D();

  var dashMaterial = new THREE.LineDashedMaterial( { color: 0x000000, dashSize: 2.5,
  gapSize: 2, depthTest: false,linewidth:1, opacity:0.2,transparent:true} );
  var baseOutlineBack = new THREE.Line( baseOutlineGeometry, dashMaterial, THREE.Lines );
  baseOutlineBack.renderDepth = 1e20
  baseOutlineBack.position.copy( new THREE.Vector3(delta.x,delta.y,delta.z-this.height/2) );

  var dashMaterial2 = new THREE.LineDashedMaterial( { color: 0x000000, dashSize: 2.5,
   gapSize: 2, depthTest: true,linewidth:1} );
  var baseOutlineFront = baseOutlineBack.clone();
  baseOutlineFront.material = dashMaterial2;

  baseOutline.add(baseOutlineBack);
  baseOutline.add(baseOutlineFront);

  this.baseOutline = baseOutline;
  this.add(baseOutline);

  var widthArrowPos = new THREE.Vector3(delta.x+this.length/2,delta.y,delta.z-this.height/2);
  var lengthArrowPos = new THREE.Vector3( delta.x, delta.y+this.width/2, delta.z-this.height/2)
  var heightArrowPos = new THREE.Vector3( delta.x-this.length/2,delta.y+this.width/2,delta.z)
  //console.log("width", this.width, "length", this.length, "height", this.height,"delta",delta, "widthArrowPos", widthArrowPos)
  var sideLength =10;

  //length, sideLength, position, direction, color, text, textSize,
  var widthArrow  = new SizeHelper( {length:this.width,sideLength:sideLength,
  position:widthArrowPos,direction:new THREE.Vector3(0,1,0),
  textBgColor:this.textBgColor, textColor:this.textColor, labelType:this.labelType  });
  var lengthArrow = new SizeHelper( {length:this.length,sideLength:sideLength,
  position:lengthArrowPos,direction:new THREE.Vector3(-1,0,0),
  textBgColor:this.textBgColor, textColor:this.textColor, labelType:this.labelType  });
  var heightArrow = new SizeHelper( {length:this.height,
  sideLength:sideLength,position:heightArrowPos,direction:new THREE.Vector3(0,0,1),
  textBgColor:this.textBgColor, textColor:this.textColor, labelType:this.labelType });

  widthArrow.set();
  lengthArrow.set();
  heightArrow.set();

  this.arrows = new THREE.Object3D();
  this.arrows.add( widthArrow );
  this.arrows.add( lengthArrow );
  this.arrows.add( heightArrow );

  this.add( this.arrows );

  this.objectOriginalPosition = this.mesh.position.clone();
}

ObjectDimensionsHelper.prototype.detach = function(mesh){
  this.mesh = null;
  //this.remove( this.meshBoundingBox );
  this.remove( this.baseOutline );
  this.remove( this.arrows );

  this.objectOriginalPosition = new THREE.Vector3();
}

ObjectDimensionsHelper.prototype.update = function(){
  var foo = this.mesh.position.clone().sub( this.objectOriginalPosition );
  this.position.add( foo );
  this.objectOriginalPosition = this.position.clone();
}

ObjectDimensionsHelper.prototype.computeMiddlePoint=function(mesh)
{
  var middle  = new THREE.Vector3()
  middle.x  = ( mesh.boundingBox.max.x + mesh.boundingBox.min.x ) / 2;
  middle.y  = ( mesh.boundingBox.max.y + mesh.boundingBox.min.y ) / 2;
  middle.z  = ( mesh.boundingBox.max.z + mesh.boundingBox.min.z ) / 2;
  //console.log("mid",geometry.boundingBox.max.z,geometry.boundingBox.min.z, geometry.boundingBox.max.z+geometry.boundingBox.min.z)
  return middle;
}

ObjectDimensionsHelper.prototype.getBounds=function(mesh)
{
  var bbox = new THREE.Box3().setFromObject( mesh );

  var length = ( (bbox.max.x-bbox.min.x).toFixed(2) )/1; // division by one to coerce to number
  var width  = ( (bbox.max.y-bbox.min.y).toFixed(2) )/1;
  var height = ( (bbox.max.z-bbox.min.z).toFixed(2) )/1;

  this.width = width;
  this.height = height;
  this.length = length;

  return [length,width, height];
}

<|endoftext|>


<commit_before>/* eslint-env mocha */
/* eslint max-nested-callbacks: ["error", 8] */

'use strict'

const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const series = require('async/series')
const pull = require('pull-stream')
const dagPB = require('ipld-dag-pb')
const DAGNode = dagPB.DAGNode
const dagCBOR = require('ipld-dag-cbor')
const CID = require('cids')

module.exports = (common) => {
  describe('.dag', () => {
    let ipfs
    let withGo

    before(function (done) {
      // CI takes longer to instantiate the daemon, so we need to increase the
      // timeout for the before step
      this.timeout(60 * 1000)

      common.setup((err, factory) => {
        expect(err).to.not.exist()
        factory.spawnNode((err, node) => {
          expect(err).to.not.exist()
          ipfs = node
          ipfs.id((err, id) => {
            expect(err).to.not.exist()
            withGo = id.agentVersion.startsWith('go-ipfs')
            done()
          })
        })
      })
    })

    after((done) => common.teardown(done))

    let pbNode
    let cborNode

    before((done) => {
      const someData = Buffer.from('some data')

      pbNode = DAGNode.create(someData, (err, node) => {
        expect(err).to.not.exist()
        pbNode = node
        done()
      })

      cborNode = {
        data: someData
      }
    })

    describe('.put', () => {
      it('dag-pb with default hash func (sha2-256)', (done) => {
        ipfs.dag.put(pbNode, {
          format: 'dag-pb',
          hashAlg: 'sha2-256'
        }, done)
      })

      it('dag-pb with custom hash func (sha3-512)', (done) => {
        ipfs.dag.put(pbNode, {
          format: 'dag-pb',
          hashAlg: 'sha3-512'
        }, done)
      })

      // This works because dag-cbor will just treat pbNode as a regular object
      it.skip('dag-pb node with wrong multicodec', (done) => {
        ipfs.dag.put(pbNode, 'dag-cbor', 'sha3-512', (err) => {
          expect(err).to.exist()
          done()
        })
      })

      it('dag-cbor with default hash func (sha2-256)', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha2-256'
        }, done)
      })

      it('dag-cbor with custom hash func (sha3-512)', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha3-512'
        }, done)
      })

      it('dag-cbor node with wrong multicodec', function (done) {
        // This works in go-ipfs because dag-pb will serialize any object. If
        // the object has neither a `data` nor `links` field it's serialized
        // as an empty object
        if (withGo) {
          this.skip()
        }
        ipfs.dag.put(cborNode, {
          format: 'dag-pb',
          hashAlg: 'sha3-512'
        }, (err) => {
          expect(err).to.exist()
          done()
        })
      })

      it('returns the cid', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha2-256'
        }, (err, cid) => {
          expect(err).to.not.exist()
          expect(cid).to.exist()
          expect(CID.isCID(cid)).to.equal(true)
          dagCBOR.util.cid(cborNode, (err, _cid) => {
            expect(err).to.not.exist()
            expect(cid.buffer).to.eql(_cid.buffer)
            done()
          })
        })
      })

      it.skip('pass the cid instead of format and hashAlg', (done) => {})

      // TODO it.skip('Promises support', (done) => {})
    })

    describe('.get', () => {
      let pbNode
      let cborNode

      let nodePb
      let nodeCbor
      let cidPb
      let cidCbor

      before((done) => {
        series([
          (cb) => {
            const someData = Buffer.from('some other data')

            pbNode = DAGNode.create(someData, (err, node) => {
              expect(err).to.not.exist()
              pbNode = node
              cb()
            })

            cborNode = {
              data: someData
            }
          },
          (cb) => {
            dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => {
              expect(err).to.not.exist()
              nodePb = node
              cb()
            })
          },
          (cb) => {
            dagPB.util.cid(nodePb, (err, cid) => {
              expect(err).to.not.exist()
              cidPb = cid
              cb()
            })
          },
          (cb) => {
            nodeCbor = {
              someData: 'I am inside a Cbor object',
              pb: { '/': cidPb.toBaseEncodedString() }
            }

            dagCBOR.util.cid(nodeCbor, (err, cid) => {
              expect(err).to.not.exist()
              cidCbor = cid
              cb()
            })
          }
        ], store)

        function store () {
          pull(
            pull.values([
              { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' },
              { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' }
            ]),
            pull.asyncMap((el, cb) => {
              ipfs.dag.put(el.node, {
                format: el.multicodec,
                hashAlg: el.hashAlg
              }, cb)
            }),
            pull.onEnd(done)
          )
        }
      })

      it('dag-pb node', (done) => {
        ipfs.dag.put(pbNode, {
          format: 'dag-pb',
          hashAlg: 'sha2-256'
        }, (err, cid) => {
          expect(err).to.not.exist()
          ipfs.dag.get(cid, (err, result) => {
            expect(err).to.not.exist()
            const node = result.value
            expect(pbNode.toJSON()).to.eql(node.toJSON())
            done()
          })
        })
      })

      it('dag-cbor node', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha2-256'
        }, (err, cid) => {
          expect(err).to.not.exist()
          ipfs.dag.get(cid, (err, result) => {
            expect(err).to.not.exist()

            const node = result.value
            expect(cborNode).to.eql(node)
            done()
          })
        })
      })

      describe('with path', () => {
        it('dag-pb get the node', (done) => {
          ipfs.dag.get(cidPb, '/', (err, result) => {
            expect(err).to.not.exist()

            const node = result.value

            dagPB.util.cid(node, (err, cid) => {
              expect(err).to.not.exist()
              expect(cid).to.eql(cidPb)
              done()
            })
          })
        })

        it('dag-pb local scope', function (done) {
          // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might
          // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is
          // done
          if (withGo) {
            this.skip()
          }
          ipfs.dag.get(cidPb, 'Data', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql(Buffer.from('I am inside a Protobuf'))
            done()
          })
        })

        it.skip('dag-pb one level', (done) => {})
        it.skip('dag-pb two levels', (done) => {})

        it('dag-cbor get the node', (done) => {
          ipfs.dag.get(cidCbor, '/', (err, result) => {
            expect(err).to.not.exist()

            const node = result.value

            dagCBOR.util.cid(node, (err, cid) => {
              expect(err).to.not.exist()
              expect(cid).to.eql(cidCbor)
              done()
            })
          })
        })

        it('dag-cbor local scope', (done) => {
          ipfs.dag.get(cidCbor, 'someData', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql('I am inside a Cbor object')
            done()
          })
        })

        it.skip('dag-cbor one level', (done) => {})
        it.skip('dag-cbor two levels', (done) => {})
        it.skip('from dag-pb to dag-cbor', (done) => {})

        it('from dag-cbor to dag-pb', function (done) {
          // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might
          // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is
          // done
          if (withGo) {
            this.skip()
          }
          ipfs.dag.get(cidCbor, 'pb/Data', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql(Buffer.from('I am inside a Protobuf'))
            done()
          })
        })

        it('CID String', (done) => {
          const cidCborStr = cidCbor.toBaseEncodedString()

          ipfs.dag.get(cidCborStr, (err, result) => {
            expect(err).to.not.exist()

            const node = result.value

            dagCBOR.util.cid(node, (err, cid) => {
              expect(err).to.not.exist()
              expect(cid).to.eql(cidCbor)
              done()
            })
          })
        })

        it('CID String + path', function (done) {
          // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might
          // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is
          // done
          if (withGo) {
            this.skip()
          }
          const cidCborStr = cidCbor.toBaseEncodedString()

          ipfs.dag.get(cidCborStr + '/pb/Data', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql(Buffer.from('I am inside a Protobuf'))
            done()
          })
        })
      })
    })

    describe.skip('.tree', function () {
      // TODO vmx 2018-02-22: Currently the tree API is not exposed in go-ipfs
      if (withGo) {
        this.skip()
      }

      let nodePb
      let nodeCbor
      let cidPb
      let cidCbor

      before((done) => {
        series([
          (cb) => {
            dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => {
              expect(err).to.not.exist()
              nodePb = node
              cb()
            })
          },
          (cb) => {
            dagPB.util.cid(nodePb, (err, cid) => {
              expect(err).to.not.exist()
              cidPb = cid
              cb()
            })
          },
          (cb) => {
            nodeCbor = {
              someData: 'I am inside a Cbor object',
              pb: { '/': cidPb.toBaseEncodedString() }
            }

            dagCBOR.util.cid(nodeCbor, (err, cid) => {
              expect(err).to.not.exist()
              cidCbor = cid
              cb()
            })
          }
        ], store)

        function store () {
          pull(
            pull.values([
              { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' },
              { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' }
            ]),
            pull.asyncMap((el, cb) => {
              ipfs.dag.put(el.node, {
                format: el.multicodec,
                hashAlg: el.hashAlg
              }, cb)
            }),
            pull.onEnd(done)
          )
        }
      })

      it('.tree with CID', (done) => {
        ipfs.dag.tree(cidCbor, (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([
            'pb',
            'someData'
          ])
          done()
        })
      })

      it('.tree with CID and path', (done) => {
        ipfs.dag.tree(cidCbor, 'someData', (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([])
          done()
        })
      })

      it('.tree with CID and path as String', (done) => {
        const cidCborStr = cidCbor.toBaseEncodedString()

        ipfs.dag.tree(cidCborStr + '/someData', (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([])
          done()
        })
      })

      it('.tree with CID recursive (accross different formats)', (done) => {
        ipfs.dag.tree(cidCbor, { recursive: true }, (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([
            'pb',
            'someData',
            'pb/Links',
            'pb/Data'
          ])
          done()
        })
      })

      it('.tree with CID and path recursive', (done) => {
        ipfs.dag.tree(cidCbor, 'pb', { recursive: true }, (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([
            'Links',
            'Data'
          ])
          done()
        })
      })
    })
  })
}
<commit_msg>Yet another fix<commit_after>/* eslint-env mocha */
/* eslint max-nested-callbacks: ["error", 8] */

'use strict'

const chai = require('chai')
const dirtyChai = require('dirty-chai')
const expect = chai.expect
chai.use(dirtyChai)
const series = require('async/series')
const pull = require('pull-stream')
const dagPB = require('ipld-dag-pb')
const DAGNode = dagPB.DAGNode
const dagCBOR = require('ipld-dag-cbor')
const CID = require('cids')

module.exports = (common) => {
  describe('.dag', () => {
    let ipfs
    let withGo

    before(function (done) {
      // CI takes longer to instantiate the daemon, so we need to increase the
      // timeout for the before step
      this.timeout(60 * 1000)

      common.setup((err, factory) => {
        expect(err).to.not.exist()
        factory.spawnNode((err, node) => {
          expect(err).to.not.exist()
          ipfs = node
          ipfs.id((err, id) => {
            expect(err).to.not.exist()
            withGo = id.agentVersion.startsWith('go-ipfs')
            done()
          })
        })
      })
    })

    after((done) => common.teardown(done))

    let pbNode
    let cborNode

    before((done) => {
      const someData = Buffer.from('some data')

      pbNode = DAGNode.create(someData, (err, node) => {
        expect(err).to.not.exist()
        pbNode = node
        done()
      })

      cborNode = {
        data: someData
      }
    })

    describe('.put', () => {
      it('dag-pb with default hash func (sha2-256)', (done) => {
        ipfs.dag.put(pbNode, {
          format: 'dag-pb',
          hashAlg: 'sha2-256'
        }, done)
      })

      it('dag-pb with custom hash func (sha3-512)', (done) => {
        ipfs.dag.put(pbNode, {
          format: 'dag-pb',
          hashAlg: 'sha3-512'
        }, done)
      })

      // This works because dag-cbor will just treat pbNode as a regular object
      it.skip('dag-pb node with wrong multicodec', (done) => {
        ipfs.dag.put(pbNode, 'dag-cbor', 'sha3-512', (err) => {
          expect(err).to.exist()
          done()
        })
      })

      it('dag-cbor with default hash func (sha2-256)', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha2-256'
        }, done)
      })

      it('dag-cbor with custom hash func (sha3-512)', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha3-512'
        }, done)
      })

      it('dag-cbor node with wrong multicodec', function (done) {
        // This works in go-ipfs because dag-pb will serialize any object. If
        // the object has neither a `data` nor `links` field it's serialized
        // as an empty object
        if (withGo) {
          this.skip()
        }
        ipfs.dag.put(cborNode, {
          format: 'dag-pb',
          hashAlg: 'sha3-512'
        }, (err) => {
          expect(err).to.exist()
          done()
        })
      })

      it('returns the cid', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha2-256'
        }, (err, cid) => {
          expect(err).to.not.exist()
          expect(cid).to.exist()
          expect(CID.isCID(cid)).to.equal(true)
          dagCBOR.util.cid(cborNode, (err, _cid) => {
            expect(err).to.not.exist()
            expect(cid.buffer).to.eql(_cid.buffer)
            done()
          })
        })
      })

      it.skip('pass the cid instead of format and hashAlg', (done) => {})

      // TODO it.skip('Promises support', (done) => {})
    })

    describe('.get', () => {
      let pbNode
      let cborNode

      let nodePb
      let nodeCbor
      let cidPb
      let cidCbor

      before((done) => {
        series([
          (cb) => {
            const someData = Buffer.from('some other data')

            pbNode = DAGNode.create(someData, (err, node) => {
              expect(err).to.not.exist()
              pbNode = node
              cb()
            })

            cborNode = {
              data: someData
            }
          },
          (cb) => {
            dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => {
              expect(err).to.not.exist()
              nodePb = node
              cb()
            })
          },
          (cb) => {
            dagPB.util.cid(nodePb, (err, cid) => {
              expect(err).to.not.exist()
              cidPb = cid
              cb()
            })
          },
          (cb) => {
            nodeCbor = {
              someData: 'I am inside a Cbor object',
              pb: { '/': cidPb.toBaseEncodedString() }
            }

            dagCBOR.util.cid(nodeCbor, (err, cid) => {
              expect(err).to.not.exist()
              cidCbor = cid
              cb()
            })
          }
        ], store)

        function store () {
          pull(
            pull.values([
              { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' },
              { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' }
            ]),
            pull.asyncMap((el, cb) => {
              ipfs.dag.put(el.node, {
                format: el.multicodec,
                hashAlg: el.hashAlg
              }, cb)
            }),
            pull.onEnd(done)
          )
        }
      })

      it('dag-pb node', (done) => {
        ipfs.dag.put(pbNode, {
          format: 'dag-pb',
          hashAlg: 'sha2-256'
        }, (err, cid) => {
          expect(err).to.not.exist()
          ipfs.dag.get(cid, (err, result) => {
            expect(err).to.not.exist()
            const node = result.value
            expect(pbNode.toJSON()).to.eql(node.toJSON())
            done()
          })
        })
      })

      it('dag-cbor node', (done) => {
        ipfs.dag.put(cborNode, {
          format: 'dag-cbor',
          hashAlg: 'sha2-256'
        }, (err, cid) => {
          expect(err).to.not.exist()
          ipfs.dag.get(cid, (err, result) => {
            expect(err).to.not.exist()

            const node = result.value
            expect(cborNode).to.eql(node)
            done()
          })
        })
      })

      describe('with path', () => {
        it('dag-pb get the node', (done) => {
          ipfs.dag.get(cidPb, '/', (err, result) => {
            expect(err).to.not.exist()

            const node = result.value

            dagPB.util.cid(node, (err, cid) => {
              expect(err).to.not.exist()
              expect(cid).to.eql(cidPb)
              done()
            })
          })
        })

        it('dag-pb local scope', function (done) {
          // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might
          // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is
          // done
          if (withGo) {
            this.skip()
          }
          ipfs.dag.get(cidPb, 'Data', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql(Buffer.from('I am inside a Protobuf'))
            done()
          })
        })

        it.skip('dag-pb one level', (done) => {})
        it.skip('dag-pb two levels', (done) => {})

        it('dag-cbor get the node', (done) => {
          ipfs.dag.get(cidCbor, '/', (err, result) => {
            expect(err).to.not.exist()

            const node = result.value

            dagCBOR.util.cid(node, (err, cid) => {
              expect(err).to.not.exist()
              expect(cid).to.eql(cidCbor)
              done()
            })
          })
        })

        it('dag-cbor local scope', (done) => {
          ipfs.dag.get(cidCbor, 'someData', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql('I am inside a Cbor object')
            done()
          })
        })

        it.skip('dag-cbor one level', (done) => {})
        it.skip('dag-cbor two levels', (done) => {})
        it.skip('from dag-pb to dag-cbor', (done) => {})

        it('from dag-cbor to dag-pb', function (done) {
          // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might
          // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is
          // done
          if (withGo) {
            this.skip()
          }
          ipfs.dag.get(cidCbor, 'pb/Data', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql(Buffer.from('I am inside a Protobuf'))
            done()
          })
        })

        it('CID String', (done) => {
          const cidCborStr = cidCbor.toBaseEncodedString()

          ipfs.dag.get(cidCborStr, (err, result) => {
            expect(err).to.not.exist()

            const node = result.value

            dagCBOR.util.cid(node, (err, cid) => {
              expect(err).to.not.exist()
              expect(cid).to.eql(cidCbor)
              done()
            })
          })
        })

        it('CID String + path', function (done) {
          // TODO vmx 2018-02-22: Currently not supported in go-ipfs, it might
          // be possible once https://github.com/ipfs/go-ipfs/issues/4728 is
          // done
          if (withGo) {
            this.skip()
          }
          const cidCborStr = cidCbor.toBaseEncodedString()

          ipfs.dag.get(cidCborStr + '/pb/Data', (err, result) => {
            expect(err).to.not.exist()
            expect(result.value).to.eql(Buffer.from('I am inside a Protobuf'))
            done()
          })
        })
      })
    })

    describe('.tree', function () {
      let nodePb
      let nodeCbor
      let cidPb
      let cidCbor

      before(function (done) {
        // TODO vmx 2018-02-22: Currently the tree API is not exposed in go-ipfs
        if (withGo) {
          this.skip()
        }
        series([
          (cb) => {
            dagPB.DAGNode.create(Buffer.from('I am inside a Protobuf'), (err, node) => {
              expect(err).to.not.exist()
              nodePb = node
              cb()
            })
          },
          (cb) => {
            dagPB.util.cid(nodePb, (err, cid) => {
              expect(err).to.not.exist()
              cidPb = cid
              cb()
            })
          },
          (cb) => {
            nodeCbor = {
              someData: 'I am inside a Cbor object',
              pb: { '/': cidPb.toBaseEncodedString() }
            }

            dagCBOR.util.cid(nodeCbor, (err, cid) => {
              expect(err).to.not.exist()
              cidCbor = cid
              cb()
            })
          }
        ], store)

        function store () {
          pull(
            pull.values([
              { node: nodePb, multicodec: 'dag-pb', hashAlg: 'sha2-256' },
              { node: nodeCbor, multicodec: 'dag-cbor', hashAlg: 'sha2-256' }
            ]),
            pull.asyncMap((el, cb) => {
              ipfs.dag.put(el.node, {
                format: el.multicodec,
                hashAlg: el.hashAlg
              }, cb)
            }),
            pull.onEnd(done)
          )
        }
      })

      it('.tree with CID', (done) => {
        ipfs.dag.tree(cidCbor, (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([
            'pb',
            'someData'
          ])
          done()
        })
      })

      it('.tree with CID and path', (done) => {
        ipfs.dag.tree(cidCbor, 'someData', (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([])
          done()
        })
      })

      it('.tree with CID and path as String', (done) => {
        const cidCborStr = cidCbor.toBaseEncodedString()

        ipfs.dag.tree(cidCborStr + '/someData', (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([])
          done()
        })
      })

      it('.tree with CID recursive (accross different formats)', (done) => {
        ipfs.dag.tree(cidCbor, { recursive: true }, (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([
            'pb',
            'someData',
            'pb/Links',
            'pb/Data'
          ])
          done()
        })
      })

      it('.tree with CID and path recursive', (done) => {
        ipfs.dag.tree(cidCbor, 'pb', { recursive: true }, (err, paths) => {
          expect(err).to.not.exist()
          expect(paths).to.eql([
            'Links',
            'Data'
          ])
          done()
        })
      })
    })
  })
}
<|endoftext|>


<commit_before>var streams = [ { title: 'WMBR',
    description: 'Music and public affairs out of MIT in Cambridge.',
    url: 'http://wmbr.org/',
    donateurl: 'https://wmbr.org/fundraising.php',
    streamurl: 'http://headphones.mit.edu:8000/hi' },
  { title: 'Berlin Community Radio',
    description: 'An electronic music internet station.',
    url: 'http://www.berlincommunityradio.com',
    donateurl: 'http://www.berlincommunityradio.com/donate-berlin-community-radio',
    streamurl: 'http://berlincommunityradio.out.airtime.pro:8000/berlincommunityradio_a' },
  { title: 'WFMU',
    description: 'New York\'s weirdest and best freeform station.',
    url: 'https://wfmu.org/',
    donateurl: 'https://wfmu.org/marathon-2016/',
    streamurl: 'http://stream0.wfmu.org/freeform-128k' },
  { title: 'WWOZ',
    description: 'New Orleans public radio.',
    url: 'http://www.wwoz.org/',
    donateurl: 'http://www.wwoz.org/donate.php',
    streamurl: 'http://wwoz-sc.streamguys.com:80/wwoz-hi.mp3' },
  { title: 'Resonance FM',
    description: 'London\'s groundbreaking art radio station.',
    url: 'https://resonancefm.com/',
    donateurl: 'https://resonancefm.com/donate',
    streamurl: 'http://54.77.136.103:8000/resonance' },
  { title: 'WERU',
    description: 'Midcoast Maine\'s voice of many voices.',
    url: 'https://weru.org/',
    donateurl: 'https://weru.org/donate',
    streamurl: 'http://stream.weru.org/weru-high.mp3' },
  { title: 'WXYC',
    description: 'Student radio from Chapel Hill, North Carolina.',
    url: 'http://www.wxyc.org/',
    donateurl: 'http://www.wxyc.org/merch',
    streamurl: 'http://audio-mp3.ibiblio.org:8000/wxyc.mp3' },
  { title: 'BBC Radio 3',
    description: 'New and classical music.',
    url: 'http://www.bbc.co.uk/radio3',
    donateurl: null,
    streamurl: 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio3_mf_p?s=1454876177&e=1454890577&h=e1f6b6a5fcb3ca60b131bbdfcb02f980' },
  { title: 'BBC Radio 4',
    description: 'Documentaries and spoken word programs.',
    url: 'http://www.bbc.co.uk/radio4',
    donateurl: null,
    streamurl: 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio4fm_mf_q' },
  { title: 'BBC Radio 6',
    description: 'BBC\'s alternative music station.',
    url: 'http://www.bbc.co.uk/6music',
    donateurl: null,
    streamurl: 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_6music_mf_p' },
  { title: 'WMPG',
    description: 'Portland, Maine community radio.',
    url: 'http://www.wmpg.org/',
    donateurl: 'http://www.wmpg.org/support-wmpg/listener-donations/',
    streamurl: 'http://quarrel.str3am.com:7270/;stream.mp3' },
  { title: 'Terry Radio',
    description: 'Electronic and experimental sounds.',
    url: 'http://www.terryradio.biz/',
    donateurl: null,
    streamurl: 'http://terryradio.dtdns.net:8000/terry1' },
  { title: 'WUMB',
    description: 'Folk and more out of UMass Boston.',
    url: 'http://www.wumb.org/home/index.php',
    donateurl: 'http://www.wumb.org/members/donate/',
    streamurl: 'http://wumb.streamguys1.com/wumb919fast' },
  { title: 'WNUR',
    description: 'Chicago\'s sound experiment.',
    url: 'https://www.wnur.org/',
    donateurl: 'http://wewill.northwestern.edu/supportwnur',
    streamurl: 'http://sc1.spacialnet.com:32660/;wnur.mp3' },
  { title: 'Wave Farm Radio',
    description: 'Transmission art out of Hudson, New York.',
    url: 'https://wavefarm.org/',
    donateurl: 'https://wavefarm.org/wgxc/donate',
    streamurl: 'http://comm.free103point9.org:8000/transmissionarts.mp3' },
  { title: 'WKCR',
    description: 'The Original FM specializing in jazz, classical, and roots music.',
    url: 'https://www.wkcr.org',
    donateurl: 'https://giving.columbia.edu/giveonline/?schoolstyle=411',
    streamurl: 'http://wkcr.streamguys1.com:80/live' },
  { title: 'Bondfire Radio',
    description: 'Brooklyn\'s real radio for real people.',
    url: 'http://bondfireradio.com/',
    donateurl: 'https://www.patreon.com/bondfireradio',
    streamurl: 'https://streaming.radio.co/sd2fdc24b0/listen' },
  { title: 'RAO (Radio Africa Online)',
    description: 'Spinning African and Caribbean music online since 2002.',
    url: 'http://soukous.org/',
    donateurl: 'http://soukous.org/donate.htm',
    streamurl: 'http://nyc04.egihosting.com:4658/;'
  }
]
<commit_msg>add more streams<commit_after>var streams = [
  { title: 'WMBR',
    description: 'Music and public affairs out of MIT in Cambridge.',
    url: 'http://wmbr.org/',
    donateurl: 'https://wmbr.org/fundraising.php',
    streamurl: 'http://headphones.mit.edu:8000/hi' },

  { title: 'Berlin Community Radio',
    description: 'An electronic music internet station.',
    url: 'http://www.berlincommunityradio.com',
    donateurl: 'http://www.berlincommunityradio.com/donate-berlin-community-radio',
    streamurl: 'http://berlincommunityradio.out.airtime.pro:8000/berlincommunityradio_a' },

  { title: 'WFMU',
    description: 'New York\'s weirdest and best freeform station.',
    url: 'https://wfmu.org/',
    donateurl: 'https://wfmu.org/marathon-2016/',
    streamurl: 'http://stream0.wfmu.org/freeform-128k' },

  { title: 'WWOZ',
    description: 'New Orleans public radio.',
    url: 'http://www.wwoz.org/',
    donateurl: 'http://www.wwoz.org/donate.php',
    streamurl: 'http://wwoz-sc.streamguys.com:80/wwoz-hi.mp3' },

  { title: 'Resonance FM',
    description: 'London\'s groundbreaking art radio station.',
    url: 'https://resonancefm.com/',
    donateurl: 'https://resonancefm.com/donate',
    streamurl: 'http://54.77.136.103:8000/resonance' },

  { title: 'WERU',
    description: 'Midcoast Maine\'s voice of many voices.',
    url: 'https://weru.org/',
    donateurl: 'https://weru.org/donate',
    streamurl: 'http://stream.weru.org/weru-high.mp3' },

  { title: 'WXYC',
    description: 'Student radio from Chapel Hill, North Carolina.',
    url: 'http://www.wxyc.org/',
    donateurl: 'http://www.wxyc.org/merch',
    streamurl: 'http://audio-mp3.ibiblio.org:8000/wxyc.mp3' },

  { title: 'BBC Radio 3',
    description: 'New and classical music.',
    url: 'http://www.bbc.co.uk/radio3',
    donateurl: null,
    streamurl: 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio3_mf_p'},

  { title: 'BBC Radio 4',
    description: 'Documentaries and spoken word programs.',
    url: 'http://www.bbc.co.uk/radio4',
    donateurl: null,
    streamurl: 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio4fm_mf_q' },

  { title: 'BBC Radio 6',
    description: 'BBC\'s alternative music station.',
    url: 'http://www.bbc.co.uk/6music',
    donateurl: null,
    streamurl: 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_6music_mf_p' },

  { title: 'Terry Radio',
    description: 'Electronic and experimental sounds.',
    url: 'http://www.terryradio.biz/',
    donateurl: null,
    streamurl: 'http://terryradio.dtdns.net:8000/terry1' },

  { title: 'WUMB',
    description: 'Folk and more out of UMass Boston.',
    url: 'http://www.wumb.org/home/index.php',
    donateurl: 'http://www.wumb.org/members/donate/',
    streamurl: 'http://wumb.streamguys1.com/wumb919fast' },

  { title: 'WNUR',
    description: 'Chicago\'s sound experiment.',
    url: 'https://www.wnur.org/',
    donateurl: 'http://wewill.northwestern.edu/supportwnur',
    streamurl: 'http://sc1.spacialnet.com:32660/;wnur.mp3' },

  { title: 'Wave Farm Radio',
    description: 'Transmission art out of Hudson, New York.',
    url: 'https://wavefarm.org/',
    donateurl: 'https://wavefarm.org/wgxc/donate',
    streamurl: 'http://comm.free103point9.org:8000/transmissionarts.mp3' },

  { title: 'WKCR',
    description: 'The Original FM specializing in jazz, classical, and roots music.',
    url: 'https://www.wkcr.org',
    donateurl: 'https://giving.columbia.edu/giveonline/?schoolstyle=411',
    streamurl: 'http://wkcr.streamguys1.com:80/live' },

  { title: 'Bondfire Radio',
    description: 'Brooklyn\'s real radio for real people.',
    url: 'http://bondfireradio.com/',
    donateurl: 'https://www.patreon.com/bondfireradio',
    streamurl: 'https://streaming.radio.co/sd2fdc24b0/listen' },

  { title: 'KALW',
    description: 'San Francisco\'s local public radio.',
    url: 'http://kalw.org/',
    donateurl: 'https://donate.nprstations.org/kalw',
    streamurl: 'http://live.str3am.com:2430/' },

  { title: 'FIP',
    description: 'Radio musicale eclectique',
    url: 'http://www.fipradio.fr/',
    donateurl: 'https://donate.nprstations.org/kalw',
    streamurl: 'http://audio.scdn.arkena.com/11016/fip-midfi128.mp3' },

  { title: 'Sveriges Radio P2',
    description: 'Classical and jazz from Sweden\'s second radio channel.',
    url: 'https://sverigesradio.se/p2',
    donateurl: null,
    streamurl: 'https://http-live.sr.se/p2musik-mp3-192' },

  { title: 'KEXP',
    description: 'Seattle\'s alternative and indie rock station.',
    url: 'kexp.org',
    donateurl: 'https://www.kexp.org/donate',
    streamurl: 'http://live-aacplus-64.kexp.org/kexp64.aac' },

  { title: '88Nine Radio Milwaukee',
    description: 'Where Milwaukee discovers new music',
    url: 'http://radiomilwaukee.org',
    donateurl: 'http://radiomilwaukee.org/support-us/',
    streamurl: 'http://radiomilwaukee.streamguys.net/live' },

  { title: 'RAO (Radio Africa Online)',
    description: 'Spinning African and Caribbean music online since 2002.',
    url: 'http://soukous.org/',
    donateurl: 'http://soukous.org/donate.htm',
    streamurl: 'http://nyc04.egihosting.com:4658/;'
  }
]
<|endoftext|>


<commit_before>define(['xaml!js/svg/SvgDescriptor', "js/svg/SvgElement", 'js/core/Base'], function (SvgDescriptor, SvgElement, Base) {

    var Svg = SvgDescriptor.inherit("js.svg.Svg", {

        defaults: {
            tagName: "svg",
            viewBox: "0 0 100 100",

            hidden: null
        },

        $classAttributes: ["defs", "hidden"],

        ctor: function () {
            this.callBase();
            this.fontManager = new Svg.FontManager(this);
            this.$svgRoot = this;

            this.setViewBox.apply(this, this.$.viewBox.split(" "));
        },

        setViewBox: function (x, y, width, height) {

            this.$viewBoxX = x;
            this.$viewBoxY = y;
            this.$viewBoxWidth = width;
            this.$viewBoxHeight = height;

            this.set("viewBox", [x, y, width, height].join(" "));
        },

        localToGlobalFactor: function () {
            return {
                x: this.$.width / this.$viewBoxWidth,
                y: this.$.height / this.$viewBoxHeight
            };
        },

        globalToLocalFactor: function () {
            return {
                x: this.$viewBoxWidth / this.$.width,
                y: this.$viewBoxHeight / this.$.height
            };
        },

        save: function () {

            var window = this.$stage.$window;

            // dirty hack to get dom as string
            var div = this.$stage.$document.createElement("div");
            div.appendChild(this.$el.cloneNode(true));
            // dirty hack2
            var svgContent = div.innerHTML.replace(/^\<svg/, "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' ");
            svgContent = svgContent.replace(/(href=['"])\/{2}/g, "$1http://");

            var svg = new Blob([svgContent], {
                type: "image/svg+xml;charset=utf-8"
            });

            var domUrl = window.URL || window.webkitURL || window;
            return domUrl.createObjectURL(svg);

        }

    });

    Svg.FontManager = Base.inherit("js.svg.Svg.FontManager", {

        ctor: function (svg) {
            this.$svg = svg;
            this.$fontCache = {};

            this.callBase();
        },

        loadExternalFont: function (fontFamily, src, callback) {
            var svg = this.$svg;

            var cache = this.$fontCache[fontFamily];

            if (!cache) {

                var text = svg.$stage.$document.createElementNS(SvgElement.SVG_NAMESPACE, "text");
                text.textContent = "SvgFontMeasurer";
                var hidden = svg.$.hidden.$el;

                hidden.appendChild(text);

                if (/\.woff|\.eot/.test(src)) {

                    var document = svg.$stage.$document,
                        body = document.getElementsByTagName("body")[0],
                        head = document.getElementsByTagName("head")[0],
                        style = document.createElement("style");

                    style.setAttribute("type", "text/css");

                    var srcAttribute = "";
                    if (src.indexOf(".woff") > -1) {
                        srcAttribute = "url('" + src + "') format('woff');";
                    } else {
                        srcAttribute = "url('" + src + "') ;";
                    }

                    /*Paul Irish's smiley method for font loading -
                     http://paulirish.com/2009/bulletproof-font-face-implementation-syntax/ */
                    style.innerHTML = "@font-face{\n" +
                        "font-family: '" + fontFamily + "';" +
//                        "src: url('" + src.replace(".woff",".eot") + "'); " +
//                        "src: url('" + src +);" +
                        "src: " + srcAttribute +
//                        "url('" + fontPath + ".ttf') format('truetype');" +
                        "}\n";

                    head.appendChild(style);

                } else if (src.indexOf(".svg") > -1) {
                    var font = svg.$templates["external-font"].createInstance({
                        $fontFamily: fontFamily,
                        $src: src
                    });
                    svg.$.defs.addChild(font);
                }

                cache = this.$fontCache[fontFamily] = {
//                    font: font,
                    loaded: false,
                    callbacks: [callback]
                };

                var maxChecks = 500,
                    originalBox,
                    current;

                setTimeout(function () {
                    text.setAttribute("font-family", "__ABCDE__");  // set to undefined font and measure
                    try {
                        originalBox = text.getBBox();
                    } catch (e) {
                    }

                    text.setAttribute("font-family", fontFamily); // set to loading font / first undefined
                    setTimeout(checkFontLoaded, 0);

                    function checkFontLoaded() {
                        try {
                            current = text.getBBox();
                        } catch (e) {
                        }

                        // compare bounding boxes of two
                        if (!current || !originalBox || (maxChecks > 0 && current.x === originalBox.x && current.y === originalBox.y &&
                            current.width === originalBox.width && current.height === originalBox.height)) {
                            maxChecks--;

                            // same check later
                            setTimeout(checkFontLoaded, 100);
                        } else {
                            cache.loaded = true;
                            hidden.removeChild(text);

                            setTimeout(function () {
                                for (var i = 0; i < cache.callbacks.length; i++) {
                                    try {
                                        cache.callbacks[i] && cache.callbacks[i]();
                                    } catch (e) {
                                        // invoke callbacks
                                    }
                                }
                            }, 200);

                        }
                    }
                }, 100);

            } else {

                if (cache.loaded) {
                    callback && callback();
                }
                else {
                    cache.callbacks.push(callback);
                }

            }

        }

    });

    return Svg;
});<commit_msg>fixed caching of loaded fonts<commit_after>define(['xaml!js/svg/SvgDescriptor', "js/svg/SvgElement", 'js/core/Base'], function (SvgDescriptor, SvgElement, Base) {

    var Svg = SvgDescriptor.inherit("js.svg.Svg", {

        defaults: {
            tagName: "svg",
            viewBox: "0 0 100 100",

            hidden: null
        },

        $classAttributes: ["defs", "hidden"],

        ctor: function () {
            this.callBase();
            this.fontManager = new Svg.FontManager(this);
            this.$svgRoot = this;

            this.setViewBox.apply(this, this.$.viewBox.split(" "));
        },

        setViewBox: function (x, y, width, height) {

            this.$viewBoxX = x;
            this.$viewBoxY = y;
            this.$viewBoxWidth = width;
            this.$viewBoxHeight = height;

            this.set("viewBox", [x, y, width, height].join(" "));
        },

        localToGlobalFactor: function () {
            return {
                x: this.$.width / this.$viewBoxWidth,
                y: this.$.height / this.$viewBoxHeight
            };
        },

        globalToLocalFactor: function () {
            return {
                x: this.$viewBoxWidth / this.$.width,
                y: this.$viewBoxHeight / this.$.height
            };
        },

        save: function () {

            var window = this.$stage.$window;

            // dirty hack to get dom as string
            var div = this.$stage.$document.createElement("div");
            div.appendChild(this.$el.cloneNode(true));
            // dirty hack2
            var svgContent = div.innerHTML.replace(/^\<svg/, "<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' ");
            svgContent = svgContent.replace(/(href=['"])\/{2}/g, "$1http://");

            var svg = new Blob([svgContent], {
                type: "image/svg+xml;charset=utf-8"
            });

            var domUrl = window.URL || window.webkitURL || window;
            return domUrl.createObjectURL(svg);

        }

    });

    var GlobalFontCache = {};

    Svg.FontManager = Base.inherit("js.svg.Svg.FontManager", {

        ctor: function (svg) {
            this.$svg = svg;
            this.callBase();
        },

        loadExternalFont: function (fontFamily, src, callback) {
            var svg = this.$svg;

            var font = GlobalFontCache[fontFamily];
            var isSvgFont = src.indexOf(".svg") > -1;

            if (!font) {

                var text = svg.$stage.$document.createElementNS(SvgElement.SVG_NAMESPACE, "text");
                text.textContent = "SvgFontMeasurer";
                var hidden = svg.$.hidden.$el;

                hidden.appendChild(text);

                if (/\.woff|\.eot/.test(src)) {

                    var document = svg.$stage.$document,
                        body = document.getElementsByTagName("body")[0],
                        head = document.getElementsByTagName("head")[0],
                        style = document.createElement("style");

                    style.setAttribute("type", "text/css");

                    var srcAttribute = "";
                    if (src.indexOf(".woff") > -1) {
                        srcAttribute = "url('" + src + "') format('woff');";
                    } else {
                        srcAttribute = "url('" + src + "') ;";
                    }

                    /*Paul Irish's smiley method for font loading -
                     http://paulirish.com/2009/bulletproof-font-face-implementation-syntax/ */
                    style.innerHTML = "@font-face{\n" +
                        "font-family: '" + fontFamily + "';" +
//                        "src: url('" + src.replace(".woff",".eot") + "'); " +
//                        "src: url('" + src +);" +
                        "src: " + srcAttribute +
//                        "url('" + fontPath + ".ttf') format('truetype');" +
                        "}\n";

                    head.appendChild(style);

                } else if (src.indexOf(".svg") > -1) {
                    var fontElement = svg.$templates["external-font"].createInstance({
                        $fontFamily: fontFamily,
                        $src: src
                    });
                    svg.$.defs.addChild(fontElement);
                }

                font = GlobalFontCache[fontFamily] = {
//                    font: font,
                    loaded: false,
                    callbacks: [callback]
                };

                var maxChecks = 500,
                    originalBox,
                    current;

                setTimeout(function () {
                    text.setAttribute("font-family", "__ABCDE__");  // set to undefined font and measure
                    try {
                        originalBox = text.getBBox();
                    } catch (e) {
                    }

                    text.setAttribute("font-family", fontFamily); // set to loading font / first undefined
                    setTimeout(checkFontLoaded, 0);

                    function checkFontLoaded() {
                        try {
                            current = text.getBBox();
                        } catch (e) {
                        }

                        // compare bounding boxes of two
                        if (!current || !originalBox || (maxChecks > 0 && current.x === originalBox.x && current.y === originalBox.y &&
                            current.width === originalBox.width && current.height === originalBox.height)) {
                            maxChecks--;

                            // same check later
                            setTimeout(checkFontLoaded, 100);
                        } else {
                            font.loaded = true;
                            hidden.removeChild(text);

                            setTimeout(function () {
                                for (var i = 0; i < font.callbacks.length; i++) {
                                    try {
                                        font.callbacks[i] && font.callbacks[i]();
                                    } catch (e) {
                                        // invoke callbacks
                                    }
                                }
                            }, 200);

                        }
                    }
                }, 100);

            } else {

                if (font.loaded) {

                    if (isSvgFont) {
                        svg.$.defs.addChild(svg.$templates["external-font"].createInstance({
                            $fontFamily: fontFamily,
                            $src: src
                        }));

                    }
                    callback && callback();
                }
                else {
                    font.callbacks.push(callback);
                }

            }

        }

    });

    return Svg;
});<|endoftext|>


<commit_before>var heights = [];

// deep magic here
function iterateTerrain(segments, iterations) {
	// how much outside the range of [left, right] we allow at each step.
	// allows slight hills and dips to occur
	var terrainDeviation = 0.1;

	for (var i = 0; i < iterations; ++i) {
		// increment by 2 each time, because we add another element each iteration
		// also, skip the last iteration, because otherwise j+1 is undefined
		for (var j = 0; j < segments.length - 1; j += 2) {
			var l = segments[j];
			var r = segments[j + 1];
			// math black magic: take a random percentage (0% to 100%) of the difference in height, and then add that on top of the initial height
			var mid = ((r - l) * randBetween(0 - terrainDeviation, 1 + terrainDeviation)) + l;
			// insert into the middle of the array
			segments.splice(j + 1, 0, mid);
		}
	}
}

function makeTerrain() {
	// array of segment heights
	var segments = [0, 0.6, -0.2, 1, 0.5, 0];

	// note that by having iterateTerrain as a function, we can do several passes. e.g., several separate islands
	iterateTerrain(segments, terrainIterations - 2);

	// put a few cliffs in
	for (var i = 1; i < segments.length - 2; ++i) {
		if (Math.random() * segments.length < 8) {
			segments[i] += Math.random() - 0.5;
		}
	}

	iterateTerrain(segments, 2);

	islandTiles = segments.length - 1;
	islandWidth = islandTiles * tileSize;

	var width = islandWidth / (segments.length - 1);

	segments = segments.map(function(elev) {
		var height = (1 - elev) * seaLevel;
		// round it to the step size of our tiles
		height = Math.round(height / stepSize) * stepSize;
		return height;
	});

	function calcSteps(i) {
		// do left - right because negative numbers are UP
		return (segments[i] - segments[i + 1]) / stepSize;
	}

	for (var i = 0; i < segments.length - 1; ++i) {
		var x = i * width;
		var left = segments[i];
		var right = segments[i + 1];

		var steps = calcSteps(i);
		var flip = false;
		if (steps < 0) {
			flip = true;
			steps = -steps;
		}
		if (steps > 4) {
			steps = 4;
		}

		var tileHeight = Math.min(left, right);

		if (steps == 0) {
			if (i > 0 && calcSteps(i - 1) < -4) {
				tileHeight -= stepSize;
				steps = 1;
				flip = true;
			} else if (calcSteps(i + 1) > 4) {
				tileHeight -= stepSize;
				steps = 1;
			}
		}

		var frameName;
		switch (steps) {
			case 0:
				frameName = 'full';
				break;
			case 1:
			case 2:
			case 3:
			case 4:
				frameName = 'slope' + steps;
				break;
		}
		var randVariation = Math.floor(Math.random() * terrainVariations);
		var frame = terrainFrames[frameName][randVariation];

		var tile = islandGroup.create(x + tileSize / 2, tileHeight + tileSize / 2, 'terrain');
		tile.anchor.setTo(0.5, 0.5);
		tile.body.immovable = true;
		tile.frame = frame;
		if (flip) {
			tile.scale.x = -1;
		}

		var heightObj = {};
		if (flip) {
			heightObj.left = tileHeight;
			heightObj.right = tileHeight + stepSize * steps;
		} else {
			heightObj.left = tileHeight + stepSize * steps;
			heightObj.right = tileHeight;
		}
		heights.push(heightObj);

		while (tileHeight < game.height) {
			tileHeight += tileSize;
			randVariation = Math.floor(Math.random() * terrainVariations);
			// don't need physics on these (hopefully), so don't add them in the islandGroup
			islandNoPhysGroup.create(x, tileHeight, 'terrain').frame = terrainFrames['full'][randVariation];
		}
	}

	heights.forEach(function(height, i) {
		var x = i * width;
		lines.push(new Phaser.Line(x, height.left, x + width, height.right));
	});
}

function heightAt(x) {
	// split x into tile (integer), and sub-tile position
	var temp = x / tileSize;
	var tile = Math.floor(temp);
	var subTile = temp - tile;

	var tileHeight = heights[tile].left;
	var diff = heights[tile + 1].right - tileHeight;

	return tileHeight + diff * subTile;
}
<commit_msg>Some fixes to the new height calculation<commit_after>var heights = [];

// deep magic here
function iterateTerrain(segments, iterations) {
	// how much outside the range of [left, right] we allow at each step.
	// allows slight hills and dips to occur
	var terrainDeviation = 0.1;

	for (var i = 0; i < iterations; ++i) {
		// increment by 2 each time, because we add another element each iteration
		// also, skip the last iteration, because otherwise j+1 is undefined
		for (var j = 0; j < segments.length - 1; j += 2) {
			var l = segments[j];
			var r = segments[j + 1];
			// math black magic: take a random percentage (0% to 100%) of the difference in height, and then add that on top of the initial height
			var mid = ((r - l) * randBetween(0 - terrainDeviation, 1 + terrainDeviation)) + l;
			// insert into the middle of the array
			segments.splice(j + 1, 0, mid);
		}
	}
}

function makeTerrain() {
	// array of segment heights
	var segments = [0, 0.6, -0.2, 1, 0.5, 0];

	// note that by having iterateTerrain as a function, we can do several passes. e.g., several separate islands
	iterateTerrain(segments, terrainIterations - 2);

	// put a few cliffs in
	for (var i = 1; i < segments.length - 2; ++i) {
		if (Math.random() * segments.length < 8) {
			segments[i] += Math.random() - 0.5;
		}
	}

	iterateTerrain(segments, 2);

	islandTiles = segments.length - 1;
	islandWidth = islandTiles * tileSize;

	var width = islandWidth / (segments.length - 1);

	segments = segments.map(function(elev) {
		var height = (1 - elev) * seaLevel;
		// round it to the step size of our tiles
		height = Math.round(height / stepSize) * stepSize;
		return height;
	});

	function calcSteps(i) {
		// do left - right because negative numbers are UP
		return (segments[i] - segments[i + 1]) / stepSize;
	}

	for (var i = 0; i < segments.length - 1; ++i) {
		var x = i * width;
		var left = segments[i];
		var right = segments[i + 1];

		var steps = calcSteps(i);
		var flip = false;
		if (steps < 0) {
			flip = true;
			steps = -steps;
		}
		if (steps > 4) {
			steps = 4;
		}

		var tileHeight = Math.min(left, right);

		if (steps == 0) {
			if (i > 0 && calcSteps(i - 1) < -4) {
				tileHeight -= stepSize;
				steps = 1;
				flip = true;
			} else if (calcSteps(i + 1) > 4) {
				tileHeight -= stepSize;
				steps = 1;
			}
		}

		var frameName;
		switch (steps) {
			case 0:
				frameName = 'full';
				break;
			case 1:
			case 2:
			case 3:
			case 4:
				frameName = 'slope' + steps;
				break;
		}
		var randVariation = Math.floor(Math.random() * terrainVariations);
		var frame = terrainFrames[frameName][randVariation];

		var tile = islandGroup.create(x + tileSize / 2, tileHeight + tileSize / 2, 'terrain');
		tile.anchor.setTo(0.5, 0.5);
		tile.body.immovable = true;
		tile.frame = frame;
		if (flip) {
			tile.scale.x = -1;
		}

		var heightObj = {};
		if (flip) {
			heightObj.left = tileHeight;
			heightObj.right = tileHeight + stepSize * steps;
		} else {
			heightObj.left = tileHeight + stepSize * steps;
			heightObj.right = tileHeight;
		}
		heights.push(heightObj);

		while (tileHeight < game.height) {
			tileHeight += tileSize;
			randVariation = Math.floor(Math.random() * terrainVariations);
			// don't need physics on these (hopefully), so don't add them in the islandGroup
			islandNoPhysGroup.create(x, tileHeight, 'terrain').frame = terrainFrames['full'][randVariation];
		}
	}

	heights.forEach(function(height, i) {
		var x = i * width;
		lines.push(new Phaser.Line(x, height.left, x + width, height.right));
	});
}

function heightAt(x) {
	// split x into tile (integer), and sub-tile position
	var temp = x / tileSize;
	var tile = Math.floor(temp);
	var subTile = temp - tile;

	tile = heights[tile];

	if (!tile) {
		return seaLevel;
	}

	var tileHeight = tile.left;
	var diff = tile.right - tileHeight;

	return tileHeight + diff * subTile;
}
<|endoftext|>