<commit_before>/* global window */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */

import React from 'react'
import PropTypes from 'prop-types'
import Link from 'gatsby-link'

import { s } from '../utils/breakpoints'

class Nav extends React.Component {
  constructor(props) {
    super(props)

    this.navTitles = ['home', 'angebot', 'projekte', 'kenntnisse']
    this.navHtmlElems = new Map()

    const { pathname } = props
    const selectedNavItemId = this.getInitialSelectedNav(
      pathname,
      this.navTitles
    )
    const indicatorWrapperStyle = { display: 'none' }

    this.state = { selectedNavItemId, indicatorWrapperStyle }
  }

  componentDidMount() {
    const selectedId = this.state.selectedNavItemId

    window.setTimeout(() => {
      if (selectedId === null || this.navHtmlElems.has(selectedId) === false) {
        return
      }

      const selectedHtmlElem = this.navHtmlElems.get(selectedId)
      this.moveSelectionIndicatorTo(selectedHtmlElem)

      const mql = window.matchMedia(`(max-width: ${s})`)
      mql.addListener(
        function() {
          return () =>
            this.moveSelectionIndicatorTo(
              this.navHtmlElems.get(this.state.selectedNavItemId)
            )
        }.bind(this)()
      )
    }, 500)
  }

  componentWillReceiveProps(nextProps) {
    const home = 'home'
    const { pathname } = nextProps
    let matchedNavTitle = ''
    let curPageHasOneOfNavTitlesList = []

    this.navTitles.forEach((title) => {
      let isHome = pathname === '/' && title === home
      let hasNavTitleMatchingPathname =
        pathname.search(new RegExp(`/${title}/?`)) !== -1
      if (hasNavTitleMatchingPathname && matchedNavTitle === '') {
        matchedNavTitle = title
      }
      curPageHasOneOfNavTitlesList.push(isHome || hasNavTitleMatchingPathname)
    })

    const setNavActiveState = (entry) => {
      if (entry !== null) {
        const selectedHtmlElem = this.navHtmlElems.get(entry)
        this.setState({ selectedNavItemId: entry })
        this.moveSelectionIndicatorTo(selectedHtmlElem)
      }

      // could be an 'else' but better be explicit
      if (entry === null) {
        this.setState({ selectedNavItemId: null })
        this.moveSelectionIndicatorTo(null)
      }
    }

    if (pathname === '/') {
      // set selectedNavItemId if root no matter
      // if other links than nav home link was used
      setNavActiveState(home)
    } else if (curPageHasOneOfNavTitlesList.includes(true) === false) {
      // unset selectedNavItemId cause current
      // page is not part of any navTitle
      setNavActiveState(null)
    } else {
      // set selectedNavItemId to matching nav entry
      setNavActiveState(matchedNavTitle)
    }
  }

  componentDidUpdate(prevProps, prevState) {
    const selectedId = this.state.selectedNavItemId

    if (selectedId === null || this.navHtmlElems.has(selectedId) === false) {
      return
    }

    if (this.state.selectedNavItemId !== prevState.selectedNavItemId) {
      const selectedHtmlElem = this.navHtmlElems.get(selectedId)
      this.moveSelectionIndicatorTo(selectedHtmlElem)
    }
  }

  getInitialSelectedNav(path, navTitles) {
    let selectedNavItemId = ''

    navTitles.forEach((title) => {
      let hasNavTitleMatchingPathname =
        path.search(new RegExp(`/${title}/?`)) !== -1
      if (hasNavTitleMatchingPathname) {
        selectedNavItemId = title
      }
    })

    if (path === '/') {
      selectedNavItemId = 'home'
    }

    return selectedNavItemId
  }

  handleClick = (e) => {
    const newNavItemId = e.currentTarget.id
    const { selectedNavItemId } = this.state

    if (newNavItemId !== selectedNavItemId) {
      this.setState({ selectedNavItemId: newNavItemId })
    }
  }

  moveSelectionIndicatorTo(newSelectedItem) {
    let indicatorWrapperStyle = { display: 'none' }
    if (newSelectedItem !== null && newSelectedItem !== undefined) {
      const {
        clientHeight,
        clientWidth,
        offsetLeft,
        offsetTop
      } = newSelectedItem
      indicatorWrapperStyle = {
        height: clientHeight,
        transform: `translateX(${Math.floor(
          offsetLeft
        )}px) translateY(${Math.floor(offsetTop)}px)`,
        width: clientWidth
      }
    }
    this.setState({ indicatorWrapperStyle })
  }

  render() {
    let navLinks = []

    this.navTitles.forEach((item) => {
      let liElem = {}
      let linkTarget = {}
      let dynamicAttrs = {}

      if (item === this.state.selectedNavItemId) {
        dynamicAttrs['data-selected'] = true
      }

      linkTarget = item === 'home' ? '/' : `/${item}/`

      liElem = (
        // eslint-disable-next-line jsx-a11y/click-events-have-key-events

 {
            this.navHtmlElems.set(item, navHtmlElem)
          }}
          {...dynamicAttrs}>
          <Link to={linkTarget}>
            {item[0].toUpperCase().concat(item.substr(1))}
          </Link>

      )
      navLinks.push(liElem)
    })

    return (

          {navLinks}

    )
  }
}

Nav.propTypes = {
  pathname: PropTypes.string.isRequired
}

export default Nav
<commit_msg>add bilingual support for Nav component<commit_after>/* global window */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */

import React from 'react'
import PropTypes from 'prop-types'

import { translate } from 'react-i18next'
import Link from 'gatsby-link'

import { s } from '../utils/breakpoints'

class Nav extends React.Component {
  constructor(props) {
    super(props)

    this.navHtmlElems = new Map()

    const { pathname } = props
    const selectedNavItemId = this.getInitialSelectedNav(pathname)
    const indicatorWrapperStyle = { display: 'none' }

    this.state = { selectedNavItemId, indicatorWrapperStyle }
  }

  componentDidMount() {
    const selectedId = this.state.selectedNavItemId

    window.setTimeout(() => {
      if (selectedId === null || this.navHtmlElems.has(selectedId) === false) {
        return
      }

      const selectedHtmlElem = this.navHtmlElems.get(selectedId)
      this.moveSelectionIndicatorTo(selectedHtmlElem)

      const mql = window.matchMedia(`(max-width: ${s})`)
      mql.addListener(
        function() {
          return () =>
            this.moveSelectionIndicatorTo(
              this.navHtmlElems.get(this.state.selectedNavItemId)
            )
        }.bind(this)()
      )
    }, 500)
  }

  componentWillReceiveProps(nextProps) {
    const home = 'home'
    const { pathname } = nextProps
    let matchedNavTitle = ''
    let curPageHasOneOfNavTitlesList = []
    let getTranslatedPathnameWord = () =>
      this.getBilingualNavTitles()[pathname.replace(/\//g, '')]

    this.getNavTitles().forEach((title) => {
      let isHome = pathname === '/' && title === home
      let hasNavTitleMatchingPathname =
        pathname.search(new RegExp(`/${title}/?`)) !== -1 ||
        getTranslatedPathnameWord() === title
      if (hasNavTitleMatchingPathname) {
        matchedNavTitle = title
      }
      curPageHasOneOfNavTitlesList.push(isHome || hasNavTitleMatchingPathname)
    })

    const setNavActiveState = (entry) => {
      if (entry !== null) {
        const selectedHtmlElem = this.navHtmlElems.get(entry)
        this.setState({ selectedNavItemId: entry })
        this.moveSelectionIndicatorTo(selectedHtmlElem)
      }

      // could be an 'else' but better be explicit
      if (entry === null) {
        this.setState({ selectedNavItemId: null })
        this.moveSelectionIndicatorTo(null)
      }
    }

    if (pathname === '/') {
      // set selectedNavItemId if root - no matter
      // if other links than nav home link was used
      setNavActiveState(home)
    } else if (curPageHasOneOfNavTitlesList.includes(true) === false) {
      // unset selectedNavItemId cause current
      // page is not part of any navTitle
      setNavActiveState(null)
    } else {
      // set selectedNavItemId to matching nav entry
      setNavActiveState(matchedNavTitle)
    }
  }

  componentDidUpdate(prevProps, prevState) {
    const selectedId = this.state.selectedNavItemId

    if (selectedId === null || this.navHtmlElems.has(selectedId) === false) {
      return
    }

    if (this.state.selectedNavItemId !== prevState.selectedNavItemId) {
      const selectedHtmlElem = this.navHtmlElems.get(selectedId)
      this.moveSelectionIndicatorTo(selectedHtmlElem)
    }
  }

  getNavTitles = () => [
    'home',
    this.props.t('offer'),
    this.props.t('projects'),
    this.props.t('skills')
  ]

  getBilingualNavTitles = () => {
    return {
      offer: 'angebot',
      angebot: 'offer',
      projects: 'projekte',
      projekte: 'projects',
      skills: 'kenntnisse',
      kenntnisse: 'skills'
    }
  }

  getInitialSelectedNav = (path) => {
    let selectedNavItemId = ''

    Object.keys(this.getBilingualNavTitles()).some((title) => {
      let hasNavTitleMatchingPathname =
        path.search(new RegExp(`/${title}/?`)) !== -1
      if (hasNavTitleMatchingPathname) {
        selectedNavItemId = title
        return true
      }
      return false
    })

    if (path === '/') {
      selectedNavItemId = 'home'
    }

    return selectedNavItemId
  }

  handleClick = (e) => {
    const newNavItemId = e.currentTarget.id
    const { selectedNavItemId } = this.state

    if (newNavItemId !== selectedNavItemId) {
      this.setState({ selectedNavItemId: newNavItemId })
    }
  }

  moveSelectionIndicatorTo(newSelectedItem) {
    let indicatorWrapperStyle = { display: 'none' }
    if (newSelectedItem !== null && newSelectedItem !== undefined) {
      const {
        clientHeight,
        clientWidth,
        offsetLeft,
        offsetTop
      } = newSelectedItem
      indicatorWrapperStyle = {
        height: clientHeight,
        transform: `translateX(${Math.floor(
          offsetLeft
        )}px) translateY(${Math.floor(offsetTop)}px)`,
        width: clientWidth
      }
    }
    this.setState({ indicatorWrapperStyle })
  }

  render() {
    let navLinks = []

    this.getNavTitles().forEach((item) => {
      let liElem = {}
      let linkTarget = {}
      let dynamicAttrs = {}
      let selNavId = this.state.selectedNavItemId
      let bilingualNavs = this.getBilingualNavTitles()

      if (item === selNavId || bilingualNavs[item] === selNavId) {
        // alert(item)
        dynamicAttrs['data-selected'] = true
      }

      linkTarget = item === 'home' ? '/' : `/${item}/`

      liElem = (
        // eslint-disable-next-line jsx-a11y/click-events-have-key-events

 {
            this.navHtmlElems.set(item, navHtmlElem)
          }}
          {...dynamicAttrs}>
          <Link to={linkTarget}>
            {item[0].toUpperCase().concat(item.substr(1))}
          </Link>

      )
      navLinks.push(liElem)
    })

    return (

          {navLinks}

    )
  }
}

Nav.propTypes = {
  pathname: PropTypes.string.isRequired,
  t: PropTypes.func.isRequired
}

export default translate()(Nav)
<|endoftext|>


<commit_before>// ==========================================================================
// YouTube plugin
// ==========================================================================

import ui from '../ui';
import { createElement, replaceElement, toggleClass } from '../utils/elements';
import { triggerEvent } from '../utils/events';
import fetch from '../utils/fetch';
import is from '../utils/is';
import loadImage from '../utils/loadImage';
import loadScript from '../utils/loadScript';
import { extend } from '../utils/objects';
import { format, generateId } from '../utils/strings';
import { setAspectRatio } from '../utils/style';

// Parse YouTube ID from URL
function parseId(url) {
    if (is.empty(url)) {
        return null;
    }

    const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
    return url.match(regex) ? RegExp.$2 : url;
}

// Set playback state and trigger change (only on actual change)
function assurePlaybackState(play) {
    if (play && !this.embed.hasPlayed) {
        this.embed.hasPlayed = true;
    }
    if (this.media.paused === play) {
        this.media.paused = !play;
        triggerEvent.call(this, this.media, play ? 'play' : 'pause');
    }
}

function getHost(config) {
    if (config.noCookie) {
        return 'https://www.youtube-nocookie.com';
    }

    if (window.location.protocol === 'http:') {
        return 'http://www.youtube.com';
    }

    // Use YouTube's default
    return undefined;
}

let onYouTubeReadyCallbacks;

const youtube = {
    setup() {
        // Add embed class for responsive
        toggleClass(this.elements.wrapper, this.config.classNames.embed, true);

        // Setup API
        if (is.object(window.YT) && is.function(window.YT.Player)) {
            youtube.ready.call(this);
        } else {

            if (!onYouTubeReadyCallbacks) {
                const oldYouTubeIframeAPIReady = window.onYouTubeIframeAPIReady;
                // Load the API
                onYouTubeReadyCallbacks = [];

                // Set callback to process queue
                window.onYouTubeIframeAPIReady = () => {
                    if (oldYouTubeIframeAPIReady && is.function(oldYouTubeIframeAPIReady)) {
                        oldYouTubeIframeAPIReady();
                    }

                    window.onYouTubeReadyCallbacks.forEach(callback => {
                        callback();
                    });
                };

                loadScript(this.config.urls.youtube.sdk).catch(error => {
                    this.debug.warn('YouTube API failed to load', error);
                });
            }

            // Add to queue
            window.onYouTubeReadyCallbacks.push(() => {
                youtube.ready.call(this);
            });
        }
    },

    // Get the media title
    getTitle(videoId) {
        const url = format(this.config.urls.youtube.api, videoId);

        fetch(url)
            .then(data => {
                if (is.object(data)) {
                    const { title, height, width } = data;

                    // Set title
                    this.config.title = title;
                    ui.setTitle.call(this);

                    // Set aspect ratio
                    this.embed.ratio = [width, height];
                }

                setAspectRatio.call(this);
            })
            .catch(() => {
                // Set aspect ratio
                setAspectRatio.call(this);
            });
    },

    // API ready
    ready() {
        const player = this;

        // Ignore already setup (race condition)
        const currentId = player.media && player.media.getAttribute('id');
        if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
            return;
        }

        // Get the source URL or ID
        let source = player.media.getAttribute('src');

        // Get from <div> if needed
        if (is.empty(source)) {
            source = player.media.getAttribute(this.config.attributes.embed.id);
        }

        // Replace the <iframe> with a <div> due to YouTube API issues
        const videoId = parseId(source);
        const id = generateId(player.provider);

        // Get poster, if already set
        const { poster } = player;

        // Replace media element
        const container = createElement('div', { id, poster });
        player.media = replaceElement(container, player.media);

        // Id to poster wrapper
        const posterSrc = format => `https://i.ytimg.com/vi/${videoId}/${format}default.jpg`;

        // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
        loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
            .catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
            .catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
            .then(image => ui.setPoster.call(player, image.src))
            .then(posterSrc => {
                // If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
                if (!posterSrc.includes('maxres')) {
                    player.elements.poster.style.backgroundSize = 'cover';
                }
            })
            .catch(() => {});

        const config = player.config.youtube;

        // Setup instance
        // https://developers.google.com/youtube/iframe_api_reference
        player.embed = new window.YT.Player(id, {
            videoId,
            host: getHost(config),
            playerVars: extend(
                {},
                {
                    autoplay: player.config.autoplay ? 1 : 0, // Autoplay
                    hl: player.config.hl, // iframe interface language
                    controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
                    disablekb: 1, // Disable keyboard as we handle it
                    playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
                    // Captions are flaky on YouTube
                    cc_load_policy: player.captions.active ? 1 : 0,
                    cc_lang_pref: player.config.captions.language,
                    // Tracking for stats
                    widget_referrer: window ? window.location.href : null,
                },
                config,
            ),
            events: {
                onError(event) {
                    // YouTube may fire onError twice, so only handle it once
                    if (!player.media.error) {
                        const code = event.data;
                        // Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
                        const message =
                            {
                                2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
                                5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
                                100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
                                101: 'The owner of the requested video does not allow it to be played in embedded players.',
                                150: 'The owner of the requested video does not allow it to be played in embedded players.',
                            }[code] || 'An unknown error occured';

                        player.media.error = { code, message };

                        triggerEvent.call(player, player.media, 'error');
                    }
                },
                onPlaybackRateChange(event) {
                    // Get the instance
                    const instance = event.target;

                    // Get current speed
                    player.media.playbackRate = instance.getPlaybackRate();

                    triggerEvent.call(player, player.media, 'ratechange');
                },
                onReady(event) {
                    // Bail if onReady has already been called. See issue #1108
                    if (is.function(player.media.play)) {
                        return;
                    }
                    // Get the instance
                    const instance = event.target;

                    // Get the title
                    youtube.getTitle.call(player, videoId);

                    // Create a faux HTML5 API using the YouTube API
                    player.media.play = () => {
                        assurePlaybackState.call(player, true);
                        instance.playVideo();
                    };

                    player.media.pause = () => {
                        assurePlaybackState.call(player, false);
                        instance.pauseVideo();
                    };

                    player.media.stop = () => {
                        instance.stopVideo();
                    };

                    player.media.duration = instance.getDuration();
                    player.media.paused = true;

                    // Seeking
                    player.media.currentTime = 0;
                    Object.defineProperty(player.media, 'currentTime', {
                        get() {
                            return Number(instance.getCurrentTime());
                        },
                        set(time) {
                            // If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
                            if (player.paused && !player.embed.hasPlayed) {
                                player.embed.mute();
                            }

                            // Set seeking state and trigger event
                            player.media.seeking = true;
                            triggerEvent.call(player, player.media, 'seeking');

                            // Seek after events sent
                            instance.seekTo(time);
                        },
                    });

                    // Playback speed
                    Object.defineProperty(player.media, 'playbackRate', {
                        get() {
                            return instance.getPlaybackRate();
                        },
                        set(input) {
                            instance.setPlaybackRate(input);
                        },
                    });

                    // Volume
                    let { volume } = player.config;
                    Object.defineProperty(player.media, 'volume', {
                        get() {
                            return volume;
                        },
                        set(input) {
                            volume = input;
                            instance.setVolume(volume * 100);
                            triggerEvent.call(player, player.media, 'volumechange');
                        },
                    });

                    // Muted
                    let { muted } = player.config;
                    Object.defineProperty(player.media, 'muted', {
                        get() {
                            return muted;
                        },
                        set(input) {
                            const toggle = is.boolean(input) ? input : muted;
                            muted = toggle;
                            instance[toggle ? 'mute' : 'unMute']();
                            triggerEvent.call(player, player.media, 'volumechange');
                        },
                    });

                    // Source
                    Object.defineProperty(player.media, 'currentSrc', {
                        get() {
                            return instance.getVideoUrl();
                        },
                    });

                    // Ended
                    Object.defineProperty(player.media, 'ended', {
                        get() {
                            return player.currentTime === player.duration;
                        },
                    });

                    // Get available speeds
                    player.options.speed = instance.getAvailablePlaybackRates();

                    // Set the tabindex to avoid focus entering iframe
                    if (player.supported.ui) {
                        player.media.setAttribute('tabindex', -1);
                    }

                    triggerEvent.call(player, player.media, 'timeupdate');
                    triggerEvent.call(player, player.media, 'durationchange');

                    // Reset timer
                    clearInterval(player.timers.buffering);

                    // Setup buffering
                    player.timers.buffering = setInterval(() => {
                        // Get loaded % from YouTube
                        player.media.buffered = instance.getVideoLoadedFraction();

                        // Trigger progress only when we actually buffer something
                        if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
                            triggerEvent.call(player, player.media, 'progress');
                        }

                        // Set last buffer point
                        player.media.lastBuffered = player.media.buffered;

                        // Bail if we're at 100%
                        if (player.media.buffered === 1) {
                            clearInterval(player.timers.buffering);

                            // Trigger event
                            triggerEvent.call(player, player.media, 'canplaythrough');
                        }
                    }, 200);

                    // Rebuild UI
                    setTimeout(() => ui.build.call(player), 50);
                },
                onStateChange(event) {
                    // Get the instance
                    const instance = event.target;

                    // Reset timer
                    clearInterval(player.timers.playing);

                    const seeked = player.media.seeking && [1, 2].includes(event.data);

                    if (seeked) {
                        // Unset seeking and fire seeked event
                        player.media.seeking = false;
                        triggerEvent.call(player, player.media, 'seeked');
                    }

                    // Handle events
                    // -1   Unstarted
                    // 0    Ended
                    // 1    Playing
                    // 2    Paused
                    // 3    Buffering
                    // 5    Video cued
                    switch (event.data) {
                        case -1:
                            // Update scrubber
                            triggerEvent.call(player, player.media, 'timeupdate');

                            // Get loaded % from YouTube
                            player.media.buffered = instance.getVideoLoadedFraction();
                            triggerEvent.call(player, player.media, 'progress');

                            break;

                        case 0:
                            assurePlaybackState.call(player, false);

                            // YouTube doesn't support loop for a single video, so mimick it.
                            if (player.media.loop) {
                                // YouTube needs a call to `stopVideo` before playing again
                                instance.stopVideo();
                                instance.playVideo();
                            } else {
                                triggerEvent.call(player, player.media, 'ended');
                            }

                            break;

                        case 1:
                            // Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
                            if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
                                player.media.pause();
                            } else {
                                assurePlaybackState.call(player, true);

                                triggerEvent.call(player, player.media, 'playing');

                                // Poll to get playback progress
                                player.timers.playing = setInterval(() => {
                                    triggerEvent.call(player, player.media, 'timeupdate');
                                }, 50);

                                // Check duration again due to YouTube bug
                                // https://github.com/sampotts/plyr/issues/374
                                // https://code.google.com/p/gdata-issues/issues/detail?id=8690
                                if (player.media.duration !== instance.getDuration()) {
                                    player.media.duration = instance.getDuration();
                                    triggerEvent.call(player, player.media, 'durationchange');
                                }
                            }

                            break;

                        case 2:
                            // Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
                            if (!player.muted) {
                                player.embed.unMute();
                            }
                            assurePlaybackState.call(player, false);

                            break;

                        default:
                            break;
                    }

                    triggerEvent.call(player, player.elements.container, 'statechange', false, {
                        code: event.data,
                    });
                },
            },
        });
    },
};

export default youtube;
<commit_msg>fix youtube embed + handle early destroy<commit_after>// ==========================================================================
// YouTube plugin
// ==========================================================================

import ui from '../ui';
import { createElement, replaceElement, toggleClass } from '../utils/elements';
import { triggerEvent } from '../utils/events';
import fetch from '../utils/fetch';
import is from '../utils/is';
import loadImage from '../utils/loadImage';
import loadScript from '../utils/loadScript';
import { extend } from '../utils/objects';
import { format, generateId } from '../utils/strings';
import { setAspectRatio } from '../utils/style';

// Parse YouTube ID from URL
function parseId(url) {
    if (is.empty(url)) {
        return null;
    }

    const regex = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/;
    return url.match(regex) ? RegExp.$2 : url;
}

// Set playback state and trigger change (only on actual change)
function assurePlaybackState(play) {
    if (play && !this.embed.hasPlayed) {
        this.embed.hasPlayed = true;
    }
    if (this.media.paused === play) {
        this.media.paused = !play;
        triggerEvent.call(this, this.media, play ? 'play' : 'pause');
    }
}

function getHost(config) {
    if (config.noCookie) {
        return 'https://www.youtube-nocookie.com';
    }

    if (window.location.protocol === 'http:') {
        return 'http://www.youtube.com';
    }

    // Use YouTube's default
    return undefined;
}

let onYouTubeReadyCallbacks;

const youtube = {
    setup() {
        // Add embed class for responsive
        toggleClass(this.elements.wrapper, this.config.classNames.embed, true);

        // Setup API
        if (is.object(window.YT) && is.function(window.YT.Player)) {
            youtube.ready.call(this);
        } else {

            if (!onYouTubeReadyCallbacks) {
                const oldYouTubeIframeAPIReady = window.onYouTubeIframeAPIReady;
                // Load the API
                onYouTubeReadyCallbacks = [];

                // Set callback to process queue
                window.onYouTubeIframeAPIReady = () => {
                    if (oldYouTubeIframeAPIReady && is.function(oldYouTubeIframeAPIReady)) {
                        oldYouTubeIframeAPIReady();
                    }

                    window.onYouTubeReadyCallbacks.forEach(callback => {
                        callback();
                    });
                };

                loadScript(this.config.urls.youtube.sdk).catch(error => {
                    this.debug.warn('YouTube API failed to load', error);
                });
            }

            // Add to queue
            onYouTubeReadyCallbacks.push(() => {
                youtube.ready.call(this);
            });
        }
    },

    // Get the media title
    getTitle(videoId) {
        const url = format(this.config.urls.youtube.api, videoId);

        fetch(url)
            .then(data => {
                if (is.object(data)) {
                    const { title, height, width } = data;

                    // Set title
                    this.config.title = title;
                    ui.setTitle.call(this);

                    // Set aspect ratio
                    this.embed.ratio = [width, height];
                }

                setAspectRatio.call(this);
            })
            .catch(() => {
                // Set aspect ratio
                setAspectRatio.call(this);
            });
    },

    // API ready
    ready() {
        const player = this;

        // Ignore already setup (race condition)
        const currentId = player.media && player.media.getAttribute('id');
        if (!is.empty(currentId) && currentId.startsWith('youtube-')) {
            return;
        }

        // Get the source URL or ID
        let source = player.media.getAttribute('src');

        // Get from <div> if needed
        if (is.empty(source)) {
            source = player.media.getAttribute(this.config.attributes.embed.id);
        }

        // Replace the <iframe> with a <div> due to YouTube API issues
        const videoId = parseId(source);
        const id = generateId(player.provider);

        // Get poster, if already set
        const { poster } = player;

        // Replace media element
        const container = createElement('div', { id, poster });
        player.media = replaceElement(container, player.media);

        // Id to poster wrapper
        const posterSrc = format => `https://i.ytimg.com/vi/${videoId}/${format}default.jpg`;

        // Check thumbnail images in order of quality, but reject fallback thumbnails (120px wide)
        loadImage(posterSrc('maxres'), 121) // Higest quality and unpadded
            .catch(() => loadImage(posterSrc('sd'), 121)) // 480p padded 4:3
            .catch(() => loadImage(posterSrc('hq'))) // 360p padded 4:3. Always exists
            .then(image => ui.setPoster.call(player, image.src))
            .then(posterSrc => {
                // If the image is padded, use background-size "cover" instead (like youtube does too with their posters)
                if (!posterSrc.includes('maxres')) {
                    player.elements.poster.style.backgroundSize = 'cover';
                }
            })
            .catch(() => {});

        const config = player.config.youtube;

        // Setup instance
        // https://developers.google.com/youtube/iframe_api_reference
        player.embed = new window.YT.Player(id, {
            videoId,
            host: getHost(config),
            playerVars: extend(
                {},
                {
                    autoplay: player.config.autoplay ? 1 : 0, // Autoplay
                    hl: player.config.hl, // iframe interface language
                    controls: player.supported.ui ? 0 : 1, // Only show controls if not fully supported
                    disablekb: 1, // Disable keyboard as we handle it
                    playsinline: !player.config.fullscreen.iosNative ? 1 : 0, // Allow iOS inline playback
                    // Captions are flaky on YouTube
                    cc_load_policy: player.captions.active ? 1 : 0,
                    cc_lang_pref: player.config.captions.language,
                    // Tracking for stats
                    widget_referrer: window ? window.location.href : null,
                },
                config,
            ),
            events: {
                onError(event) {
                    // YouTube may fire onError twice, so only handle it once
                    if (!player.media.error) {
                        const code = event.data;
                        // Messages copied from https://developers.google.com/youtube/iframe_api_reference#onError
                        const message =
                            {
                                2: 'The request contains an invalid parameter value. For example, this error occurs if you specify a video ID that does not have 11 characters, or if the video ID contains invalid characters, such as exclamation points or asterisks.',
                                5: 'The requested content cannot be played in an HTML5 player or another error related to the HTML5 player has occurred.',
                                100: 'The video requested was not found. This error occurs when a video has been removed (for any reason) or has been marked as private.',
                                101: 'The owner of the requested video does not allow it to be played in embedded players.',
                                150: 'The owner of the requested video does not allow it to be played in embedded players.',
                            }[code] || 'An unknown error occured';

                        player.media.error = { code, message };

                        triggerEvent.call(player, player.media, 'error');
                    }
                },
                onPlaybackRateChange(event) {
                    // Get the instance
                    const instance = event.target;

                    // Get current speed
                    player.media.playbackRate = instance.getPlaybackRate();

                    triggerEvent.call(player, player.media, 'ratechange');
                },
                onReady(event) {
                    // Bail if onReady has already been called. See issue #1108
                    if (is.function(player.media.play)) {
                        return;
                    }
                    // Get the instance
                    const instance = event.target;

                    // Get the title
                    youtube.getTitle.call(player, videoId);

                    // Create a faux HTML5 API using the YouTube API
                    player.media.play = () => {
                        assurePlaybackState.call(player, true);
                        instance.playVideo();
                    };

                    player.media.pause = () => {
                        assurePlaybackState.call(player, false);
                        instance.pauseVideo();
                    };

                    player.media.stop = () => {
                        instance.stopVideo();
                    };

                    player.media.duration = instance.getDuration();
                    player.media.paused = true;

                    // Seeking
                    player.media.currentTime = 0;
                    Object.defineProperty(player.media, 'currentTime', {
                        get() {
                            return Number(instance.getCurrentTime());
                        },
                        set(time) {
                            // If paused and never played, mute audio preventively (YouTube starts playing on seek if the video hasn't been played yet).
                            if (player.paused && !player.embed.hasPlayed) {
                                player.embed.mute();
                            }

                            // Set seeking state and trigger event
                            player.media.seeking = true;
                            triggerEvent.call(player, player.media, 'seeking');

                            // Seek after events sent
                            instance.seekTo(time);
                        },
                    });

                    // Playback speed
                    Object.defineProperty(player.media, 'playbackRate', {
                        get() {
                            return instance.getPlaybackRate();
                        },
                        set(input) {
                            instance.setPlaybackRate(input);
                        },
                    });

                    // Volume
                    let { volume } = player.config;
                    Object.defineProperty(player.media, 'volume', {
                        get() {
                            return volume;
                        },
                        set(input) {
                            volume = input;
                            instance.setVolume(volume * 100);
                            triggerEvent.call(player, player.media, 'volumechange');
                        },
                    });

                    // Muted
                    let { muted } = player.config;
                    Object.defineProperty(player.media, 'muted', {
                        get() {
                            return muted;
                        },
                        set(input) {
                            const toggle = is.boolean(input) ? input : muted;
                            muted = toggle;
                            instance[toggle ? 'mute' : 'unMute']();
                            triggerEvent.call(player, player.media, 'volumechange');
                        },
                    });

                    // Source
                    Object.defineProperty(player.media, 'currentSrc', {
                        get() {
                            return instance.getVideoUrl();
                        },
                    });

                    // Ended
                    Object.defineProperty(player.media, 'ended', {
                        get() {
                            return player.currentTime === player.duration;
                        },
                    });

                    // Get available speeds
                    player.options.speed = instance.getAvailablePlaybackRates();

                    // Set the tabindex to avoid focus entering iframe
                    if (player.supported.ui) {
                        player.media.setAttribute('tabindex', -1);
                    }

                    triggerEvent.call(player, player.media, 'timeupdate');
                    triggerEvent.call(player, player.media, 'durationchange');

                    // Reset timer
                    clearInterval(player.timers.buffering);

                    // Setup buffering
                    player.timers.buffering = setInterval(() => {
                        // Get loaded % from YouTube
                        player.media.buffered = instance.getVideoLoadedFraction();

                        // Trigger progress only when we actually buffer something
                        if (player.media.lastBuffered === null || player.media.lastBuffered < player.media.buffered) {
                            triggerEvent.call(player, player.media, 'progress');
                        }

                        // Set last buffer point
                        player.media.lastBuffered = player.media.buffered;

                        // Bail if we're at 100%
                        if (player.media.buffered === 1) {
                            clearInterval(player.timers.buffering);

                            // Trigger event
                            triggerEvent.call(player, player.media, 'canplaythrough');
                        }
                    }, 200);

                    // Rebuild UI
                    setTimeout(() => ui.build.call(player), 50);
                },
                onStateChange(event) {
                    // Get the instance
                    const instance = event.target;

                    // Reset timer
                    clearInterval(player.timers.playing);

                    const seeked = player.media.seeking && [1, 2].includes(event.data);

                    if (seeked) {
                        // Unset seeking and fire seeked event
                        player.media.seeking = false;
                        triggerEvent.call(player, player.media, 'seeked');
                    }

                    // Handle events
                    // -1   Unstarted
                    // 0    Ended
                    // 1    Playing
                    // 2    Paused
                    // 3    Buffering
                    // 5    Video cued
                    switch (event.data) {
                        case -1:
                            // Update scrubber
                            triggerEvent.call(player, player.media, 'timeupdate');

                            // Get loaded % from YouTube
                            player.media.buffered = instance.getVideoLoadedFraction();
                            triggerEvent.call(player, player.media, 'progress');

                            break;

                        case 0:
                            assurePlaybackState.call(player, false);

                            // YouTube doesn't support loop for a single video, so mimick it.
                            if (player.media.loop) {
                                // YouTube needs a call to `stopVideo` before playing again
                                instance.stopVideo();
                                instance.playVideo();
                            } else {
                                triggerEvent.call(player, player.media, 'ended');
                            }

                            break;

                        case 1:
                            // Restore paused state (YouTube starts playing on seek if the video hasn't been played yet)
                            if (!player.config.autoplay && player.media.paused && !player.embed.hasPlayed) {
                                player.media.pause();
                            } else {
                                assurePlaybackState.call(player, true);

                                triggerEvent.call(player, player.media, 'playing');

                                // Poll to get playback progress
                                player.timers.playing = setInterval(() => {
                                    triggerEvent.call(player, player.media, 'timeupdate');
                                }, 50);

                                // Check duration again due to YouTube bug
                                // https://github.com/sampotts/plyr/issues/374
                                // https://code.google.com/p/gdata-issues/issues/detail?id=8690
                                if (player.media.duration !== instance.getDuration()) {
                                    player.media.duration = instance.getDuration();
                                    triggerEvent.call(player, player.media, 'durationchange');
                                }
                            }

                            break;

                        case 2:
                            // Restore audio (YouTube starts playing on seek if the video hasn't been played yet)
                            if (!player.muted) {
                                player.embed.unMute();
                            }
                            assurePlaybackState.call(player, false);

                            break;

                        default:
                            break;
                    }

                    triggerEvent.call(player, player.elements.container, 'statechange', false, {
                        code: event.data,
                    });
                },
            },
        });
    },
};

export default youtube;
<|endoftext|>


<commit_before>/* globals pixelwidth, pixelheight */
DropSomething.MainMenu = function (game) {
	this.game = game;
	this.music = null;
};

DropSomething.MainMenu.prototype = {
	create: function () {
        this.game.stage.backgroundColor = '#E0F8D0';
        this.spaceKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

        this.welcome = this.addTextElement("Drop Something!");
        this.scorefont = this.addTextElement("Highscore: " + this.game.CS.highscore);
        this.pressSpace = this.addTextElement("Press Space to play!");

        this.titleText = this.game.add.image(this.game.world.centerX - 16, this.game.world.centerY, this.welcome);
        this.titleText.anchor.setTo(0.5, 0.5);

        this.pressSpace = this.game.add.image(this.game.world.centerX, this.game.world.centerY + 32, this.pressSpace);
        this.pressSpace.anchor.setTo(0.5, 0.5);

        this.highscore = this.game.add.image(this.game.world.centerX, this.game.world.centerY - 32, this.scorefont);
        this.highscore.anchor.setTo(0.5, 0.5);

        this.game.add.tween(this.titleText)
            .to(
                { x: this.game.world.centerX + 16 },
                1000,
                Phaser.Easing.Linear.None,
                true,
                0,
                1000,
                true
            );

        /*
        this.tweet = this.game.input.keyboard.addKey(Phaser.Keyboard.T);
        this.tweet.onUp.add(function(){
            this.tweetScore();
        }, this);

        var twitter = this.game.add.image(this.game.world.width - 18, this.game.world.height - 18, 'twitter');
        */
	},

    tweetScore: function() {
        var twtTitle  = "Wow! I've dropped a score of  " + this.game.CS.highscore + " points! How much do you get?";
        var twtUrl    = location.href;
        var twtLink = 'http://twitter.com/home?status='+encodeURIComponent(twtTitle + ' ' + twtUrl);
        window.open(twtLink,'_blank');
    },

    addTextElement: function (endTextValue) {
        var newText = this.game.add.retroFont('font', 8, 8, '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ:!?');
        newText.text = endTextValue;
        return newText;
    },

	update: function () {
        if (this.spaceKey.isDown) {
            this.startGame();
        }

	},

	startGame: function (pointer) {
		//this.music.stop();
		this.game.state.start('Game');
	},

    render: function() {
        this.game.CS.settings.pixelcontext.drawImage(
            this.game.canvas,
            0,
            0,
            this.game.CS.settings.originWidth,
            this.game.CS.settings.originHeight,
            0,
            0,
            this.game.CS.settings.pixelwidth,
            this.game.CS.settings.pixelheight
        );
    }
};
<commit_msg>Added some fancy stuff to mainscreen<commit_after>/* globals pixelwidth, pixelheight */
DropSomething.MainMenu = function (game) {
	this.game = game;
};

DropSomething.MainMenu.prototype = {
	create: function () {
        this.game.physics.startSystem(Phaser.Physics.ARCADE);

        this.game.stage.backgroundColor = '#E0F8D0';

        this.platform = this.game.add.group();
        this.platform.createMultiple(5, 'sprites', 'ground1');
        this.game.physics.enable(this.platform);

        this.spaceKey = this.game.input.keyboard.addKey(Phaser.Keyboard.SPACEBAR);

        this.welcome = this.addTextElement("Drop Something!");
        this.scorefont = this.addTextElement("Highscore: " + this.game.CS.highscore);
        this.pressSpace = this.addTextElement("Press Space to play!");

        this.titleText = this.game.add.image(this.game.world.centerX - 16, this.game.world.centerY, this.welcome);
        this.titleText.anchor.setTo(0.5, 0.5);

        this.pressSpace = this.game.add.image(this.game.world.centerX, this.game.world.centerY + 32, this.pressSpace);
        this.pressSpace.anchor.setTo(0.5, 0.5);

        this.highscore = this.game.add.image(this.game.world.centerX, this.game.world.centerY - 32, this.scorefont);
        this.highscore.anchor.setTo(0.5, 0.5);

        this.game.add.tween(this.titleText)
            .to(
                { x: this.game.world.centerX + 16 },
                1000,
                Phaser.Easing.Linear.None,
                true,
                0,
                1000,
                true
            );

        /*
        this.tweet = this.game.input.keyboard.addKey(Phaser.Keyboard.T);
        this.tweet.onUp.add(function(){
            this.tweetScore();
        }, this);

        var twitter = this.game.add.image(this.game.world.width - 18, this.game.world.height - 18, 'twitter');
        */

        this.game.time.events.loop(Phaser.Timer.SECOND, this.platformGenerator, this);
	},

    platformGenerator: function() {
        var myPlatform = this.platform.getFirstDead();
        if (myPlatform !== null) {
            myPlatform.reset(this.game.rnd.integerInRange(16, 160-16), 160);

            myPlatform.outOfBoundsKill = true;
            myPlatform.checkWorldBounds = true;
            myPlatform.body.velocity.y = -75;
        }
    },

    tweetScore: function() {
        var twtTitle  = "Wow! I've dropped a score of  " + this.game.CS.highscore + " points! How much do you get?";
        var twtUrl    = location.href;
        var twtLink = 'http://twitter.com/home?status='+encodeURIComponent(twtTitle + ' ' + twtUrl);
        window.open(twtLink,'_blank');
    },

    addTextElement: function (endTextValue) {
        var newText = this.game.add.retroFont('font', 8, 8, '1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ:!?');
        newText.text = endTextValue;
        return newText;
    },

	update: function () {
        if (this.spaceKey.isDown) {
            this.startGame();
        }
	},

	startGame: function (pointer) {

		this.game.state.start('Game');
	},

    render: function() {
        this.game.CS.settings.pixelcontext.drawImage(
            this.game.canvas,
            0,
            0,
            this.game.CS.settings.originWidth,
            this.game.CS.settings.originHeight,
            0,
            0,
            this.game.CS.settings.pixelwidth,
            this.game.CS.settings.pixelheight
        );
    }
};
<|endoftext|>


<commit_before>/*jslint nomen: true */
/*globals define: true */

define([
  // Libraries
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/leaflet.draw/leaflet.draw',
  'moment',
  'lib/tinypubsub',
  'lib/kissmetrics',

  // LocalData
  'settings',
  'api',

  // Models
  'models/zones',

  // Templates
  'text!templates/surveys/settings-map.html',
  'text!templates/surveys/settings-map-zones.html'
],

// https://github.com/LocalData/localdata-dashboard/compare/map-draw?expand=1

function($, _, Backbone, L, moment, events, _kmq, settings, api,
  Zones, MapDrawTemplate, MapZonesTemplate) {
  'use strict';

  function flip(a) {
    return [a[1], a[0]];
  }

  var MapDrawView = Backbone.View.extend({
    map: null,

    el: "#map-draw-view-container",

    template: _.template(MapDrawTemplate),
    zonesTemplate: _.template(MapZonesTemplate),

    events: {
      'click .draw': 'drawZone',
      'click .remove': 'removeZone',
      'click .done': 'doneDrawingZone'
    },

    initialize: function(options) {
      _.bindAll(this,
        'render',
        'fitBounds',
        'addZone',
        'renderZones',
        'getZones'
      );

      this.survey = options.survey;
      this.survey.on('change', this.render);

      this.zones = new Zones.Collection();

      this.zones.on('add', this.saveZones);
      this.zones.on('remove', this.unmapZone);

      this.render();
    },

    render: function() {
      // Don't re-render
      if(this.map) {
        return;
      }

      this.$el.html(this.template());

      // Initialize the map
      this.map = new L.map('map-draw', {
        maxZoom: 19
      });

      // Set up the base map
      this.baseLayer = new L.tileLayer(settings.baseLayer);
      this.map.addLayer(this.baseLayer);

      // Center on the survey
      // TODO: use geocoded center
      this.fitBounds();

      // Initialize the FeatureGroup to store editable layers
      this.drawnItems = new L.FeatureGroup();
      this.map.addLayer(this.drawnItems);

      // Initialize the draw control and pass it the FeatureGroup of editable layers
      var drawControl = new L.Control.Draw({
        draw: {
          polyline: false,
          rectangle: false,
          circle: false,
          marker:false,
          polygon: {
            shapeOptions: {
                color: '#ef6d4a',
                fillColor: '#ef6d4a'
            }
          }
        },
        edit: {
          featureGroup: this.drawnItems
        }
      });
      this.map.addControl(drawControl);

      this.map.on('draw:created', this.addZone);

      // Add existing zones
      this.zones.reset(this.survey.get('zones'));
      this.renderZones();
    },

    fitBounds: function() {
      var bounds = this.survey.get('responseBounds');
      if (bounds) {
        bounds = [flip(bounds[0]), flip(bounds[1])];
        if (bounds[0][0] === bounds[1][0] || bounds[0][1] === bounds[1][1]) {
          this.map.setView(bounds[0], 15);
        } else {
          this.map.fitBounds(bounds, {
            reset: true,
            maxZoom: 18
          });
        }
      }
    },

    style: function(feature) {
      return {
        color: feature.properties.color,
        opacity: 1,
        fillColor: feature.properties.color
      };
    },

    addZone: function(event) {
      var type  = event.layerType,
          layer = event.layer;

      // Style zones with a unique color
      // TODO: Add more colors.
      // We only have 6 colors right now.
      var zoneNumber = this.zones.length;
      var color = settings.colorRange[zoneNumber + 1];
      layer.setStyle({
        color: color,
        opacity: 1,
        fillColor: color
      });

      // Create a new zone model
      var geoJSON = layer.toGeoJSON();
      geoJSON.properties = {
        name: 'Zone ' + (this.zones.length + 1),
        color: color
      };
      // Add the zone layer to the layerGroup
      this.drawnItems.addLayer(layer);

      // Save the layer to the model
      geoJSON.layer = layer;
      var zone = new Zones.Model(geoJSON);
      this.zones.push(zone);

      // Render the form
      $('#map-zones').html(this.zonesTemplate({
        zones: this.zones.toJSON()
      }));
    },

    /**
     * Render surveyor zones on the map
     */
    renderZones: function() {
      var zones, layer;

      // If the survey already has zones, render them
      if(this.survey.get('zones')) {
        zones = this.survey.get('zones');

        // Add each zone to the map
        _.each(zones, function(zone) {
          layer = L.geoJson(zone, {
            style: this.style
          });
          this.drawnItems.addLayer(layer);
          zone.layer = layer;
        }.bind(this));

        this.zones.reset(zones);
      }

      // Renter the form for the zones
      $('#map-zones').html(this.zonesTemplate({
        zones: this.zones.toJSON()
      }));
    },

    removeZone: function(event) {
      var index = $(event.target).attr('data-index');
      var model = this.zones.at(index);

      this.drawnItems.removeLayer(model.get('layer'));
      this.zones.remove(model);
      $(event.target).parent().remove();
    },

    getZones: function() {
      // Update the names
      $('#survey-zone-form input').each(function(index, $input) {
        this.zones.at(index).attributes.properties.name = $input.value;
      }.bind(this));
      return this.zones.toJSON();
    }

  });

  return MapDrawView;

});
<commit_msg>Use existing zones as individual features, not as one featuregroup<commit_after>/*jslint nomen: true */
/*globals define: true */

define([
  // Libraries
  'jquery',
  'lib/lodash',
  'backbone',
  'lib/leaflet.draw/leaflet.draw',
  'moment',
  'lib/tinypubsub',
  'lib/kissmetrics',

  // LocalData
  'settings',
  'api',

  // Models
  'models/zones',

  // Templates
  'text!templates/surveys/settings-map.html',
  'text!templates/surveys/settings-map-zones.html'
],

// https://github.com/LocalData/localdata-dashboard/compare/map-draw?expand=1

function($, _, Backbone, L, moment, events, _kmq, settings, api,
  Zones, MapDrawTemplate, MapZonesTemplate) {
  'use strict';

  function flip(a) {
    return [a[1], a[0]];
  }

  var MapDrawView = Backbone.View.extend({
    map: null,

    el: "#map-draw-view-container",

    template: _.template(MapDrawTemplate),
    zonesTemplate: _.template(MapZonesTemplate),

    events: {
      'click .draw': 'drawZone',
      'click .remove': 'removeZone',
      'click .done': 'doneDrawingZone'
    },

    initialize: function(options) {
      _.bindAll(this,
        'render',
        'fitBounds',
        'addZone',
        'renderZones',
        'getZones'
      );

      this.survey = options.survey;
      this.survey.on('change', this.render);

      this.zones = new Zones.Collection();

      this.zones.on('add', this.saveZones);
      this.zones.on('remove', this.unmapZone);

      this.render();
    },

    render: function() {
      // Don't re-render
      if(this.map) {
        return;
      }

      this.$el.html(this.template());

      // Initialize the map
      this.map = new L.map('map-draw', {
        maxZoom: 19
      });

      // Set up the base map
      this.baseLayer = new L.tileLayer(settings.baseLayer);
      this.map.addLayer(this.baseLayer);

      // Center on the survey
      // TODO: use geocoded center
      this.fitBounds();

      // Initialize the FeatureGroup to store editable layers
      this.drawnItems = new L.FeatureGroup();
      this.map.addLayer(this.drawnItems);

      // Initialize the draw control and pass it the FeatureGroup of editable layers
      var drawControl = new L.Control.Draw({
        draw: {
          polyline: false,
          rectangle: false,
          circle: false,
          marker:false,
          polygon: {
            shapeOptions: {
                color: '#ef6d4a',
                fillColor: '#ef6d4a'
            }
          }
        },
        edit: {
          featureGroup: this.drawnItems
        }
      });
      this.map.addControl(drawControl);

      this.map.on('draw:created', this.addZone);

      // Add existing zones
      this.zones.reset(this.survey.get('zones'));
      this.renderZones();
    },

    fitBounds: function() {
      var bounds = this.survey.get('responseBounds');
      if (bounds) {
        bounds = [flip(bounds[0]), flip(bounds[1])];
        if (bounds[0][0] === bounds[1][0] || bounds[0][1] === bounds[1][1]) {
          this.map.setView(bounds[0], 15);
        } else {
          this.map.fitBounds(bounds, {
            reset: true,
            maxZoom: 18
          });
        }
      }
    },

    style: function(feature) {
      return {
        color: feature.properties.color,
        opacity: 1,
        fillColor: feature.properties.color
      };
    },

    addZone: function(event) {
      var type  = event.layerType,
          layer = event.layer;

      // Style zones with a unique color
      // TODO: Add more colors.
      // We only have 6 colors right now.
      var zoneNumber = this.zones.length;
      var color = settings.colorRange[zoneNumber + 1];
      layer.setStyle({
        color: color,
        opacity: 1,
        fillColor: color
      });

      // Create a new zone model
      var geoJSON = layer.toGeoJSON();
      geoJSON.properties = {
        name: 'Zone ' + (this.zones.length + 1),
        color: color
      };
      // Add the zone layer to the layerGroup
      this.drawnItems.addLayer(layer);

      // Save the layer to the model
      geoJSON.layer = layer;
      var zone = new Zones.Model(geoJSON);
      this.zones.push(zone);

      // Render the form
      $('#map-zones').html(this.zonesTemplate({
        zones: this.zones.toJSON()
      }));
    },

    /**
     * Render surveyor zones on the map
     */
    renderZones: function() {
      var zones, layer;

      // If the survey already has zones, render them
      if(this.survey.get('zones')) {
        zones = this.survey.get('zones');

        // Add each zone to the map
        _.each(zones, function(zone) {
          layer = L.geoJson(zone, {
            style: this.style
          });
          this.drawnItems.addLayer(layer);
          zone.layer = layer;
        }.bind(this));

        this.zones.reset(zones.features);
      }

      // Renter the form for the zones
      $('#map-zones').html(this.zonesTemplate({
        zones: this.zones.toJSON()
      }));
    },

    removeZone: function(event) {
      var index = $(event.target).attr('data-index');
      var model = this.zones.at(index);

      this.drawnItems.removeLayer(model.get('layer'));
      this.zones.remove(model);
      $(event.target).parent().remove();
    },

    getZones: function() {
      // Update the names
      $('#survey-zone-form input').each(function(index, $input) {
        this.zones.at(index).attributes.properties.name = $input.value;
      }.bind(this));
      return this.zones.toJSON();
    }

  });

  return MapDrawView;

});
<|endoftext|>


<commit_before>/**
 * Given a target object on input, it will be "filled" with the properties of objects as long as
 * the properties of other objects are not already present in the target object.
 *
 * @param {Object} target
 * @param {...Object} fillers
 *
 * @return {Object}
 * The original `target` object is returned
 */
export default function objectFill (target, ...fillers) {
    fillers.forEach(filler => {
        if (filler) {
            Object.keys(filler).forEach(fillerAttr => {
                if (!(fillerAttr in target)) {
                    target[fillerAttr] = filler[fillerAttr];
                }
            });
        }
    });

    return target;
}<commit_msg>objectFill - recursive support<commit_after>/**
 * Given a target object on input, it will be "filled" with the properties of objects as long as
 * the properties of other objects are not already present in the target object.
 *
 * @param {Object} target
 * @param {...Object} fillers
 *
 * @return {Object}
 * The original `target` object is returned
 */
export function objectFill (target, ...fillers) {
    fillers.forEach(filler => {
        if (filler) {
            Object.keys(filler).forEach(attrName => {
                if (!(attrName in target)) {
                    target[attrName] = filler[attrName];
                }
                else if (isPureObject(target[attrName] && isPureObject(filler[attrName]))) {
                    objectFill(target[attrName], filler[attrName]);
                }
            });
        }
    });

    return target;
}

function isPureObject(obj) {
    return Object.prototype.toString.call(obj) === "[object Object]";
}

// Default export
export default objectFill;<|endoftext|>


<commit_before>// Define Pokemon Object. Requires `Data` to be defined.

function Pokemon(name, set) {
	this.id = toID(name);
	if (!this.id) return false;
	var set = set || {item:"",ability:"",ivs:{},evs:{}};
	for (var prop in Data.Pokedex[this.id]) this[prop] = Data.Pokedex[this.id][prop];
	this.set = {ivs:{},evs:{}};
	for (var prop in set) this.set[prop] = set[prop];
	this.item = toID(this.set.item || (this.set.items && this.set.items[0]) || "");
	this.ability = toID(this.set.ability || (this.set.abilities && this.set.abilities[0]) || this.abilities[0]);
	this.nature = this.set.nature || (this.set.natures && this.set.natures[0]) || "Docile";
	this.level = parseInt(this.set.level) || 100;
	var statTable = {"hp" : "HP","atk" : "Atk","def" : "Def","spa" : "SpA","spd" : "SpD","spe" : "Spe"}
	this.ivs = {}; this.evs = {};
	this.baseStats = this.baseStats || {};
	this.boosts = this.set.boosts || {};
	for (var stat in statTable) {
		this.baseStats[stat] = parseInt(this.baseStats[stat]) || 10;
		this.ivs[stat] = parseInt(this.set.ivs[stat]) || 31;
		this.evs[stat] = parseInt(this.set.evs[stat]) || 0;
		if (stat !== 'hp') this.boosts[stat] = parseInt(this.boosts[stat]) || 0;
	}
	this.calcStats();
	this.moveset = (this.set.moveset || this.randomMoveset() || []).slice(0,4).map(toID);
	while (this.moveset.length < 4) this.moveset.push('');
	if (!this.genderRatio && !this.gender) this.genderRatio = {M:0.5,F:0.5};
	this.gender = this.gender || this.set.gender || this.randomGender();
	var hpTypes = ["Fighting", "Flying", "Poison", "Ground", "Rock", "Bug", "Ghost", "Steel", "Fire", "Water", "Grass", "Electric", "Psychic", "Ice", "Dragon", "Dark"];
	var hpTypeIndex = Math.floor(parseInt(function() { var buf = ""; buf += ''+(this.ivs[stat] % 2) ; return buf; }, 2) * 15 / 63)
	this.hpType = hpTypes[hpTypeIndex];
	this.hpPower = 60; //Math.floor(parseInt(function() { var buf = ""; for (var stat in statTable) { buf += ''+((this.ivs[stat] & 2) / 2) }; return buf; }, 2) * 40 / 63 + 30)
	this.status = this.set.status || "";
	this.currentHP = parseInt(this.set.currentHP || this.stats['hp']);
};

Pokemon.prototype.changeSet = function(set) {
	set = set || {item:"",ability:"",ivs:{},evs:{}};
	this.set = {};
	for (var prop in set) this.set[prop] = set[prop];
	this.item = toID(this.set.item || (this.set.items && this.set.items[0]) || "");
	this.ability = toID(this.set.ability || (this.set.abilities && this.set.abilities[0]) || this.abilities[0]);
	this.nature = this.set.nature || "Docile";
	this.level = this.set.level || 100;
	var statTable = {"hp" : "HP" , "atk" : "Atk" , "def" : "Def" , "spa" : "SpA" , "spd" : "SpD" , "spe" : "Spe"};
	this.ivs = {}; this.evs = {};
	for (var stat in statTable) {
		this.baseStats[stat] = this.baseStats[stat] || 10;
		this.ivs[stat] = this.set.ivs[stat] || 31;
		this.evs[stat] = this.set.evs[stat] || 0;
	}
	this.moveset = (this.set.moveset || ["","","",""]).map(toID);
};
Pokemon.prototype.calcStats = function() {
	this.stats = {};
	for (var stat in {"hp":"HP","atk":"Atk","def":"Def","spa":"SpA","spd":"SpD","spe":"Spe"}) {
		if (stat === 'hp') this.stats[stat] = (this.baseStats[stat] === 1 ? 1 : Math.floor(Math.floor(2*this.baseStats[stat] + this.ivs[stat] + this.evs[stat]/4) * this.level/100) + this.level + 10);
		else {
			var nature = Data.Natures[this.nature];
			var natureBoost = (nature.plus === stat ? 1.1 : (nature.minus === stat ? 0.9 : 1));
			this.stats[stat] = Math.floor(Math.floor(Math.floor(2*this.baseStats[stat] + this.ivs[stat] + this.evs[stat]/4) * this.level/100 + 5) * natureBoost);
		}
	}
}
Pokemon.prototype.randomGender = function () {
	if (!this.genderRatio) return this.gender || "N";
	return (Math.floor(Math.random()*0x100)/0x100 < this.genderRatio['M'] ? 'F' : 'M');
};
Pokemon.prototype.randomMoveset = function() {
	if (!Data.getLearnset) return null;
	var lset = Data.getLearnset(this.id);
	if (!lset) return false;
	var moves = Object.keys(lset['sketch'] ? Data.Movedex : lset);
	var randSet = [];
	for (var i = 0; i < 4; i++) {
		randSet.push(moves[parseInt(Math.random()*moves.length)]);
	}
	return randSet;
};
Pokemon.prototype.resetDetails = function() {
	var newPoke = new Pokemon(this.id);
	for (var prop in newPoke) this[prop] = newPoke[prop];
};
Pokemon.prototype.update = function() {
	this.calcStats();
};
<commit_msg>Add `happiness` field to `Pokemon`<commit_after>// Define Pokemon Object. Requires `Data` to be defined.

function Pokemon(name, set) {
	this.id = toID(name);
	if (!this.id) return false;
	var set = set || {item:"",ability:"",ivs:{},evs:{}};
	for (var prop in Data.Pokedex[this.id]) this[prop] = Data.Pokedex[this.id][prop];
	this.set = {ivs:{},evs:{}};
	for (var prop in set) this.set[prop] = set[prop];
	this.item = toID(this.set.item || (this.set.items && this.set.items[0]) || "");
	this.ability = toID(this.set.ability || (this.set.abilities && this.set.abilities[0]) || this.abilities[0]);
	this.nature = this.set.nature || (this.set.natures && this.set.natures[0]) || "Docile";
	this.level = parseInt(this.set.level) || 100;
	var statTable = {"hp" : "HP","atk" : "Atk","def" : "Def","spa" : "SpA","spd" : "SpD","spe" : "Spe"}
	this.ivs = {}; this.evs = {};
	this.baseStats = this.baseStats || {};
	this.boosts = this.set.boosts || {};
	for (var stat in statTable) {
		this.baseStats[stat] = parseInt(this.baseStats[stat]) || 10;
		this.ivs[stat] = parseInt(this.set.ivs[stat]) || 31;
		this.evs[stat] = parseInt(this.set.evs[stat]) || 0;
		if (stat !== 'hp') this.boosts[stat] = parseInt(this.boosts[stat]) || 0;
	}
	this.calcStats();
	this.moveset = (this.set.moveset || this.randomMoveset() || []).slice(0,4).map(toID);
	while (this.moveset.length < 4) this.moveset.push('');
	if (!this.genderRatio && !this.gender) this.genderRatio = {M:0.5,F:0.5};
	this.gender = this.gender || this.set.gender || this.randomGender();
	var hpTypes = ["Fighting", "Flying", "Poison", "Ground", "Rock", "Bug", "Ghost", "Steel", "Fire", "Water", "Grass", "Electric", "Psychic", "Ice", "Dragon", "Dark"];
	var hpTypeIndex = Math.floor(parseInt(function() { var buf = ""; buf += ''+(this.ivs[stat] % 2) ; return buf; }, 2) * 15 / 63)
	this.hpType = hpTypes[hpTypeIndex];
	this.hpPower = 60; //Math.floor(parseInt(function() { var buf = ""; for (var stat in statTable) { buf += ''+((this.ivs[stat] & 2) / 2) }; return buf; }, 2) * 40 / 63 + 30)
	this.status = this.set.status || "";
	this.currentHP = parseInt(this.set.currentHP || this.stats['hp']);
	this.happiness = parseInt(this.set.happiness || 255);
};

Pokemon.prototype.changeSet = function(set) {
	set = set || {item:"",ability:"",ivs:{},evs:{}};
	this.set = {};
	for (var prop in set) this.set[prop] = set[prop];
	this.item = toID(this.set.item || (this.set.items && this.set.items[0]) || "");
	this.ability = toID(this.set.ability || (this.set.abilities && this.set.abilities[0]) || this.abilities[0]);
	this.nature = this.set.nature || "Docile";
	this.level = this.set.level || 100;
	var statTable = {"hp" : "HP" , "atk" : "Atk" , "def" : "Def" , "spa" : "SpA" , "spd" : "SpD" , "spe" : "Spe"};
	this.ivs = {}; this.evs = {};
	for (var stat in statTable) {
		this.baseStats[stat] = this.baseStats[stat] || 10;
		this.ivs[stat] = this.set.ivs[stat] || 31;
		this.evs[stat] = this.set.evs[stat] || 0;
	}
	this.moveset = (this.set.moveset || ["","","",""]).map(toID);
};
Pokemon.prototype.calcStats = function() {
	this.stats = {};
	for (var stat in {"hp":"HP","atk":"Atk","def":"Def","spa":"SpA","spd":"SpD","spe":"Spe"}) {
		if (stat === 'hp') this.stats[stat] = (this.baseStats[stat] === 1 ? 1 : Math.floor(Math.floor(2*this.baseStats[stat] + this.ivs[stat] + this.evs[stat]/4) * this.level/100) + this.level + 10);
		else {
			var nature = Data.Natures[this.nature];
			var natureBoost = (nature.plus === stat ? 1.1 : (nature.minus === stat ? 0.9 : 1));
			this.stats[stat] = Math.floor(Math.floor(Math.floor(2*this.baseStats[stat] + this.ivs[stat] + this.evs[stat]/4) * this.level/100 + 5) * natureBoost);
		}
	}
}
Pokemon.prototype.randomGender = function () {
	if (!this.genderRatio) return this.gender || "N";
	return (Math.floor(Math.random()*0x100)/0x100 < this.genderRatio['M'] ? 'F' : 'M');
};
Pokemon.prototype.randomMoveset = function() {
	if (!Data.getLearnset) return null;
	var lset = Data.getLearnset(this.id);
	if (!lset) return false;
	var moves = Object.keys(lset['sketch'] ? Data.Movedex : lset);
	var randSet = [];
	for (var i = 0; i < 4; i++) {
		randSet.push(moves[parseInt(Math.random()*moves.length)]);
	}
	return randSet;
};
Pokemon.prototype.resetDetails = function() {
	var newPoke = new Pokemon(this.id);
	for (var prop in newPoke) this[prop] = newPoke[prop];
};
Pokemon.prototype.update = function() {
	this.calcStats();
};
<|endoftext|>


<commit_before>/**
 * TIME
 * ====
 * All values in milliseconds unless specified
 * Naming convention: T1T2 = T1 - T2
 */

const time = {

  // MJD = JD - 2400000.5 days
  MJDJD  : -2400000.5, // days

  // TT = TAI + 32.184 seconds
  TTTAI  : +32.184,

  // DUT1 = UT1 - UTC = -0.5 (valid from 2014-12-25)
  // http://datacenter.iers.org/eop/-/somos/5Rgv/getTX/17/bulletind-121.txt
  DUT1   : -0.500,

  // TAI - UTC = 35.000 seconds (Leap seconds) (valid from 2012-07-01 until at least 2015-06-30)
  // http://datacenter.iers.org/eop/-/somos/5Rgv/getTX/16/bulletinc-048.txt
  TAIUTC : +35.000,

  // TAI - GPS = 19.000 seconds (fixed)
  TAIGPS : +19.000
};

export default time;
<commit_msg>Update DUT1<commit_after>/**
 * TIME
 * ====
 * All values in milliseconds unless specified
 * Naming convention: T1T2 = T1 - T2
 */

const time = {

  // MJD = JD - 2400000.5 days
  MJDJD  : -2400000.5, // days

  // TT = TAI + 32.184 seconds
  TTTAI  : +32.184,

  // DUT1 = UT1 - UTC = -0.6 (valid from 2015-03-19)
  // http://datacenter.iers.org/eop/-/somos/5Rgv/getTX/17/bulletind-122.txt
  DUT1   : -0.600,

  // TAI - UTC = 35.000 seconds (Leap seconds) (valid from 2012-07-01 until at least 2015-06-30)
  // http://datacenter.iers.org/eop/-/somos/5Rgv/getTX/16/bulletinc-048.txt
  TAIUTC : +35.000,

  // TAI - GPS = 19.000 seconds (fixed)
  TAIGPS : +19.000
};

export default time;
<|endoftext|>


<commit_before>import React, { Component } from 'react';

import Root from './Root';
import Home from './Home';
import Counter from './Counter';
import Splash from './Splash';
import Article from './Article';
import ThemeDaily from './ThemeDaily';
import Editors from './Editors';
import Editor from './Editor';

import routeReducer from '../reducers/routes';

import Theme from '../utils/Theme';
import '../utils/Storage';

storage.load({
  key: 'theme'
}).then(ret => { // 使用保存的主题
  global.theme = ret.name;
}).catch(err => { // 如果没有保存的主题，使用默认主题
  global.theme = Theme.LIGHT;
  storage.save({ // 储存默认主题
    key: 'theme',
    rawData: {
      name: Theme.LIGHT
    }
  });
});

export default class App extends Component {

  renderScene = props => {
    switch (props.scene.key) {
      case 'scene_home':
        return <Home onNavigate={props.onNavigate} />;
      case 'scene_counter':
        return <Counter onNavigate={props.onNavigate} />;
      case 'scene_splash':
        return <Splash onNavigate={props.onNavigate} />;
      case 'scene_article':
        return
;
      case 'scene_theme_daily':
        return <ThemeDaily onNavigate={props.onNavigate} />;
      case 'scene_editors':
        return <Editors onNavigate={props.onNavigate} />;
      case 'scene_editor':
        return <Editor onNavigate={props.onNavigate} />;
      default:
        return null;
    }
  };

  render() {
    return (
      <Root
        reducer={routeReducer}
        renderScene={this.renderScene}
      />
    );
  }
}
<commit_msg>Check update manual.<commit_after>import React, { Component } from 'react';
import CodePush from 'react-native-code-push';
import Root from './Root';
import Home from './Home';
import Counter from './Counter';
import Splash from './Splash';
import Article from './Article';
import ThemeDaily from './ThemeDaily';
import Editors from './Editors';
import Editor from './Editor';

import routeReducer from '../reducers/routes';

import Theme from '../utils/Theme';
import '../utils/Storage';

storage.load({
  key: 'theme'
}).then(ret => { // 使用保存的主题
  global.theme = ret.name;
}).catch(err => { // 如果没有保存的主题，使用默认主题
  global.theme = Theme.LIGHT;
  storage.save({ // 储存默认主题
    key: 'theme',
    rawData: {
      name: Theme.LIGHT
    }
  });
});

@CodePush({ checkFrequency: CodePush.CheckFrequency.MANUAL })
export default class App extends Component {

  renderScene = props => {
    switch (props.scene.key) {
      case 'scene_home':
        return <Home onNavigate={props.onNavigate} />;
      case 'scene_counter':
        return <Counter onNavigate={props.onNavigate} />;
      case 'scene_splash':
        return <Splash onNavigate={props.onNavigate} />;
      case 'scene_article':
        return
;
      case 'scene_theme_daily':
        return <ThemeDaily onNavigate={props.onNavigate} />;
      case 'scene_editors':
        return <Editors onNavigate={props.onNavigate} />;
      case 'scene_editor':
        return <Editor onNavigate={props.onNavigate} />;
      default:
        return null;
    }
  };

  render() {
    return (
      <Root
        reducer={routeReducer}
        renderScene={this.renderScene}
      />
    );
  }
}
<|endoftext|>


<commit_before>Ext.define('Ext.Promise', function() {
    Ext.define('Promise', {
        config: {
            state: null,
            resolvedValue: null,
            thenCallback: null
        },
        then: function(callback, scope) {
            this.thenCallback = callback.bind(scope);
            if (this.state === Ext.Promise.STATE.FULFILLED) {
                this.thenCallback(this.resolvedValue);
            }
        }
    });
    return {
        statics: {
            STATE: {
                PENDING: 0,
                FULFILLED: 1,
                REJECTED: 2
            }
        },

        constructor: function() {
            this.deferred();
        },

        deferred: function() {
            this.promise = Ext.create('Promise');
            this.promise.state = Ext.Promise.STATE.PENDING;
            return this;
        },

        resolve: function(value) {
            console.log(this);
            this.promise.state = Ext.Promise.STATE.FULFILLED;
            this.promise.resolvedValue = value;
            if (this.promise.thenCallback) {
                this.promise.thenCallback(this.promise.resolvedValue);
            }
        }
    };
});
<commit_msg>removes console.log<commit_after>Ext.define('Ext.Promise', function() {
    Ext.define('Promise', {
        config: {
            state: null,
            resolvedValue: null,
            thenCallback: null
        },
        then: function(callback, scope) {
            this.thenCallback = callback.bind(scope);
            if (this.state === Ext.Promise.STATE.FULFILLED) {
                this.thenCallback(this.resolvedValue);
            }
        }
    });
    return {
        statics: {
            STATE: {
                PENDING: 0,
                FULFILLED: 1,
                REJECTED: 2
            }
        },

        constructor: function() {
            this.deferred();
        },

        deferred: function() {
            this.promise = Ext.create('Promise');
            this.promise.state = Ext.Promise.STATE.PENDING;
            return this;
        },

        resolve: function(value) {
            this.promise.state = Ext.Promise.STATE.FULFILLED;
            this.promise.resolvedValue = value;
            if (this.promise.thenCallback) {
                this.promise.thenCallback(this.promise.resolvedValue);
            }
        }
    };
});
<|endoftext|>


<commit_before>const EXPORT = ["HttpWatcher"];

const HTTP_ON_MODIFY_REQUEST = "http-on-modify-request";
const HTTP_ON_EXAMINE_RESPONSE = "http-on-examine-response";

var HttpWatcher = {
    get taskSet HW_get_taskSet() {
        let taskSet = null;
        if (shared.has("HttpWatcherTaskSet")) {
            taskSet = shared.get("HttpWatcherTaskSet");
        } else {
            taskSet = {};
            shared.set("HttpWatcherTaskSet", taskSet);
        }
        delete this.taskSet;
        return this.taskSet = taskSet;
    },

    pushTask: function HW__pushTask(url, data) {
        //if (url in this.taskSet) return;
        this.taskSet[url] = data;
    },

    popTask: function HW__popTask(url) {
        let task = null;
        if (url in this.taskSet) {
            task = this.taskSet[url];
            delete this.taskSet[url];
        }
        return task;
    },

    onRequest: function HW_onRequest(channel) {
        let data = this._getPostData(channel);
        this.pushTask(data.url, data);
    },

    _getPostData: function HW__getPostData(channel) {
        if (!(channel instanceof Ci.nsIUploadChannel)) return;
        let rawStream = channel.uploadStream;
        if (!(rawStream instanceof Ci.nsISeekableStream)) return;
        let hasHeaders = rawStream instanceof Ci.nsIMIMEInputStream;
        let stream = Cc["@mozilla.org/scriptableinputstream;1"].
                     createInstance(Ci.nsIScriptableInputStream);
        stream.init(rawStream);
        let body = stream.read(stream.available());
        rawStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
        if (hasHeaders)
            body = body.replace(/^[\s\S]*?\r\n\r\n/, "");
        return this._toMap(body);
    },

    _toMap: function HW__toMap(string) {
        let result = {};
        string.replace(/\+/g, "%20").split("&").forEach(function (pair) {
            let [key, value] = pair.split("=");
            try {
                value = decodeURIComponent(value);
            } catch (ex) {}
            result[key] = value;
        });
        return result;
    },

    onResponse: function HW_onResponse(channel) {
        let url = this._getBookmakedURL(channel);
        if (!url) return;
        let task = this.popTask(url);
        if (!task) return;
        this.performTask(task);
    },

    _getBookmakedURL: function HW__getBookmarkedURL(channel) {
        try {
            return channel.getResponseHeader("X-Bookmark-URL");
        } catch (ex) {
            return null;
        }
    },

    performTask: function HW_performTask(task) {
        // ブックマーク成功したら、sync する
        // これにより、リモートとのデータの同期がとれる
        p('performTask');
        // XXX: Sync に依存してしまう
        Sync.sync();

        let bookmark = Model.Bookmark.findByUrl(task.url)[0];
        if (bookmark) {
            // すでに存在するブックマークは Sync で
            // 同期できないので、ここで DB を更新しておく。
            if (task.title)
                bookmark.title = task.title;
            bookmark.comment = task.comment;
            bookmark.save();
            EventService.dispatch('BookmarksUpdated');
        }
    },

    startObserving: function HW_startObserving() {
        ObserverService.addObserver(this, HTTP_ON_MODIFY_REQUEST, false);
        ObserverService.addObserver(this, HTTP_ON_EXAMINE_RESPONSE, false);
    },

    stopObserving: function HW_stopObserving() {
        ObserverService.removeObserver(this, HTTP_ON_MODIFY_REQUEST);
        ObserverService.removeObserver(this, HTTP_ON_EXAMINE_RESPONSE);
    },

    observe: function HW_observe(subject, topic, data) {
        if (!(subject instanceof Ci.nsIHttpChannel) ||
            !this._isHBookmarkOperation(subject))
            return;
        switch (topic) {
        case HTTP_ON_MODIFY_REQUEST:
            this.onRequest(subject);
            break;

        case HTTP_ON_EXAMINE_RESPONSE:
            this.onResponse(subject);
            break;
        }
    },

    _isHBookmarkOperation: function HW__isHBookmarkOperation(channel) {
        let uri = channel.URI;
        return /\.hatena\.ne\.jp$/.test(uri.host) &&
               /^\/(?:bookmarklet\.edit|[\w-]+\/add\.edit)\b/.test(uri.path);
    }
};

function printRequestHeaders(channel) {
    let visitor = {
        visitHeader: function visitHeader(header, value) {
            this[header] = value;
        }
    };
    channel.visitRequestHeaders(visitor);
    delete visitor.visitHeader;
    p(channel.URI.spec);
    p.apply(null, [header + ": " + value
                   for ([header, value] in Iterator(visitor))]);
}

function printPostData(channel) {
    if (!(channel instanceof Ci.nsIUploadChannel)) return;
    let rawStream = channel.uploadStream;
    if (!(rawStream instanceof Ci.nsISeekableStream)) return;
    let stream = Cc["@mozilla.org/scriptableinputstream;1"].
                 createInstance(Ci.nsIScriptableInputStream);
    stream.init(rawStream);
    let body = stream.read(stream.available());
    rawStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
    p("Post Data:", body);
}

HttpWatcher.startObserving();
window.addEventListener("unload", function () {
    p("stop HTTP observing");
    HttpWatcher.stopObserving();
}, false);
<commit_msg>同期処理がうまくいかなかったら再試行するように。<commit_after>const EXPORT = ["HttpWatcher"];

const HTTP_ON_MODIFY_REQUEST = "http-on-modify-request";
const HTTP_ON_EXAMINE_RESPONSE = "http-on-examine-response";

var HttpWatcher = {
    get taskSet HW_get_taskSet() {
        let taskSet = null;
        if (shared.has("HttpWatcherTaskSet")) {
            taskSet = shared.get("HttpWatcherTaskSet");
        } else {
            taskSet = {};
            shared.set("HttpWatcherTaskSet", taskSet);
        }
        delete this.taskSet;
        return this.taskSet = taskSet;
    },

    pushTask: function HW__pushTask(url, data) {
        //if (url in this.taskSet) return;
        this.taskSet[url] = data;
    },

    popTask: function HW__popTask(url) {
        let task = null;
        if (url in this.taskSet) {
            task = this.taskSet[url];
            delete this.taskSet[url];
        }
        return task;
    },

    onRequest: function HW_onRequest(channel) {
        let data = this._getPostData(channel);
        this.pushTask(data.url, data);
    },

    _getPostData: function HW__getPostData(channel) {
        if (!(channel instanceof Ci.nsIUploadChannel)) return;
        let rawStream = channel.uploadStream;
        if (!(rawStream instanceof Ci.nsISeekableStream)) return;
        let hasHeaders = rawStream instanceof Ci.nsIMIMEInputStream;
        let stream = Cc["@mozilla.org/scriptableinputstream;1"].
                     createInstance(Ci.nsIScriptableInputStream);
        stream.init(rawStream);
        let body = stream.read(stream.available());
        rawStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
        if (hasHeaders)
            body = body.replace(/^[\s\S]*?\r\n\r\n/, "");
        return this._toMap(body);
    },

    _toMap: function HW__toMap(string) {
        let result = {};
        string.replace(/\+/g, "%20").split("&").forEach(function (pair) {
            let [key, value] = pair.split("=");
            try {
                value = decodeURIComponent(value);
            } catch (ex) {}
            result[key] = value;
        });
        return result;
    },

    onResponse: function HW_onResponse(channel) {
        let url = this._getBookmakedURL(channel);
        if (!url) return;
        let task = this.popTask(url);
        if (!task) return;
        this.performTask(task);
    },

    _getBookmakedURL: function HW__getBookmarkedURL(channel) {
        try {
            return channel.getResponseHeader("X-Bookmark-URL");
        } catch (ex) {
            return null;
        }
    },

    performTask: function HW_performTask(task) {
        // ブックマーク成功したら、sync する
        // これにより、リモートとのデータの同期がとれる
        // XXX: Sync に依存してしまう
        let listener = Sync.createListener("complete", function onSync() {
            p('Sync completed');
            if (Model.Bookmark.findByUrl(task.url).length) {
                p('Sync succeeded');
                listener.unlisten();
            } else {
                p(task.url + ' is not registered.  Retry sync.');
                // 同期が間に合わなかったら少し待って再度同期する。
                setTimeout(method(Sync, 'sync'), 2000);
            }
        }, null, 0, false);
        Sync.sync();

        let bookmark = Model.Bookmark.findByUrl(task.url)[0];
        if (bookmark) {
            // すでに存在するブックマークは Sync で
            // 同期できないので、ここで DB を更新しておく。
            if (task.title)
                bookmark.title = task.title;
            bookmark.comment = task.comment;
            bookmark.save();
            EventService.dispatch('BookmarksUpdated');
        }
    },

    startObserving: function HW_startObserving() {
        ObserverService.addObserver(this, HTTP_ON_MODIFY_REQUEST, false);
        ObserverService.addObserver(this, HTTP_ON_EXAMINE_RESPONSE, false);
    },

    stopObserving: function HW_stopObserving() {
        ObserverService.removeObserver(this, HTTP_ON_MODIFY_REQUEST);
        ObserverService.removeObserver(this, HTTP_ON_EXAMINE_RESPONSE);
    },

    observe: function HW_observe(subject, topic, data) {
        if (!(subject instanceof Ci.nsIHttpChannel) ||
            !this._isHBookmarkOperation(subject))
            return;
        switch (topic) {
        case HTTP_ON_MODIFY_REQUEST:
            this.onRequest(subject);
            break;

        case HTTP_ON_EXAMINE_RESPONSE:
            this.onResponse(subject);
            break;
        }
    },

    _isHBookmarkOperation: function HW__isHBookmarkOperation(channel) {
        let uri = channel.URI;
        return /\.hatena\.ne\.jp$/.test(uri.host) &&
               /^\/(?:bookmarklet\.edit|[\w-]+\/add\.edit)\b/.test(uri.path);
    }
};

function printRequestHeaders(channel) {
    let visitor = {
        visitHeader: function visitHeader(header, value) {
            this[header] = value;
        }
    };
    channel.visitRequestHeaders(visitor);
    delete visitor.visitHeader;
    p(channel.URI.spec);
    p.apply(null, [header + ": " + value
                   for ([header, value] in Iterator(visitor))]);
}

function printPostData(channel) {
    if (!(channel instanceof Ci.nsIUploadChannel)) return;
    let rawStream = channel.uploadStream;
    if (!(rawStream instanceof Ci.nsISeekableStream)) return;
    let stream = Cc["@mozilla.org/scriptableinputstream;1"].
                 createInstance(Ci.nsIScriptableInputStream);
    stream.init(rawStream);
    let body = stream.read(stream.available());
    rawStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
    p("Post Data:", body);
}

HttpWatcher.startObserving();
window.addEventListener("unload", function () {
    p("stop HTTP observing");
    HttpWatcher.stopObserving();
}, false);
<|endoftext|>