<commit_before>import _helpers from './helpers';

/**
 * -----------------------------------------------------------------------------
 *  ____  _           _          _    _
 * |  _ \(_) ___ ___ | |__   ___| |  (_)___
 * | |_) | |/ __/ _ \| '_ \ / _ \ |  | / __|
 * |  __/| | (_| (_) | |_) |  __/ |_ | \__ \
 * |_|   |_|\___\___/|_.__/ \___|_(_)| |___/
 * Picobel.js                       _/ |
 * tomhazledine.com/audio          |__/
 *
 * =============================================================================
 *
 * Replace any native <audio> instances with standard elements (spans, buttons &
 * divs) that we can style however we like.
 *
 * Functionality powered by Web Audio API.
 * -----------------------------------------------------------------------------
 */

/**
 * -----------------------------------------------------------------------------
 * SETUP
 *
 * Parse our options, and set starting state.
 * -----------------------------------------------------------------------------
 */
export const PicobelSetup = {
    // Parse the options on init.
    parseOptions: (rawOptions = {}) => {
        // Define our default options.
        const defaultOptions = {
            theme: 'default',
            preload: false,
            components: {
                theme: 'default',
                playPause: true,
                progress: true,
                volume: true,
                download: false,
                mute: true,
                duration: true,
                timer: true
            }
        };
        // Set `options` from arguments, usind `defaultOptions` as fallback.
        const options = Object.assign(defaultOptions, rawOptions);

        return options;
    },

    // Update the state.
    setState: (oldState, changes) => Object.assign(oldState, changes),

    // Return a `components` object that matches the provided themename.
    setComponentsByTheme: (themename = 'default', rawComponents = {}) => {
        const defaultComponents = {
            theme: themename,
            playPause: true,
            progress: true,
            volume: true,
            download: false,
            mute: true,
            duration: true,
            timer: true
        };
        const activeComponents = Object.assign(defaultComponents, rawComponents);
        return activeComponents;
    }
};

export const PicobelData = {
    // Return an array of all the <audio> elements found on the page.
    findAudio: () => {
        // Get all the <audio> occurrences in the page.
        let audioElements = document.getElementsByTagName('audio');
        // Save our audioElements as an array (so we can manipulate the DOM but
        // still access our items).
        let items = [].slice.call(audioElements);
        return items;
    },

    // Build an array of classes to add to each new "player" element
    prepareClasses: (index, classes, theme) => {
        const classesString = `customAudioPlayer loading player_${index} ${classes}`;
        const classesArray = classesString.trim().split(' ');
        classesArray.push(theme);
        return classesArray;
    },

    // Get the url for each audio file we want to load [using elements found by findAudio()]
    getRawData: nodes =>
        nodes.map((node, key) => ({
            key: key,
            preload: node.preload,
            url: node.src,
            className: node.className
        }))
};

export const PicobelMarkup = {
    // Create markup for a custom slider
    buildSlider: (namespace = 'picobel', min = 0, max = 100, value = 0, step = false) => {
        // Create a container element to hold all the parts
        let wrapper = PicobelMarkup.createElement('div', `${namespace}-slider__wrapper`);

        // Create a background div
        let background = PicobelMarkup.createElement('div', `${namespace}-slider__background`);
        // Add the background to the container
        wrapper.appendChild(background);

        // Create a progress indicator
        let progressIndicator = PicobelMarkup.createElement(
            'div',
            `${namespace}-slider__progress-indicator`
        );
        wrapper.appendChild(progressIndicator);

        // Create a "playhead"
        let playhead = PicobelMarkup.createElement('div', `${namespace}-slider__playhead`);
        wrapper.appendChild(playhead);

        // Create an (invisible) input (html range)
        let progress = PicobelMarkup.createElement('input', `${namespace}-slider__range`);
        progress.type = 'range';
        progress.min = min;
        progress.max = max;
        progress.value = value;
        if (step) {
            progress.step = step;
        }
        wrapper.appendChild(progress);

        return wrapper;
    },

    createElement: (type = 'div', className = '') => {
        const newElement = document.createElement(type);
        newElement.className = className;
        return newElement;
    },

    generateMarkup: (nodes = [], components) => {
        const markupArray = nodes.map(node => {
            // Create a container for our new player
            const newPlayer = PicobelMarkup.createElement('div');

            // Set the relevant classes on the new player element
            const classes = PicobelData.prepareClasses(node.key, node.className, components.theme);
            newPlayer.classList.add(...classes);

            // Set song index attribute
            newPlayer.setAttribute('data-song-index', node.key);

            // Create a loading indicator
            let loading = PicobelMarkup.createElement('div', 'loader');
            newPlayer.appendChild(loading);

            // TODO: Add "waiting" indicator here?

            // -----------------
            // PLAY/PAUSE BUTTON
            // -----------------
            if (components.playPause) {
                // Create a play/pause button
                let button = PicobelMarkup.createElement('button', 'playerTrigger');
                let buttonText = PicobelMarkup.createElement('span', 'buttonText');
                buttonText.innerHTML = 'play';
                button.appendChild(buttonText);
                // Add the button to the player
                newPlayer.appendChild(button);
            }

            // ---------
            // META DATA
            // ---------
            // Create a wrapper for our player's metadata
            let meta = PicobelMarkup.createElement('div', 'metaWrapper');

            // Create elements to display file metadata
            let meta_title = PicobelMarkup.createElement('span', 'titleDisplay');
            meta_title.innerHTML = 'File ' + (node.key + 1);
            meta.appendChild(meta_title);

            let meta_artist = PicobelMarkup.createElement('span', 'artistDisplay');
            meta.appendChild(meta_artist);

            // Add the metadata to the player
            newPlayer.appendChild(meta);

            // -----------------------------------
            // TIMINGS (PROGRESS, DURATION, TIMER)
            // -----------------------------------
            if (components.progress || components.duration || components.timer) {
                let timings = PicobelMarkup.createElement('div', 'timingsWrapper');

                if (components.timer) {
                    let timer = PicobelMarkup.createElement('span', 'songPlayTimer');
                    timer.innerHTML = '0:00';
                    timings.appendChild(timer);
                }

                if (components.progress) {
                    let progress = PicobelMarkup.buildSlider('progress', 0, 100, 0);
                    timings.appendChild(progress);
                }

                if (components.duration) {
                    let duration = PicobelMarkup.createElement('span', 'songDuration');
                    duration.innerHTML = '-:--';
                    timings.appendChild(duration);
                }

                // Add the timings to the player
                newPlayer.appendChild(timings);
            }

            // ----------------
            // VOLUME INDICATOR
            // ----------------
            if (components.volume || components.mute) {
                // Volume Indicator
                let volume = PicobelMarkup.createElement('div', 'songVolume');

                if (components.mute) {
                    let mute = PicobelMarkup.createElement('button', 'songMuteButton');
                    mute.innerHTML = 'Mute';
                    volume.appendChild(mute);
                }

                if (components.volume) {
                    let volume_label_wrapper = PicobelMarkup.createElement(
                        'div',
                        'songVolumeLabelWrapper'
                    );
                    let volume_label = PicobelMarkup.createElement('span', 'songVolumeLabel');
                    volume_label.innerHTML = 'Volume';
                    volume_label_wrapper.appendChild(volume_label);
                    let volume_value = PicobelMarkup.createElement('span', 'songVolumeValue');
                    volume_value.innerHTML = '10';
                    volume_label_wrapper.appendChild(volume_value);
                    volume.appendChild(volume_label_wrapper);

                    let volume_slider = PicobelMarkup.buildSlider('volume', 0, 1, 1, 0.1);
                    volume.appendChild(volume_slider);
                }
                newPlayer.appendChild(volume);
            }

            return newPlayer;
        });

        return markupArray;
    }
};

function Picobel(rawOptions = {}) {
    /**
     * -------------------------------------------------------------------------
     * RUN THE CODE
     *
     * This is where the methods are called. Reading these lines should give you
     * a step-by-step overview of what Picobel does.
     * -------------------------------------------------------------------------
     */

    // Parse the options
    const options = PicobelSetup.parseOptions(rawOptions);

    // Declare a `state` variable that will hold the active state
    let state = PicobelSetup.setState({}, { rawNodes: [], audioNodes: [] });

    // Add options to state
    state = PicobelSetup.setState(state, {
        theme: options.theme,
        components: options.components
    });

    const _replaceNodes = (audioElements, newMarkup) => {
        audioElements.map((element, key) => {
            element.parentNode.replaceChild(newMarkup[key], element);
        });
    };

    // Set `components` based on theme (but overridden by explicit `options`).
    state.components = PicobelSetup.setComponentsByTheme(state.theme, rawOptions.components);

    // Get audio elements from page, and save their details to state.
    state.rawNodes = PicobelData.findAudio();

    state.audioNodes = PicobelData.getRawData(state.rawNodes);

    // Build markup for each element, based on `components`
    const markup = PicobelMarkup.generateMarkup(state.audioNodes, state.components);

    // Replace audio elements in DOM with new markup
    _replaceNodes(state.rawNodes, markup);

    // Setup event listeners

    // Provide methods for external use?

    return {
        state
    };
}

export default Picobel;
<commit_msg>removing need to create new audio nodes<commit_after>import _helpers from './helpers';

/**
 * -----------------------------------------------------------------------------
 *  ____  _           _          _    _
 * |  _ \(_) ___ ___ | |__   ___| |  (_)___
 * | |_) | |/ __/ _ \| '_ \ / _ \ |  | / __|
 * |  __/| | (_| (_) | |_) |  __/ |_ | \__ \
 * |_|   |_|\___\___/|_.__/ \___|_(_)| |___/
 * Picobel.js                       _/ |
 * tomhazledine.com/audio          |__/
 *
 * =============================================================================
 *
 * Replace any native <audio> instances with standard elements (spans, buttons &
 * divs) that we can style however we like.
 *
 * Functionality powered by Web Audio API.
 * -----------------------------------------------------------------------------
 */

/**
 * -----------------------------------------------------------------------------
 * SETUP
 *
 * Parse our options, and set starting state.
 * -----------------------------------------------------------------------------
 */
export const PicobelSetup = {
    // Parse the options on init.
    parseOptions: (rawOptions = {}) => {
        // Define our default options.
        const defaultOptions = {
            theme: 'default',
            preload: false,
            components: {
                theme: 'default',
                playPause: true,
                progress: true,
                volume: true,
                download: false,
                mute: true,
                duration: true,
                timer: true
            }
        };
        // Set `options` from arguments, usind `defaultOptions` as fallback.
        const options = Object.assign(defaultOptions, rawOptions);

        return options;
    },

    // Update the state.
    setState: (oldState, changes) => Object.assign(oldState, changes),

    // Return a `components` object that matches the provided themename.
    setComponentsByTheme: (themename = 'default', rawComponents = {}) => {
        const defaultComponents = {
            theme: themename,
            playPause: true,
            progress: true,
            volume: true,
            download: false,
            mute: true,
            duration: true,
            timer: true
        };
        const activeComponents = Object.assign(defaultComponents, rawComponents);
        return activeComponents;
    }
};

export const PicobelData = {
    // Return an array of all the <audio> elements found on the page.
    findAudio: () => {
        // Get all the <audio> occurrences in the page.
        let audioElements = document.getElementsByTagName('audio');
        // Save our audioElements as an array (so we can manipulate the DOM but
        // still access our items).
        let items = [].slice.call(audioElements);
        return items;
    },

    // Build an array of classes to add to each new "player" element
    prepareClasses: (index, classes, theme) => {
        const classesString = `customAudioPlayer loading player_${index} ${classes}`;
        const classesArray = classesString.trim().split(' ');
        classesArray.push(theme);
        return classesArray;
    },

    // Get the url for each audio file we want to load [using elements found by findAudio()]
    getRawData: nodes =>
        // nodes.map((node, key) => ({
        //     key: key,
        //     preload: node.preload,
        //     url: node.src,
        //     className: node.className
        // }))
        nodes.map((node, key) => {
            node.key = key;
            return node;
        })
};

export const PicobelMarkup = {
    // Create markup for a custom slider
    buildSlider: (namespace = 'picobel', min = 0, max = 100, value = 0, step = false) => {
        // Create a container element to hold all the parts
        let wrapper = PicobelMarkup.createElement('div', `${namespace}-slider__wrapper`);

        // Create a background div
        let background = PicobelMarkup.createElement('div', `${namespace}-slider__background`);
        // Add the background to the container
        wrapper.appendChild(background);

        // Create a progress indicator
        let progressIndicator = PicobelMarkup.createElement(
            'div',
            `${namespace}-slider__progress-indicator`
        );
        wrapper.appendChild(progressIndicator);

        // Create a "playhead"
        let playhead = PicobelMarkup.createElement('div', `${namespace}-slider__playhead`);
        wrapper.appendChild(playhead);

        // Create an (invisible) input (html range)
        let progress = PicobelMarkup.createElement('input', `${namespace}-slider__range`);
        progress.type = 'range';
        progress.min = min;
        progress.max = max;
        progress.value = value;
        if (step) {
            progress.step = step;
        }
        wrapper.appendChild(progress);

        return wrapper;
    },

    createElement: (type = 'div', className = '') => {
        const newElement = document.createElement(type);
        newElement.className = className;
        return newElement;
    },

    generateMarkup: (nodes = [], components) => {
        const markupArray = nodes.map(node => {
            // Create a container for our new player
            const newPlayer = PicobelMarkup.createElement('div');

            // Set the relevant classes on the new player element
            const classes = PicobelData.prepareClasses(node.key, node.className, components.theme);
            newPlayer.classList.add(...classes);

            // Set song index attribute
            newPlayer.setAttribute('data-song-index', node.key);

            // Create a loading indicator
            let loading = PicobelMarkup.createElement('div', 'loader');
            newPlayer.appendChild(loading);

            // TODO: Add "waiting" indicator here?

            // -----------------
            // PLAY/PAUSE BUTTON
            // -----------------
            if (components.playPause) {
                // Create a play/pause button
                let button = PicobelMarkup.createElement('button', 'playerTrigger');
                let buttonText = PicobelMarkup.createElement('span', 'buttonText');
                buttonText.innerHTML = 'play';
                button.appendChild(buttonText);
                // Add the button to the player
                newPlayer.appendChild(button);
            }

            // ---------
            // META DATA
            // ---------
            // Create a wrapper for our player's metadata
            let meta = PicobelMarkup.createElement('div', 'metaWrapper');

            // Create elements to display file metadata
            let meta_title = PicobelMarkup.createElement('span', 'titleDisplay');
            meta_title.innerHTML = 'File ' + (node.key + 1);
            meta.appendChild(meta_title);

            let meta_artist = PicobelMarkup.createElement('span', 'artistDisplay');
            meta.appendChild(meta_artist);

            // Add the metadata to the player
            newPlayer.appendChild(meta);

            // -----------------------------------
            // TIMINGS (PROGRESS, DURATION, TIMER)
            // -----------------------------------
            if (components.progress || components.duration || components.timer) {
                let timings = PicobelMarkup.createElement('div', 'timingsWrapper');

                if (components.timer) {
                    let timer = PicobelMarkup.createElement('span', 'songPlayTimer');
                    timer.innerHTML = '0:00';
                    timings.appendChild(timer);
                }

                if (components.progress) {
                    let progress = PicobelMarkup.buildSlider('progress', 0, 100, 0);
                    timings.appendChild(progress);
                }

                if (components.duration) {
                    let duration = PicobelMarkup.createElement('span', 'songDuration');
                    duration.innerHTML = '-:--';
                    timings.appendChild(duration);
                }

                // Add the timings to the player
                newPlayer.appendChild(timings);
            }

            // ----------------
            // VOLUME INDICATOR
            // ----------------
            if (components.volume || components.mute) {
                // Volume Indicator
                let volume = PicobelMarkup.createElement('div', 'songVolume');

                if (components.mute) {
                    let mute = PicobelMarkup.createElement('button', 'songMuteButton');
                    mute.innerHTML = 'Mute';
                    volume.appendChild(mute);
                }

                if (components.volume) {
                    let volume_label_wrapper = PicobelMarkup.createElement(
                        'div',
                        'songVolumeLabelWrapper'
                    );
                    let volume_label = PicobelMarkup.createElement('span', 'songVolumeLabel');
                    volume_label.innerHTML = 'Volume';
                    volume_label_wrapper.appendChild(volume_label);
                    let volume_value = PicobelMarkup.createElement('span', 'songVolumeValue');
                    volume_value.innerHTML = '10';
                    volume_label_wrapper.appendChild(volume_value);
                    volume.appendChild(volume_label_wrapper);

                    let volume_slider = PicobelMarkup.buildSlider('volume', 0, 1, 1, 0.1);
                    volume.appendChild(volume_slider);
                }
                newPlayer.appendChild(volume);
            }

            return newPlayer;
        });

        return markupArray;
    }
};

export const PicobelAudio = {};

function Picobel(rawOptions = {}) {
    /**
     * -------------------------------------------------------------------------
     * RUN THE CODE
     *
     * This is where the methods are called. Reading these lines should give you
     * a step-by-step overview of what Picobel does.
     * -------------------------------------------------------------------------
     */

    // Parse the options
    const options = PicobelSetup.parseOptions(rawOptions);

    // Declare a `state` variable that will hold the active state
    let state = PicobelSetup.setState({}, { audioNodes: [] });

    // Add options to state
    state = PicobelSetup.setState(state, {
        theme: options.theme,
        components: options.components
    });

    const _replaceNodes = (audioElements, newMarkup) => {
        audioElements.map((element, key) => {
            element.parentNode.replaceChild(newMarkup[key], element);
        });
    };

    // Set `components` based on theme (but overridden by explicit `options`).
    state.components = PicobelSetup.setComponentsByTheme(state.theme, rawOptions.components);

    // Get audio elements from page, and save their details to state.
    state.audioNodes = PicobelData.findAudio();

    state.audioNodes = PicobelData.getRawData(state.audioNodes);

    // Build markup for each element, based on `components`
    const markup = PicobelMarkup.generateMarkup(state.audioNodes, state.components);

    // Replace audio elements in DOM with new markup
    _replaceNodes(state.audioNodes, markup);

    // state.audioNodes = PicobelAudio.init(state.audioNodes);

    // Setup event listeners

    // Provide methods for external use?

    return {
        state
    };
}

export default Picobel;
<|endoftext|>


<commit_before>import _helpers from './helpers';

/**
 * -----------------------------------------------------------------------------
 *  ____  _           _          _    _
 * |  _ \(_) ___ ___ | |__   ___| |  (_)___
 * | |_) | |/ __/ _ \| '_ \ / _ \ |  | / __|
 * |  __/| | (_| (_) | |_) |  __/ |_ | \__ \
 * |_|   |_|\___\___/|_.__/ \___|_(_)| |___/
 * Picobel.js                       _/ |
 * tomhazledine.com/audio          |__/
 *
 * =============================================================================
 *
 * Replace any native <audio> instances with standard elements (spans, buttons &
 * divs) that we can style however we like.
 *
 * Functionality powered by Web Audio API.
 * -----------------------------------------------------------------------------
 */

/**
 * -----------------------------------------------------------------------------
 * SETUP
 *
 * Parse our options, and set starting state.
 * -----------------------------------------------------------------------------
 */
export const PicobelSetup = {
    // Parse the options on init.
    parseOptions: (rawOptions = {}) => {
        // Define our default options.
        const defaultOptions = {
            theme: 'default',
            preload: false,
            components: {
                theme: 'default',
                playPause: true,
                progress: true,
                volume: true,
                download: false,
                mute: true,
                duration: true,
                timer: true
            }
        };
        // Set `options` from arguments, usind `defaultOptions` as fallback.
        const options = Object.assign(defaultOptions, rawOptions);

        return options;
    },

    // Update the state.
    setState: (oldState, changes) => Object.assign(oldState, changes),

    // Return a `components` object that matches the provided themename.
    setComponentsByTheme: (themename = 'default', rawComponents = {}) => {
        const defaultComponents = {
            theme: themename,
            playPause: true,
            progress: true,
            volume: true,
            download: false,
            mute: true,
            duration: true,
            timer: true
        };
        const activeComponents = Object.assign(defaultComponents, rawComponents);
        return activeComponents;
    }
};

export const PicobelData = {
    // Return an array of all the <audio> elements found on the page.
    findAudio: () => {
        // Get all the <audio> occurrences in the page.
        let audioElements = document.getElementsByTagName('audio');
        // Save our audioElements as an array (so we can manipulate the DOM but
        // still access our items).
        let items = [].slice.call(audioElements);
        return items;
    },

    // Build an array of classes to add to each new "player" element
    prepareClasses: (index, classes, theme) => {
        const classesString = `customAudioPlayer loading player_${index} ${classes}`;
        const classesArray = classesString.trim().split(' ');
        classesArray.push(theme);
        return classesArray;
    },

    // Get the url for each audio file we want to load [using elements found by findAudio()]
    getRawData: nodes =>
        nodes.map((node, key) => {
            node.key = key;
            return node;
        }),

    // Get info about the audio track
    getMeta: item => {
        let meta = {};
        // Get the filename and type
        meta.url = item.src;
        meta.fileType = _helpers.getFileType(meta.url);
        meta.fileName = _helpers.getFileName(meta.url);
        // If there is a valid title, get that title, otherwise get the file name.
        meta.title = item.title !== '' ? item.title : `${meta.fileName}.${meta.fileType}`;
        // If there is a valid 'artist', get the artist name.
        meta.artist = item.dataset.artist ? item.dataset.artist : false;
        return meta;
    }
};

export const PicobelMarkup = {
    // Create markup for a custom slider
    buildSlider: (namespace = 'picobel', min = 0, max = 100, value = 0, step = false) => {
        // Create a container element to hold all the parts
        let wrapper = PicobelMarkup.createElement('div', `${namespace}-slider__wrapper`);

        // Create a background div
        let background = PicobelMarkup.createElement('div', `${namespace}-slider__background`);
        // Add the background to the container
        wrapper.appendChild(background);

        // Create a progress indicator
        let progressIndicator = PicobelMarkup.createElement(
            'div',
            `${namespace}-slider__progress-indicator`
        );
        wrapper.appendChild(progressIndicator);

        // Create a "playhead"
        let playhead = PicobelMarkup.createElement('div', `${namespace}-slider__playhead`);
        wrapper.appendChild(playhead);

        // Create an (invisible) input (html range)
        let progress = PicobelMarkup.createElement('input', `${namespace}-slider__range`);
        progress.type = 'range';
        progress.min = min;
        progress.max = max;
        progress.value = value;
        if (step) {
            progress.step = step;
        }
        wrapper.appendChild(progress);

        return wrapper;
    },

    createElement: (type = 'div', className = '') => {
        const newElement = document.createElement(type);
        newElement.className = className;
        return newElement;
    },

    // Set the value of the song-length display
    setLengthDisplay: item => {
        let duration = _helpers.parseTime(item.duration);
        item.elements.durationDisplay[0].innerHTML = duration;
    },

    setMeta: (meta, elements) => {
        if (meta.artist) {
            elements.artistDisplay[0].innerHTML = meta.artist;
        }
        elements.titleDisplay[0].innerHTML = meta.title;
    },

    generateMarkup: (nodes = [], components) => {
        const markupArray = nodes.map(node => {
            // Create a container for our new player
            const newPlayer = PicobelMarkup.createElement('div');

            // Set the relevant classes on the new player element
            const classes = PicobelData.prepareClasses(node.key, node.className, components.theme);
            newPlayer.classList.add(...classes);

            // Set song index attribute
            newPlayer.setAttribute('data-song-index', node.key);

            // Create a loading indicator
            let loading = PicobelMarkup.createElement('div', 'loader');
            newPlayer.appendChild(loading);

            // TODO: Add "waiting" indicator here?

            // -----------------
            // PLAY/PAUSE BUTTON
            // -----------------
            if (components.playPause) {
                // Create a play/pause button
                let button = PicobelMarkup.createElement('button', 'playerTrigger');
                let buttonText = PicobelMarkup.createElement('span', 'buttonText');
                buttonText.innerHTML = 'play';
                button.appendChild(buttonText);
                // Add the button to the player
                newPlayer.appendChild(button);
            }

            // ---------
            // META DATA
            // ---------
            // Create a wrapper for our player's metadata
            let meta = PicobelMarkup.createElement('div', 'metaWrapper');

            // Create elements to display file metadata
            let meta_title = PicobelMarkup.createElement('span', 'titleDisplay');
            meta_title.innerHTML = 'File ' + (node.key + 1);
            meta.appendChild(meta_title);

            let meta_artist = PicobelMarkup.createElement('span', 'artistDisplay');
            meta.appendChild(meta_artist);

            // Add the metadata to the player
            newPlayer.appendChild(meta);

            // -----------------------------------
            // TIMINGS (PROGRESS, DURATION, TIMER)
            // -----------------------------------
            if (components.progress || components.duration || components.timer) {
                let timings = PicobelMarkup.createElement('div', 'timingsWrapper');

                if (components.timer) {
                    let timer = PicobelMarkup.createElement('span', 'songPlayTimer');
                    timer.innerHTML = '0:00';
                    timings.appendChild(timer);
                }

                if (components.progress) {
                    let progress = PicobelMarkup.buildSlider('progress', 0, 100, 0);
                    timings.appendChild(progress);
                }

                if (components.duration) {
                    let duration = PicobelMarkup.createElement('span', 'songDuration');
                    duration.innerHTML = '-:--';
                    timings.appendChild(duration);
                }

                // Add the timings to the player
                newPlayer.appendChild(timings);
            }

            // ----------------
            // VOLUME INDICATOR
            // ----------------
            if (components.volume || components.mute) {
                // Volume Indicator
                let volume = PicobelMarkup.createElement('div', 'songVolume');

                if (components.mute) {
                    let mute = PicobelMarkup.createElement('button', 'songMuteButton');
                    mute.innerHTML = 'Mute';
                    volume.appendChild(mute);
                }

                if (components.volume) {
                    let volume_label_wrapper = PicobelMarkup.createElement(
                        'div',
                        'songVolumeLabelWrapper'
                    );
                    let volume_label = PicobelMarkup.createElement('span', 'songVolumeLabel');
                    volume_label.innerHTML = 'Volume';
                    volume_label_wrapper.appendChild(volume_label);
                    let volume_value = PicobelMarkup.createElement('span', 'songVolumeValue');
                    volume_value.innerHTML = '10';
                    volume_label_wrapper.appendChild(volume_value);
                    volume.appendChild(volume_label_wrapper);

                    let volume_slider = PicobelMarkup.buildSlider('volume', 0, 1, 1, 0.1);
                    volume.appendChild(volume_slider);
                }
                newPlayer.appendChild(volume);
            }

            return newPlayer;
        });

        return markupArray;
    },

    elementHooks: nodes =>
        nodes.map(node => {
            let wrapper = document.querySelectorAll(`[data-song-index='${node.key}']`);
            node.elements = {
                wrapper: wrapper[0],
                playPauseButton: wrapper[0].querySelectorAll('.playerTrigger'),
                muteButton: wrapper[0].querySelectorAll('.songMuteButton'),
                playPauseButtonText: wrapper[0].querySelectorAll('.buttonText'),
                playTimer: wrapper[0].querySelectorAll('.songPlayTimer'),
                durationDisplay: wrapper[0].querySelectorAll('.songDuration'),
                titleDisplay: wrapper[0].querySelectorAll('.titleDisplay'),
                artistDisplay: wrapper[0].querySelectorAll('.artistDisplay'),
                progressBar: wrapper[0].querySelectorAll('.progress-slider__range'),
                playhead: wrapper[0].querySelectorAll('.progress-slider__playhead'),
                indicator: wrapper[0].querySelectorAll('.progress-slider__progress-indicator'),
                volumeControl: wrapper[0].querySelectorAll('.volume-slider__range'),
                volumeDisplay: wrapper[0].querySelectorAll('.songVolumeValue'),
                volumeIndicator: wrapper[0].querySelectorAll('.volume-slider__progress-indicator'),
                volumePlayhead: wrapper[0].querySelectorAll('.volume-slider__playhead')
            };
            return node;
        })
};

function Picobel(rawOptions = {}) {
    console.log("picobel init'ed");
    /**
     * -------------------------------------------------------------------------
     * RUN THE CODE
     *
     * This is where the methods are called. Reading these lines should give you
     * a step-by-step overview of what Picobel does.
     * -------------------------------------------------------------------------
     */

    // Parse the options
    const options = PicobelSetup.parseOptions(rawOptions);

    // Declare a `state` variable that will hold the active state
    let state = PicobelSetup.setState({}, { audioNodes: [] });

    // Add options to state
    state = PicobelSetup.setState(state, {
        theme: options.theme,
        components: options.components
    });

    const _replaceNodes = (audioElements, newMarkup) => {
        audioElements.map((element, key) => {
            element.parentNode.replaceChild(newMarkup[key], element);
        });
    };

    const _setupLocalListeners = nodes =>
        nodes.map((node, key) => {
            // Audio event listeners
            node.addEventListener('timeupdate', audioFunctions.triggerUpdateProgress, false);
            node.addEventListener('loadstart', audioFunctions.loadStart, false);
            node.addEventListener('canplaythrough', audioFunctions.canplaythrough, false);
            node.addEventListener('error', audioFunctions.errors, false);
            node.addEventListener('stalled', audioFunctions.stalled, false);
            node.addEventListener('waiting', audioFunctions.errors, false);
            node.addEventListener('progress', audioFunctions.progress, false);

            // DOM interaction event listeners
            node.elements.playPauseButton[0].addEventListener(
                'click',
                audioFunctions.triggerPlayPauseAudio,
                false
            );
            node.elements.progressBar[0].addEventListener(
                'input',
                audioFunctions.sliderScrub,
                false
            );
            node.elements.volumeControl[0].addEventListener('input', audioFunctions.volume, false);
            node.elements.muteButton[0].addEventListener(
                'click',
                audioFunctions.muteUnmuteAudio,
                false
            );
            return node;
        });

    const audioFunctions = {
        pauseAll: () => {
            const paused = state.audioNodes.map(node => {
                audioFunctions.pause(node);
            });
        },
        triggerPlayPauseAudio: event => {
            let index = _helpers.findParentIndex(event.srcElement);
            let node = state.audioNodes.find(node => node.key == index);
            let button = event.srcElement;
            audioFunctions.playPauseAudio(node, button);
        },
        playPauseAudio: node => {
            if (node.paused || node.currentTime === 0) {
                audioFunctions.pauseAll();
                audioFunctions.play(node);
            } else {
                audioFunctions.pause(node);
            }
        },
        play: node => {
            node.play();
            let button = node.elements.playPauseButton[0];
            button.classList.remove('songPaused');
            button.classList.add('songPlaying');
        },
        pause: node => {
            node.pause();
            let button = node.elements.playPauseButton[0];
            button.classList.remove('songPlaying');
            button.classList.add('songPaused');
        },
        triggerUpdateProgress: event => audioFunctions.updateProgress(event.srcElement),

        updateProgress: node => {
            let progress = node.currentTime;
            let duration = node.duration;
            let progressParsed = _helpers.parseTime(progress);
            node.elements.playTimer[0].innerHTML = progressParsed;
            if (progress >= duration) {
                node.elements.playPauseButton[0].classList.remove('songPlaying');
            }
            let progressPercent = (progress / duration * 100).toFixed(2);
            node.elements.progressBar[0].value = progressPercent;
            // console.log(node.elements.indicator);
            node.elements.indicator[0].style.width = progressPercent + '%';
            node.elements.playhead[0].style.left = progressPercent + '%';
        },
        loadStart: () => {
            console.log('loadStart');
        },
        canplaythrough: function() {
            PicobelMarkup.setLengthDisplay(this);
            this.elements.wrapper.classList.remove('loading');
            let meta = PicobelData.getMeta(this);
            PicobelMarkup.setMeta(meta, this.elements);
        },
        errors: error => {
            console.log(error);
        },
        stalled: () => {
            console.log('stalled');
        },
        progress: () => {
            console.log('progress');
        },
        sliderScrub: event => {
            let index = _helpers.findParentIndex(event.srcElement);
            let activeNode = state.audioNodes.find(node => node.key == index);
            let duration = activeNode.duration;
            let targetTime = duration * (event.srcElement.value / 100);
            targetTime = targetTime.toFixed(2);
            activeNode.currentTime = targetTime;
            audioFunctions.updateProgress(activeNode);
        },
        volume: () => {},
        muteUnmuteAudio: function() {
            let index = _helpers.findParentIndex(this);
            console.log();
        }
    };

    // Set `components` based on theme (but overridden by explicit `options`).
    state.components = PicobelSetup.setComponentsByTheme(state.theme, rawOptions.components);

    // Get audio elements from page, and save their details to state.
    state.audioNodes = PicobelData.findAudio();

    state.audioNodes = PicobelData.getRawData(state.audioNodes);

    // Build markup for each element, based on `components`
    const markup = PicobelMarkup.generateMarkup(state.audioNodes, state.components);

    // Replace audio elements in DOM with new markup
    _replaceNodes(state.audioNodes, markup);

    // Save new DOM elements to our node list
    state.audioNodes = PicobelMarkup.elementHooks(state.audioNodes);
    // state.audioNodes = PicobelAudio.setupListeners(state.audioNodes);
    state.audioNodes = _setupLocalListeners(state.audioNodes);

    // Replace audio elements in DOM with new markup
    // _replaceNodes(state.audioNodes, markup);

    // state.audioNodes = PicobelAudio.init(state.audioNodes);

    // Setup event listeners

    // Provide methods for external use?

    return {
        state
    };
}

export default Picobel;
<commit_msg>mute button display toggling<commit_after>import _helpers from './helpers';

/**
 * -----------------------------------------------------------------------------
 *  ____  _           _          _    _
 * |  _ \(_) ___ ___ | |__   ___| |  (_)___
 * | |_) | |/ __/ _ \| '_ \ / _ \ |  | / __|
 * |  __/| | (_| (_) | |_) |  __/ |_ | \__ \
 * |_|   |_|\___\___/|_.__/ \___|_(_)| |___/
 * Picobel.js                       _/ |
 * tomhazledine.com/audio          |__/
 *
 * =============================================================================
 *
 * Replace any native <audio> instances with standard elements (spans, buttons &
 * divs) that we can style however we like.
 *
 * Functionality powered by Web Audio API.
 * -----------------------------------------------------------------------------
 */

/**
 * -----------------------------------------------------------------------------
 * SETUP
 *
 * Parse our options, and set starting state.
 * -----------------------------------------------------------------------------
 */
export const PicobelSetup = {
    // Parse the options on init.
    parseOptions: (rawOptions = {}) => {
        // Define our default options.
        const defaultOptions = {
            theme: 'default',
            preload: false,
            components: {
                theme: 'default',
                playPause: true,
                progress: true,
                volume: true,
                download: false,
                mute: true,
                duration: true,
                timer: true
            }
        };
        // Set `options` from arguments, usind `defaultOptions` as fallback.
        const options = Object.assign(defaultOptions, rawOptions);

        return options;
    },

    // Update the state.
    setState: (oldState, changes) => Object.assign(oldState, changes),

    // Return a `components` object that matches the provided themename.
    setComponentsByTheme: (themename = 'default', rawComponents = {}) => {
        const defaultComponents = {
            theme: themename,
            playPause: true,
            progress: true,
            volume: true,
            download: false,
            mute: true,
            duration: true,
            timer: true
        };
        const activeComponents = Object.assign(defaultComponents, rawComponents);
        return activeComponents;
    }
};

export const PicobelData = {
    // Return an array of all the <audio> elements found on the page.
    findAudio: () => {
        // Get all the <audio> occurrences in the page.
        let audioElements = document.getElementsByTagName('audio');
        // Save our audioElements as an array (so we can manipulate the DOM but
        // still access our items).
        let items = [].slice.call(audioElements);
        return items;
    },

    // Build an array of classes to add to each new "player" element
    prepareClasses: (index, classes, theme) => {
        const classesString = `customAudioPlayer loading player_${index} ${classes}`;
        const classesArray = classesString.trim().split(' ');
        classesArray.push(theme);
        return classesArray;
    },

    // Get the url for each audio file we want to load [using elements found by findAudio()]
    getRawData: nodes =>
        nodes.map((node, key) => {
            node.key = key;
            node.mute = false;
            node.tmpVolume = 1;
            return node;
        }),

    // Get info about the audio track
    getMeta: item => {
        let meta = {};
        // Get the filename and type
        meta.url = item.src;
        meta.fileType = _helpers.getFileType(meta.url);
        meta.fileName = _helpers.getFileName(meta.url);
        // If there is a valid title, get that title, otherwise get the file name.
        meta.title = item.title !== '' ? item.title : `${meta.fileName}.${meta.fileType}`;
        // If there is a valid 'artist', get the artist name.
        meta.artist = item.dataset.artist ? item.dataset.artist : false;
        return meta;
    }
};

export const PicobelMarkup = {
    // Create markup for a custom slider
    buildSlider: (namespace = 'picobel', min = 0, max = 100, value = 0, step = false) => {
        // Create a container element to hold all the parts
        let wrapper = PicobelMarkup.createElement('div', `${namespace}-slider__wrapper`);

        // Create a background div
        let background = PicobelMarkup.createElement('div', `${namespace}-slider__background`);
        // Add the background to the container
        wrapper.appendChild(background);

        // Create a progress indicator
        let progressIndicator = PicobelMarkup.createElement(
            'div',
            `${namespace}-slider__progress-indicator`
        );
        wrapper.appendChild(progressIndicator);

        // Create a "playhead"
        let playhead = PicobelMarkup.createElement('div', `${namespace}-slider__playhead`);
        wrapper.appendChild(playhead);

        // Create an (invisible) input (html range)
        let progress = PicobelMarkup.createElement('input', `${namespace}-slider__range`);
        progress.type = 'range';
        progress.min = min;
        progress.max = max;
        progress.value = value;
        if (step) {
            progress.step = step;
        }
        wrapper.appendChild(progress);

        return wrapper;
    },

    createElement: (type = 'div', className = '') => {
        const newElement = document.createElement(type);
        newElement.className = className;
        return newElement;
    },

    // Set the value of the song-length display
    setLengthDisplay: item => {
        let duration = _helpers.parseTime(item.duration);
        item.elements.durationDisplay[0].innerHTML = duration;
    },

    setMeta: (meta, elements) => {
        if (meta.artist) {
            elements.artistDisplay[0].innerHTML = meta.artist;
        }
        elements.titleDisplay[0].innerHTML = meta.title;
    },

    generateMarkup: (nodes = [], components) => {
        const markupArray = nodes.map(node => {
            // Create a container for our new player
            const newPlayer = PicobelMarkup.createElement('div');

            // Set the relevant classes on the new player element
            const classes = PicobelData.prepareClasses(node.key, node.className, components.theme);
            newPlayer.classList.add(...classes);

            // Set song index attribute
            newPlayer.setAttribute('data-song-index', node.key);

            // Create a loading indicator
            let loading = PicobelMarkup.createElement('div', 'loader');
            newPlayer.appendChild(loading);

            // TODO: Add "waiting" indicator here?

            // -----------------
            // PLAY/PAUSE BUTTON
            // -----------------
            if (components.playPause) {
                // Create a play/pause button
                let button = PicobelMarkup.createElement('button', 'playerTrigger');
                let buttonText = PicobelMarkup.createElement('span', 'buttonText');
                buttonText.innerHTML = 'play';
                button.appendChild(buttonText);
                // Add the button to the player
                newPlayer.appendChild(button);
            }

            // ---------
            // META DATA
            // ---------
            // Create a wrapper for our player's metadata
            let meta = PicobelMarkup.createElement('div', 'metaWrapper');

            // Create elements to display file metadata
            let meta_title = PicobelMarkup.createElement('span', 'titleDisplay');
            meta_title.innerHTML = 'File ' + (node.key + 1);
            meta.appendChild(meta_title);

            let meta_artist = PicobelMarkup.createElement('span', 'artistDisplay');
            meta.appendChild(meta_artist);

            // Add the metadata to the player
            newPlayer.appendChild(meta);

            // -----------------------------------
            // TIMINGS (PROGRESS, DURATION, TIMER)
            // -----------------------------------
            if (components.progress || components.duration || components.timer) {
                let timings = PicobelMarkup.createElement('div', 'timingsWrapper');

                if (components.timer) {
                    let timer = PicobelMarkup.createElement('span', 'songPlayTimer');
                    timer.innerHTML = '0:00';
                    timings.appendChild(timer);
                }

                if (components.progress) {
                    let progress = PicobelMarkup.buildSlider('progress', 0, 100, 0);
                    timings.appendChild(progress);
                }

                if (components.duration) {
                    let duration = PicobelMarkup.createElement('span', 'songDuration');
                    duration.innerHTML = '-:--';
                    timings.appendChild(duration);
                }

                // Add the timings to the player
                newPlayer.appendChild(timings);
            }

            // ----------------
            // VOLUME INDICATOR
            // ----------------
            if (components.volume || components.mute) {
                // Volume Indicator
                let volume = PicobelMarkup.createElement('div', 'songVolume');

                if (components.mute) {
                    let mute = PicobelMarkup.createElement('button', 'songMuteButton');
                    mute.innerHTML = 'Mute';
                    volume.appendChild(mute);
                }

                if (components.volume) {
                    let volume_label_wrapper = PicobelMarkup.createElement(
                        'div',
                        'songVolumeLabelWrapper'
                    );
                    let volume_label = PicobelMarkup.createElement('span', 'songVolumeLabel');
                    volume_label.innerHTML = 'Volume';
                    volume_label_wrapper.appendChild(volume_label);
                    let volume_value = PicobelMarkup.createElement('span', 'songVolumeValue');
                    volume_value.innerHTML = '10';
                    volume_label_wrapper.appendChild(volume_value);
                    volume.appendChild(volume_label_wrapper);

                    let volume_slider = PicobelMarkup.buildSlider('volume', 0, 1, 1, 0.1);
                    volume.appendChild(volume_slider);
                }
                newPlayer.appendChild(volume);
            }

            return newPlayer;
        });

        return markupArray;
    },

    elementHooks: nodes =>
        nodes.map(node => {
            let wrapper = document.querySelectorAll(`[data-song-index='${node.key}']`);
            node.elements = {
                wrapper: wrapper[0],
                playPauseButton: wrapper[0].querySelectorAll('.playerTrigger'),
                muteButton: wrapper[0].querySelectorAll('.songMuteButton'),
                playPauseButtonText: wrapper[0].querySelectorAll('.buttonText'),
                playTimer: wrapper[0].querySelectorAll('.songPlayTimer'),
                durationDisplay: wrapper[0].querySelectorAll('.songDuration'),
                titleDisplay: wrapper[0].querySelectorAll('.titleDisplay'),
                artistDisplay: wrapper[0].querySelectorAll('.artistDisplay'),
                progressBar: wrapper[0].querySelectorAll('.progress-slider__range'),
                playhead: wrapper[0].querySelectorAll('.progress-slider__playhead'),
                indicator: wrapper[0].querySelectorAll('.progress-slider__progress-indicator'),
                volumeControl: wrapper[0].querySelectorAll('.volume-slider__range'),
                volumeDisplay: wrapper[0].querySelectorAll('.songVolumeValue'),
                volumeIndicator: wrapper[0].querySelectorAll('.volume-slider__progress-indicator'),
                volumePlayhead: wrapper[0].querySelectorAll('.volume-slider__playhead')
            };
            return node;
        })
};

function Picobel(rawOptions = {}) {
    console.log("picobel init'ed");
    /**
     * -------------------------------------------------------------------------
     * RUN THE CODE
     *
     * This is where the methods are called. Reading these lines should give you
     * a step-by-step overview of what Picobel does.
     * -------------------------------------------------------------------------
     */

    // Parse the options
    const options = PicobelSetup.parseOptions(rawOptions);

    // Declare a `state` variable that will hold the active state
    let state = PicobelSetup.setState({}, { audioNodes: [] });

    // Add options to state
    state = PicobelSetup.setState(state, {
        theme: options.theme,
        components: options.components
    });

    const _replaceNodes = (audioElements, newMarkup) => {
        audioElements.map((element, key) => {
            element.parentNode.replaceChild(newMarkup[key], element);
        });
    };

    const _setupLocalListeners = nodes =>
        nodes.map((node, key) => {
            // Audio event listeners
            node.addEventListener('timeupdate', audioFunctions.triggerUpdateProgress, false);
            node.addEventListener('loadstart', audioFunctions.loadStart, false);
            node.addEventListener('canplaythrough', audioFunctions.canplaythrough, false);
            node.addEventListener('error', audioFunctions.errors, false);
            node.addEventListener('stalled', audioFunctions.stalled, false);
            node.addEventListener('waiting', audioFunctions.errors, false);
            node.addEventListener('progress', audioFunctions.progress, false);

            // DOM interaction event listeners
            node.elements.playPauseButton[0].addEventListener(
                'click',
                audioFunctions.triggerPlayPauseAudio,
                false
            );
            node.elements.progressBar[0].addEventListener(
                'input',
                audioFunctions.sliderScrub,
                false
            );
            node.elements.volumeControl[0].addEventListener('input', audioFunctions.volume, false);
            node.elements.muteButton[0].addEventListener(
                'click',
                audioFunctions.muteUnmuteAudio,
                false
            );
            return node;
        });

    const audioFunctions = {
        pauseAll: () => {
            const paused = state.audioNodes.map(node => {
                audioFunctions.pause(node);
            });
        },
        triggerPlayPauseAudio: event => {
            let index = _helpers.findParentIndex(event.srcElement);
            let node = state.audioNodes.find(node => node.key == index);
            audioFunctions.playPauseAudio(node);
        },
        playPauseAudio: node => {
            if (node.paused || node.currentTime === 0) {
                audioFunctions.pauseAll();
                audioFunctions.play(node);
            } else {
                audioFunctions.pause(node);
            }
        },
        play: node => {
            node.play();
            let button = node.elements.playPauseButton[0];
            button.classList.remove('songPaused');
            button.classList.add('songPlaying');
        },
        pause: node => {
            node.pause();
            let button = node.elements.playPauseButton[0];
            button.classList.remove('songPlaying');
            button.classList.add('songPaused');
        },
        triggerUpdateProgress: event => audioFunctions.updateProgress(event.srcElement),

        updateProgress: node => {
            let progress = node.currentTime;
            let duration = node.duration;
            let progressParsed = _helpers.parseTime(progress);
            node.elements.playTimer[0].innerHTML = progressParsed;
            if (progress >= duration) {
                node.elements.playPauseButton[0].classList.remove('songPlaying');
            }
            let progressPercent = (progress / duration * 100).toFixed(2);
            node.elements.progressBar[0].value = progressPercent;
            // console.log(node.elements.indicator);
            node.elements.indicator[0].style.width = progressPercent + '%';
            node.elements.playhead[0].style.left = progressPercent + '%';
        },
        loadStart: () => {
            console.log('loadStart');
        },
        canplaythrough: function() {
            PicobelMarkup.setLengthDisplay(this);
            this.elements.wrapper.classList.remove('loading');
            let meta = PicobelData.getMeta(this);
            PicobelMarkup.setMeta(meta, this.elements);
        },
        errors: error => {
            console.log(error);
        },
        stalled: () => {
            console.log('stalled');
        },
        progress: () => {
            console.log('progress');
        },
        sliderScrub: event => {
            let index = _helpers.findParentIndex(event.srcElement);
            let activeNode = state.audioNodes.find(node => node.key == index);
            let duration = activeNode.duration;
            let targetTime = duration * (event.srcElement.value / 100);
            targetTime = targetTime.toFixed(2);
            activeNode.currentTime = targetTime;
            audioFunctions.updateProgress(activeNode);
        },
        volume: () => {},
        muteUnmuteAudio: event => {
            console.log('mute button clicked!');
            let index = _helpers.findParentIndex(event.srcElement);
            let node = state.audioNodes.find(node => node.key == index);
            audioFunctions.mute(node, node.mute);
        },
        mute: (node, mute) => {
            node.mute = !mute;
            console.log(`mute = ${mute} for ${node.key}`);
            // let oldVolume;
            let button = node.elements.muteButton[0];
            if (node.mute) {
                node.tmpVolume = node.volume;
                // button.setAttribute('data-saved-volume', oldVolume);
                // setVolume(index, 0);
                button.classList.add('songMuted');
                button.classList.remove('songUnmuted');
                button.innerHTML = 'unmute';
            } else {
                // myAudio[index].volume = 0;
                // oldVolume = button.getAttribute('data-saved-volume');
                if (typeof node.tmpVolume != 'undefined' && node.tmpVolume > 0) {
                    // setVolume(index, oldVolume);
                } else {
                    // setVolume(index, 1);
                }

                button.classList.remove('songMuted');
                button.classList.add('songUnmuted');
                button.innerHTML = 'mute';
            }
        }
    };

    // Set `components` based on theme (but overridden by explicit `options`).
    state.components = PicobelSetup.setComponentsByTheme(state.theme, rawOptions.components);

    // Get audio elements from page, and save their details to state.
    state.audioNodes = PicobelData.findAudio();

    state.audioNodes = PicobelData.getRawData(state.audioNodes);

    // Build markup for each element, based on `components`
    const markup = PicobelMarkup.generateMarkup(state.audioNodes, state.components);

    // Replace audio elements in DOM with new markup
    _replaceNodes(state.audioNodes, markup);

    // Save new DOM elements to our node list
    state.audioNodes = PicobelMarkup.elementHooks(state.audioNodes);
    // state.audioNodes = PicobelAudio.setupListeners(state.audioNodes);
    state.audioNodes = _setupLocalListeners(state.audioNodes);

    // Replace audio elements in DOM with new markup
    // _replaceNodes(state.audioNodes, markup);

    // state.audioNodes = PicobelAudio.init(state.audioNodes);

    // Setup event listeners

    // Provide methods for external use?

    return {
        state
    };
}

export default Picobel;
<|endoftext|>


<commit_before>var GitHubApi     = require('github'),
    GithubApiTags = require('./'),
    path          = require('path'),
    ProgressBar   = require('progress'),
    moment        = require('moment');

var repoId = {
  'user': 'golang',
  'repo': 'go'
};

var github = new GitHubApi({
  version: '3.0.0'
});

var githubApiAuth = require('./github-api-auth.json');
if(githubApiAuth) {
  github.authenticate(githubApiAuth);
}

var gat = new GithubApiTags();

var bar = new ProgressBar('Fetching tag/commit :current/:total [:bar] :percent :etas', { total: 100 });

var tagChanged = function() {
  bar.total = this.tagsAll * 2;
  bar.tick();
};
gat.on('tag', tagChanged);
gat.on('tag-commit', tagChanged);

var byAuthorDateAsc = function(tagA, tagB) {
  return githubCompareDates(
    tagA.commit.author.date,
    tagB.commit.author.date
  );
};
var githubCompareDates = function(dateStrA, dateStrB) {
  return moment(dateStrA).diff(dateStrB);
};

console.log('...');
gat.fetch(repoId, github)
.then(function(tags) {
  var tagsSortedDateDesc = tags.sort(byAuthorDateAsc).reverse();

  console.log('Tags sorted:');
  console.log(tagsSortedDateDesc);
});
<commit_msg>Improve example. Remove unused code.<commit_after>var GitHubApi     = require('github'),
    GithubApiTags = require('./'),
    ProgressBar   = require('progress'),
    moment        = require('moment');

var repoId = {
  'user': 'golang',
  'repo': 'go'
};

var github = new GitHubApi({
  version: '3.0.0'
});

var githubApiAuth = require('./config/github-api-auth.json');
if(githubApiAuth) {
  github.authenticate(githubApiAuth);
}

var gat = new GithubApiTags();

var bar = new ProgressBar('Fetching tag/commit :current/:total [:bar] :percent :etas', { total: 100 });

var tagChanged = function() {
  bar.total = this.tagsAll * 2;
  bar.tick();
};
gat.on('tag', tagChanged);
gat.on('tag-commit', tagChanged);

var byAuthorDateAsc = function(tagA, tagB) {
  return githubCompareDates(
    tagA.commit.author.date,
    tagB.commit.author.date
  );
};
var githubCompareDates = function(dateStrA, dateStrB) {
  return moment(dateStrA).diff(dateStrB);
};

console.log('...');
gat.fetch(repoId, github)
.then(function(tags) {
  var tagsSortedDateDesc = tags.sort(byAuthorDateAsc).reverse();

  console.log('Tags sorted.');
  console.log('Latest tag: ');
  console.log(tagsSortedDateDesc[0]);
});
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides class sap.ui.dt.plugin.DragDrop.
sap.ui.define([
	'sap/ui/dt/Plugin',
	'sap/ui/dt/DOMUtil'
],
function(Plugin, DOMUtil) {
	"use strict";

	/**
	 * Constructor for a new DragDrop.
	 *
	 * @param {string} [sId] id for the new object, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new object
	 *
	 * @class
	 * The DragDrop plugin is an abstract plugin to enable drag and drop functionallity of the Overlays
	 * This Plugin should be overriden by the D&D plugin implementations, the abstract functions should be ussed to performe actions
	 * @extends sap.ui.dt.plugin.Plugin
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @constructor
	 * @private
	 * @since 1.30
	 * @alias sap.ui.dt.plugin.DragDrop
	 * @experimental Since 1.30. This class is experimental and provides only limited functionality. Also the API might be changed in future.
	 */
	var DragDrop = Plugin.extend("sap.ui.dt.plugin.DragDrop", /** @lends sap.ui.dt.plugin.DragDrop.prototype */ {
		metadata : {
			abstract : true,
			// ---- object ----

			// ---- control specific ----
			library : "sap.ui.dt",
			properties : {
			},
			associations : {
			},
			events : {
			}
		}
	});

	/*
	 * @private
	 */
	DragDrop.prototype.init = function() {
		Plugin.prototype.init.apply(this, arguments);

		this._oOverlayDelegate = {
			"onAfterRendering" : this._checkDraggable
		};
	};

	/**
	 * @override
	 * @param {sap.ui.dt.Overlay} an Overlay which should be registered
	 */
	DragDrop.prototype.registerOverlay = function(oOverlay) {
		oOverlay.addEventDelegate(this._oOverlayDelegate, this);

		oOverlay.attachEvent("draggableChange", this._onDraggableChange, this);

		if (oOverlay.isDraggable()) {
			this._attachDragEvents(oOverlay);
		}

		oOverlay.attachBrowserEvent("dragover", this._onDragOver, this);
		oOverlay.attachBrowserEvent("dragenter", this._onDragEnter, this);
	};

	/**
	 * @override
	 */
	DragDrop.prototype.registerAggregationOverlay = function(oAggregationOverlay) {
		oAggregationOverlay.attachDroppableChange(this._onAggregationDroppableChange, this);
	};

	/**
	 * @override
	 */
	DragDrop.prototype.deregisterOverlay = function(oOverlay) {
		oOverlay.removeEventDelegate(this._oOverlayDelegate, this);

		oOverlay.detachEvent("draggableChange", this._onDraggableChange, this);

		this._detachDragEvents(oOverlay);

		oOverlay.detachBrowserEvent("dragover", this._onDragOver, this);
		oOverlay.detachBrowserEvent("dragenter", this._onDragEnter, this);

	};

	/**
	 * @override
	 */
	DragDrop.prototype.deregisterAggregationOverlay = function(oAggregationOverlay) {
		oAggregationOverlay.detachDroppableChange(this._onAggregationDroppableChange, this);
	};

	/**
	 * @private
	 * @param {sap.ui.dt.Overlay} an Overlay to attach events to
	 */
	DragDrop.prototype._attachDragEvents = function(oOverlay) {
		oOverlay.attachBrowserEvent("dragstart", this._onDragStart, this);
		oOverlay.attachBrowserEvent("drag", this._onDrag, this);
		oOverlay.attachBrowserEvent("dragend", this._onDragEnd, this);
	};

	/**
	 * @private
	 * @param {sap.ui.dt.Overlay} an Overlay to detach events from
	 */
	DragDrop.prototype._detachDragEvents = function(oOverlay) {
		oOverlay.detachBrowserEvent("dragstart", this._onDragStart, this);
		oOverlay.detachBrowserEvent("dragend", this._onDragEnd, this);
		oOverlay.detachBrowserEvent("drag", this._onDrag, this);
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.onDraggableChange = function(oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onDragStart = function(oDraggedOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onDragEnd = function(oDraggedOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onDrag = function(oDraggedOverlay, oEvent) { };

	/**
	 * @return {boolean} return true to omit event.preventDefault
	 * @protected
	 */
	DragDrop.prototype.onDragEnter = function(oOverlay, oEvent) { };

	/**
	 * @return {boolean} return true to omit event.preventDefault
	 * @protected
	 */
	DragDrop.prototype.onDragOver = function(oOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDragEnter = function(oAggregationOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDragOver = function(oAggregationOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDragLeave = function(oAggregationOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDrop = function(oAggregationOverlay, oEvent) { };

	/**
	 * @private
	 */
	DragDrop.prototype._checkDraggable = function(oEvent) {
		var oOverlay = oEvent.srcControl;
		if (oOverlay.isDraggable()) {
			DOMUtil.setDraggable(oOverlay.$(), true);
		}
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDraggableChange = function(oEvent) {
		var oOverlay = oEvent.getSource();
		if (oOverlay.isDraggable()) {
			this._attachDragEvents(oOverlay);
		} else {
			this._detachDragEvents(oOverlay);
		}

		this.onDraggableChange(oOverlay, oEvent);
	};
	/**
	 * @private
	 */
	DragDrop.prototype._onDragStart = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this._oDraggedOverlay = oOverlay;

		oEvent.stopPropagation();

		if (oEvent && oEvent.originalEvent && oEvent.originalEvent.dataTransfer && oEvent.originalEvent.dataTransfer.setData) {
			// Fix for Firfox - Firefox only fires drag events when data is set
			oEvent.originalEvent.dataTransfer.setData('text/plain', '');
		}

		this.showGhost(oOverlay, oEvent);
		this.onDragStart(oOverlay, oEvent);
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.showGhost = function(oOverlay, oEvent) {
		var that = this;

		// not supported in IE10+
		if (oEvent && oEvent.originalEvent && oEvent.originalEvent.dataTransfer && oEvent.originalEvent.dataTransfer.setDragImage) {
			this._$ghost = this.createGhost(oOverlay, oEvent);

			// ghost should be visible to set it as dragImage
			this._$ghost.appendTo("body");
			// if ghost will be removed without timeout, setDragImage won't work
			setTimeout(function() {
				that._removeGhost();
			}, 0);
			oEvent.originalEvent.dataTransfer.setDragImage(
				this._$ghost.get(0),
				oEvent.originalEvent.pageX - this.getDraggedOverlay().$().offset().left,
				oEvent.originalEvent.pageY - this.getDraggedOverlay().$().offset().top
			);
		}
	};

	/**
	 * @private
	 */
	DragDrop.prototype._removeGhost = function() {
		this.removeGhost();
		delete this._$ghost;
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.removeGhost = function() {
		var $ghost = this.getGhost();
		if ($ghost) {
			$ghost.remove();
		}
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.createGhost = function(oOverlay) {
		var oGhostDom = oOverlay.getAssociatedDomRef();
		var $ghost;
		if (!oGhostDom) {
			oGhostDom = this._getAssociatedDomCopy(oOverlay);
			$ghost = jQuery(oGhostDom);
		} else {
			$ghost = jQuery("

");
			DOMUtil.cloneDOMAndStyles(oGhostDom, $ghost);
		}

		var $ghostWrapper = jQuery("

").addClass("sapUiDtDragGhostWrapper");
		return $ghostWrapper.append($ghost.addClass("sapUiDtDragGhost"));
	};

	/**
	 * @private
	 */
	DragDrop.prototype._getAssociatedDomCopy = function(oOverlay) {
		var that = this;

		var $DomCopy = jQuery("

");

		oOverlay.getAggregationOverlays().forEach(function(oAggregationOverlay) {
			oAggregationOverlay.getChildren().forEach(function(oChildOverlay) {
				var oChildDom = oChildOverlay.getAssociatedDomRef();
				if (oChildDom) {
					DOMUtil.cloneDOMAndStyles(oChildDom, $DomCopy);
				} else {
					DOMUtil.cloneDOMAndStyles(that._getAssociatedDomCopy(oChildOverlay), $DomCopy);
				}
			});
		});

		return $DomCopy.get(0);
	};

	/**
	 * @protected
	 * @return {jQuery} jQuery object drag ghost
	 */
	DragDrop.prototype.getGhost = function() {
		return this._$ghost;
	};

	/**
	 * returns the dragged overlay (only during drag&drop)
	 * @public
	 * @return {sap.ui.dt.Overlay} overlays which is dragged
	 */
	DragDrop.prototype.getDraggedOverlay = function() {
		return this._oDraggedOverlay;
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDragEnd = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this._removeGhost();

		this.onDragEnd(oOverlay, oEvent);

		delete this._oDraggedOverlay;
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDrag = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);

		this.onDrag(oOverlay, oEvent);

		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDragEnter = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		var oAggregationOverlay = oOverlay.getParent();
		var bInDroppableAggregation = oAggregationOverlay && oAggregationOverlay.isDroppable && oAggregationOverlay.isDroppable();
		if (bInDroppableAggregation) {
			//if "true" returned, propagation won't be canceled
			if (!this.onDragEnter(oOverlay, oEvent)) {
				oEvent.stopPropagation();
			}
		}

		oEvent.preventDefault();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDragOver = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		var oAggregationOverlay = oOverlay.getParent();
		var bInDroppableAggregation = oAggregationOverlay && oAggregationOverlay.isDroppable && oAggregationOverlay.isDroppable();
		if (bInDroppableAggregation) {
			//if "true" returned, propagation won't be canceled
			if (!this.onDragOver(oOverlay, oEvent)) {
				oEvent.stopPropagation();
			}
		}

		oEvent.preventDefault();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDroppableChange = function(oEvent) {
		var oAggregationOverlay = oEvent.getSource();
		var bDroppable = oEvent.getParameter("droppable");

		if (bDroppable) {
			this._attachAggregationOverlayEvents(oAggregationOverlay);
		} else {
			this._detachAggregationOverlayEvents(oAggregationOverlay);
		}

	};

	/**
	 * @private
	 */
	DragDrop.prototype._attachAggregationOverlayEvents = function(oAggregationOverlay) {

		oAggregationOverlay.attachBrowserEvent("dragenter", this._onAggregationDragEnter, this);
		oAggregationOverlay.attachBrowserEvent("dragover", this._onAggregationDragOver, this);
		oAggregationOverlay.attachBrowserEvent("dragleave", this._onAggregationDragLeave, this);
		oAggregationOverlay.attachBrowserEvent("drop", this._onAggregationDrop, this);
	};

	/**
	 * @private
	 */
	DragDrop.prototype._detachAggregationOverlayEvents = function(oAggregationOverlay) {
		oAggregationOverlay.detachBrowserEvent("dragenter", this._onAggregationDragEnter, this);
		oAggregationOverlay.detachBrowserEvent("dragover", this._onAggregationDragOver, this);
		oAggregationOverlay.detachBrowserEvent("dragleave", this._onAggregationDragLeave, this);
		oAggregationOverlay.detachBrowserEvent("drop", this._onAggregationDrop, this);
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDragEnter = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDragEnter(oAggregationOverlay, oEvent);

		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDragOver = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDragOver(oAggregationOverlay, oEvent);

		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDragLeave = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDragLeave(oAggregationOverlay, oEvent);

		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDrop = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDrop(oAggregationOverlay, oEvent);

		oEvent.stopPropagation();
	};

	return DragDrop;
}, /* bExport= */ true);<commit_msg>[INTERNAL] sap.ui.dt.plugin.DragDrop fix ie&firefox dataTransfer<commit_after>/*!
 * ${copyright}
 */

// Provides class sap.ui.dt.plugin.DragDrop.
sap.ui.define([
	'sap/ui/dt/Plugin',
	'sap/ui/dt/DOMUtil'
],
function(Plugin, DOMUtil) {
	"use strict";

	/**
	 * Constructor for a new DragDrop.
	 *
	 * @param {string} [sId] id for the new object, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new object
	 *
	 * @class
	 * The DragDrop plugin is an abstract plugin to enable drag and drop functionallity of the Overlays
	 * This Plugin should be overriden by the D&D plugin implementations, the abstract functions should be ussed to performe actions
	 * @extends sap.ui.dt.plugin.Plugin
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @constructor
	 * @private
	 * @since 1.30
	 * @alias sap.ui.dt.plugin.DragDrop
	 * @experimental Since 1.30. This class is experimental and provides only limited functionality. Also the API might be changed in future.
	 */
	var DragDrop = Plugin.extend("sap.ui.dt.plugin.DragDrop", /** @lends sap.ui.dt.plugin.DragDrop.prototype */ {
		metadata : {
			abstract : true,
			// ---- object ----

			// ---- control specific ----
			library : "sap.ui.dt",
			properties : {
			},
			associations : {
			},
			events : {
			}
		}
	});

	/*
	 * @private
	 */
	DragDrop.prototype.init = function() {
		Plugin.prototype.init.apply(this, arguments);

		this._oOverlayDelegate = {
			"onAfterRendering" : this._checkDraggable
		};
	};

	/**
	 * @override
	 * @param {sap.ui.dt.Overlay} an Overlay which should be registered
	 */
	DragDrop.prototype.registerOverlay = function(oOverlay) {
		oOverlay.addEventDelegate(this._oOverlayDelegate, this);

		oOverlay.attachEvent("draggableChange", this._onDraggableChange, this);

		if (oOverlay.isDraggable()) {
			this._attachDragEvents(oOverlay);
		}

		oOverlay.attachBrowserEvent("dragover", this._onDragOver, this);
		oOverlay.attachBrowserEvent("dragenter", this._onDragEnter, this);
	};

	/**
	 * @override
	 */
	DragDrop.prototype.registerAggregationOverlay = function(oAggregationOverlay) {
		oAggregationOverlay.attachDroppableChange(this._onAggregationDroppableChange, this);
	};

	/**
	 * @override
	 */
	DragDrop.prototype.deregisterOverlay = function(oOverlay) {
		oOverlay.removeEventDelegate(this._oOverlayDelegate, this);

		oOverlay.detachEvent("draggableChange", this._onDraggableChange, this);

		this._detachDragEvents(oOverlay);

		oOverlay.detachBrowserEvent("dragover", this._onDragOver, this);
		oOverlay.detachBrowserEvent("dragenter", this._onDragEnter, this);

	};

	/**
	 * @override
	 */
	DragDrop.prototype.deregisterAggregationOverlay = function(oAggregationOverlay) {
		oAggregationOverlay.detachDroppableChange(this._onAggregationDroppableChange, this);
	};

	/**
	 * @private
	 * @param {sap.ui.dt.Overlay} an Overlay to attach events to
	 */
	DragDrop.prototype._attachDragEvents = function(oOverlay) {
		oOverlay.attachBrowserEvent("dragstart", this._onDragStart, this);
		oOverlay.attachBrowserEvent("drag", this._onDrag, this);
		oOverlay.attachBrowserEvent("dragend", this._onDragEnd, this);
	};

	/**
	 * @private
	 * @param {sap.ui.dt.Overlay} an Overlay to detach events from
	 */
	DragDrop.prototype._detachDragEvents = function(oOverlay) {
		oOverlay.detachBrowserEvent("dragstart", this._onDragStart, this);
		oOverlay.detachBrowserEvent("dragend", this._onDragEnd, this);
		oOverlay.detachBrowserEvent("drag", this._onDrag, this);
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.onDraggableChange = function(oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onDragStart = function(oDraggedOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onDragEnd = function(oDraggedOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onDrag = function(oDraggedOverlay, oEvent) { };

	/**
	 * @return {boolean} return true to omit event.preventDefault
	 * @protected
	 */
	DragDrop.prototype.onDragEnter = function(oOverlay, oEvent) { };

	/**
	 * @return {boolean} return true to omit event.preventDefault
	 * @protected
	 */
	DragDrop.prototype.onDragOver = function(oOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDragEnter = function(oAggregationOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDragOver = function(oAggregationOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDragLeave = function(oAggregationOverlay, oEvent) { };

	/**
	 * @protected
	 */
	DragDrop.prototype.onAggregationDrop = function(oAggregationOverlay, oEvent) { };

	/**
	 * @private
	 */
	DragDrop.prototype._checkDraggable = function(oEvent) {
		var oOverlay = oEvent.srcControl;
		if (oOverlay.isDraggable()) {
			DOMUtil.setDraggable(oOverlay.$(), true);
		}
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDraggableChange = function(oEvent) {
		var oOverlay = oEvent.getSource();
		if (oOverlay.isDraggable()) {
			this._attachDragEvents(oOverlay);
		} else {
			this._detachDragEvents(oOverlay);
		}

		this.onDraggableChange(oOverlay, oEvent);
	};
	/**
	 * @private
	 */
	DragDrop.prototype._onDragStart = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this._oDraggedOverlay = oOverlay;

		oEvent.stopPropagation();

		// Fix for Firfox - Firefox only fires drag events when data is set
		if (sap.ui.Device.browser.firefox) {
			oEvent.originalEvent.dataTransfer.setData('text/plain', '');
		}

		this.showGhost(oOverlay, oEvent);
		this.onDragStart(oOverlay, oEvent);
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.showGhost = function(oOverlay, oEvent) {
		var that = this;

		// not supported in IE10+
		if (oEvent && oEvent.originalEvent && oEvent.originalEvent.dataTransfer && oEvent.originalEvent.dataTransfer.setDragImage) {
			this._$ghost = this.createGhost(oOverlay, oEvent);

			// ghost should be visible to set it as dragImage
			this._$ghost.appendTo("body");
			// if ghost will be removed without timeout, setDragImage won't work
			setTimeout(function() {
				that._removeGhost();
			}, 0);
			oEvent.originalEvent.dataTransfer.setDragImage(
				this._$ghost.get(0),
				oEvent.originalEvent.pageX - this.getDraggedOverlay().$().offset().left,
				oEvent.originalEvent.pageY - this.getDraggedOverlay().$().offset().top
			);
		}
	};

	/**
	 * @private
	 */
	DragDrop.prototype._removeGhost = function() {
		this.removeGhost();
		delete this._$ghost;
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.removeGhost = function() {
		var $ghost = this.getGhost();
		if ($ghost) {
			$ghost.remove();
		}
	};

	/**
	 * @protected
	 */
	DragDrop.prototype.createGhost = function(oOverlay) {
		var oGhostDom = oOverlay.getAssociatedDomRef();
		var $ghost;
		if (!oGhostDom) {
			oGhostDom = this._getAssociatedDomCopy(oOverlay);
			$ghost = jQuery(oGhostDom);
		} else {
			$ghost = jQuery("

");
			DOMUtil.cloneDOMAndStyles(oGhostDom, $ghost);
		}

		var $ghostWrapper = jQuery("

").addClass("sapUiDtDragGhostWrapper");
		return $ghostWrapper.append($ghost.addClass("sapUiDtDragGhost"));
	};

	/**
	 * @private
	 */
	DragDrop.prototype._getAssociatedDomCopy = function(oOverlay) {
		var that = this;

		var $DomCopy = jQuery("

");

		oOverlay.getAggregationOverlays().forEach(function(oAggregationOverlay) {
			oAggregationOverlay.getChildren().forEach(function(oChildOverlay) {
				var oChildDom = oChildOverlay.getAssociatedDomRef();
				if (oChildDom) {
					DOMUtil.cloneDOMAndStyles(oChildDom, $DomCopy);
				} else {
					DOMUtil.cloneDOMAndStyles(that._getAssociatedDomCopy(oChildOverlay), $DomCopy);
				}
			});
		});

		return $DomCopy.get(0);
	};

	/**
	 * @protected
	 * @return {jQuery} jQuery object drag ghost
	 */
	DragDrop.prototype.getGhost = function() {
		return this._$ghost;
	};

	/**
	 * returns the dragged overlay (only during drag&drop)
	 * @public
	 * @return {sap.ui.dt.Overlay} overlays which is dragged
	 */
	DragDrop.prototype.getDraggedOverlay = function() {
		return this._oDraggedOverlay;
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDragEnd = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this._removeGhost();

		this.onDragEnd(oOverlay, oEvent);

		delete this._oDraggedOverlay;
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDrag = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);

		this.onDrag(oOverlay, oEvent);

		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDragEnter = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		var oAggregationOverlay = oOverlay.getParent();
		var bInDroppableAggregation = oAggregationOverlay && oAggregationOverlay.isDroppable && oAggregationOverlay.isDroppable();
		if (bInDroppableAggregation) {
			//if "true" returned, propagation won't be canceled
			if (!this.onDragEnter(oOverlay, oEvent)) {
				oEvent.stopPropagation();
			}
		}

		oEvent.preventDefault();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onDragOver = function(oEvent) {
		var oOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		var oAggregationOverlay = oOverlay.getParent();
		var bInDroppableAggregation = oAggregationOverlay && oAggregationOverlay.isDroppable && oAggregationOverlay.isDroppable();
		if (bInDroppableAggregation) {
			//if "true" returned, propagation won't be canceled
			if (!this.onDragOver(oOverlay, oEvent)) {
				oEvent.stopPropagation();
			}
		}

		oEvent.preventDefault();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDroppableChange = function(oEvent) {
		var oAggregationOverlay = oEvent.getSource();
		var bDroppable = oEvent.getParameter("droppable");

		if (bDroppable) {
			this._attachAggregationOverlayEvents(oAggregationOverlay);
		} else {
			this._detachAggregationOverlayEvents(oAggregationOverlay);
		}

	};

	/**
	 * @private
	 */
	DragDrop.prototype._attachAggregationOverlayEvents = function(oAggregationOverlay) {

		oAggregationOverlay.attachBrowserEvent("dragenter", this._onAggregationDragEnter, this);
		oAggregationOverlay.attachBrowserEvent("dragover", this._onAggregationDragOver, this);
		oAggregationOverlay.attachBrowserEvent("dragleave", this._onAggregationDragLeave, this);
		oAggregationOverlay.attachBrowserEvent("drop", this._onAggregationDrop, this);
	};

	/**
	 * @private
	 */
	DragDrop.prototype._detachAggregationOverlayEvents = function(oAggregationOverlay) {
		oAggregationOverlay.detachBrowserEvent("dragenter", this._onAggregationDragEnter, this);
		oAggregationOverlay.detachBrowserEvent("dragover", this._onAggregationDragOver, this);
		oAggregationOverlay.detachBrowserEvent("dragleave", this._onAggregationDragLeave, this);
		oAggregationOverlay.detachBrowserEvent("drop", this._onAggregationDrop, this);
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDragEnter = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDragEnter(oAggregationOverlay, oEvent);

		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDragOver = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDragOver(oAggregationOverlay, oEvent);

		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDragLeave = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDragLeave(oAggregationOverlay, oEvent);

		oEvent.preventDefault();
		oEvent.stopPropagation();
	};

	/**
	 * @private
	 */
	DragDrop.prototype._onAggregationDrop = function(oEvent) {
		var oAggregationOverlay = sap.ui.getCore().byId(oEvent.currentTarget.id);
		this.onAggregationDrop(oAggregationOverlay, oEvent);

		oEvent.stopPropagation();
	};

	return DragDrop;
}, /* bExport= */ true);<|endoftext|>


<commit_before>/* eslint no-console: ["allow"] */

const fs = require('fs')
const ReplayParser = require('./index.js')

const reppi = fs.createReadStream(process.argv[2])
  .pipe(new ReplayParser())

reppi.on('replayHeader', header => {
  const {
    gameName,
    mapName,
    gameType,
    gameSubtype,
    players,
    durationFrames,
  } = header
  console.log(`${gameName} on ${mapName} (Game type ${gameType}, ${gameSubtype})`)
  const minutes = Math.floor(durationFrames / 24 / 60)
  const seconds = Math.floor(durationFrames / 24) % 60
  console.log(`Duration: ${minutes}:${seconds}`)
  for (const { name, id, race, team, isComputer } of players) {
    if (isComputer) {
      console.log(`Computer ${name} (${id}): Race ${race}, team ${team}`)
    } else {
      console.log(`Player ${name} (${id}): Race ${race}, team ${team}`)
    }
  }
})

reppi.on('data', ({ id, frame, player }) => {
  const name = ReplayParser.commands()[id].name
  console.log(`Command ${name} @ frame ${frame} for player ${player}`)
})

reppi.on('error', err => {
  console.log(`Rip rap nib nab ${err}`)
})
<commit_msg>Update the example to show that replay seed can be used as timestamp<commit_after>/* eslint no-console: ["allow"] */

const fs = require('fs')
const ReplayParser = require('./index.js')

const reppi = fs.createReadStream(process.argv[2])
  .pipe(new ReplayParser())

reppi.on('replayHeader', header => {
  const {
    gameName,
    mapName,
    gameType,
    gameSubtype,
    players,
    durationFrames,
    seed,
  } = header
  console.log(`${gameName} on ${mapName} (Game type ${gameType}, ${gameSubtype})`)

  // Seed is just generated with C stdlib `time()`, so it can be used to figure out timestamp.
  const date = new Date(seed * 1000)
  console.log(`Played on ${date.toLocaleString()}`)

  const minutes = Math.floor(durationFrames / 24 / 60)
  const seconds = Math.floor(durationFrames / 24) % 60
  console.log(`Duration: ${minutes}:${seconds}`)
  for (const { name, id, race, team, isComputer } of players) {
    if (isComputer) {
      console.log(`Computer ${name} (${id}): Race ${race}, team ${team}`)
    } else {
      console.log(`Player ${name} (${id}): Race ${race}, team ${team}`)
    }
  }
})

// `ReplayParser` is a `Transform` stream, which transforms binary data to replay actions.
// But if we arent't interested in actions, we'll need to call `reppi.resume()`, explicity.
reppi.on('data', ({ id, frame, player }) => {
  const name = ReplayParser.commands()[id].name
  console.log(`Command ${name} @ frame ${frame} for player ${player}`)
})

reppi.on('error', err => {
  console.log(`Rip rap nib nab ${err}`)
})
<|endoftext|>


<commit_before>'use strict';

var gulp = require('./.').create({repl: true});

function rand(){
  return Math.floor((10000 - 1) * Math.random());
}

gulp.task('webpack', function(next){
  setTimeout(next, rand());
});

gulp.task('less', function(next){
  setTimeout(next, rand());
});

gulp.task('jade', function(next){
  setTimeout(next, rand());
});

gulp.task('serve', function(next){
  setTimeout(next, rand());
});

gulp.task('watch', function(next){
  setTimeout(next, rand());
});

gulp.stack(
  'serve', 'less',
  gulp.stack('webpack', 'jade', {wait: true}),
  'watch'
)();
<commit_msg>[ci skip] included errors to see how to handle their loggin<commit_after>'use strict';

var gulp = require('./.').create({repl: true});

function rand(){
  return Math.floor((100 - 1) * Math.random());
}

gulp.task('autoprefixer', function(next){
  setTimeout(next, rand());
});

gulp.task('less', ['autoprefixer'], function(next){
  setTimeout(next, rand());
});

gulp.task('jade', function(next){
  setTimeout(next, rand());
});

gulp.task('serve', function(next){
  setTimeout(next, rand());
});

gulp.task('watch', function(next){
  setTimeout(next, rand());
});

gulp.task(['serve'], function webpack(next){
  setTimeout(function(){
    if([0, 1][Math.random() > 0.4 ? 1 : 0]){
      throw new Error('fail!');
    } else {
      next();
    }
  }, rand());
});

gulp.stack(
  'less',
  gulp.stack('webpack', 'jade', {wait: true}),
  'watch'
)();
<|endoftext|>


<commit_before>var woodpecker = require('woodpecker');

woodpecker.service('logger', function() {
  this.log = function(msg) {
    console.log(msg);
  }
});

woodpecker.service('userDAO', function() {
  this.getUser = function(id) {
    return {
      id: id,
      name: 'Theo',
      age: 28
    }
  }
});

woodpecker.service('userService', function(logger, userDAO) {
  this.print = function() {
    logger.log(userDAO.getUser().name);
  }
});

woodpecker.service('userService').print();
<commit_msg>Update exmaple<commit_after>var woodpecker = require('woodpecker');

function Logger() {
  this.log = function(msg) {
    console.log(msg);
  }
}

function UserDAO() {
  this.getUser = function(id) {
    return {
      id: id,
      name: 'Theo',
      age: 28
    }
  }
}

function UserService(logger, userDAO) {
  this.print = function() {
    logger.log(userDAO.getUser().name);
  }
}

woodpecker.service('logger', Logger);
woodpecker.service('userDAO', UserDAO);
woodpecker.service('userService', UserService);

var userService = woodpecker.service('userService');
userService.print();
<|endoftext|>


<commit_before>'use strict';

var fs = require('fs');
var through = require('through2');
var Promise = require('es6-promise').Promise;

var Runtime = require('./.');

var runtime = Runtime.create({
  reduceStack: function (stack, site) {
    if (typeof site === 'function') {
      stack.push({
        fn: site,
        label: site.stack instanceof Runtime
          ? site.stack.tree(this).label
          : site.label || site.name || 'anonymous'
      });
    }
    return stack;
  },
  onHandleStart: function (site, stack) {
    console.log('`%s` started', site.label);
    site.time = process.hrtime();
  },
  onHandleEnd: function (site, stack) {
    var diff = process.hrtime(site.time);
    console.log('`%s` ended after %s ms',
      site.label, diff[0]*1e+3 + Math.floor(diff[1]*1e-6)
    );
  },
  onHandleError: function (error, site) {
    var file = error.stack.match(/\/[^)]+/).pop();
    console.log('`%s` errored at', site.label, file);
    console.log(error.stack);
  }
});

function foo (next, value) {
  console.log('received `%s`', value);
  setTimeout(function () {
    if (Math.random() > 0.7) {
      next(new Error('bad luck today!'));
    } else {
      next(null, 'Callback');
    }
  }, Math.random()*10);
}

function bar (next, value) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (Math.random() > 0.9) {
        reject(new Error('bad luck today!'));
      } else {
        resolve(value + 'Promise');
      }
    }, Math.random()*10);
  });
}

function baz (next, value) {
  var stream = through.obj(
    function write (chunk, enc, cb) {
      if (Math.random() > 0.95) {
        cb(new Error('bad luck today!'));
      } else {
        cb();
      }
    },
    function end () {
      next(value + 'Stream');
    }
  );

  return fs.createReadStream(__filename).pipe(stream);
}

var composed = runtime.stack(foo, bar, baz, {wait: true});

// lets make it pretty
console.log('Stack tree -> %s',
  require('archy')(composed.stack.tree(runtime))
);

composed('insert args here', function onStackEnd (err, result) {
  if (err) {
    console.log(err.stack);
  } else {
    console.log('result: `%s`', result);
  }
});
<commit_msg>dev: update example<commit_after>'use strict';

var fs = require('fs');
var through = require('through2');
var Promise = require('es6-promise').Promise;

var Runtime = require('./.');

var runtime = Runtime.create({
  reduceStack: function (stack, site) {
    if (typeof site === 'function') {
      stack.push({
        fn: site,
        label: site.stack instanceof Runtime.Stack
          ? this.tree(site.stack).label
          : site.label || site.name || 'anonymous'
      });
    }
    return stack;
  },
  onHandleStart: function (site, stack) {
    console.log('`%s` started', site.label);
    site.time = process.hrtime();
  },
  onHandleEnd: function (site, stack) {
    var diff = process.hrtime(site.time);
    console.log('`%s` ended after %s ms',
      site.label, diff[0]*1e+3 + Math.floor(diff[1]*1e-6)
    );
  },
  onHandleError: function (error, site) {
    var file = error.stack.match(/\/[^)]+/).pop();
    console.log('`%s` errored at', site.label, file);
    console.log(error.stack);
  }
});

function foo (next, value) {
  console.log('received `%s`', value);
  setTimeout(function () {
    if (Math.random() > 0.7) {
      next(new Error('bad luck today!'));
    } else {
      next(null, 'Callback');
    }
  }, Math.random()*10);
}

function bar (next, value) {
  return new Promise(function (resolve, reject) {
    setTimeout(function () {
      if (Math.random() > 0.9) {
        reject(new Error('bad luck today!'));
      } else {
        resolve(value + 'Promise');
      }
    }, Math.random()*10);
  });
}

function baz (next, value) {
  var stream = through.obj(
    function write (chunk, enc, cb) {
      if (Math.random() > 0.95) {
        cb(new Error('bad luck today!'));
      } else {
        cb();
      }
    },
    function end () {
      next(value + 'Stream');
    }
  );

  return fs.createReadStream(__filename).pipe(stream);
}

var stack = runtime.stack(foo, bar);
var composed = runtime.stack(foo, stack, bar, baz, {wait: true});

// lets make it pretty
console.log('Stack tree -> %s',
  require('archy')(runtime.tree(composed.stack))
);

composed('insert args here', function onStackEnd (err, result) {
  if (err) {
    console.log(err.stack);
  } else {
    console.log('result: `%s`', result);
  }
});
<|endoftext|>