<commit_before>function rockPaperScissor(userInput) {
        var choices = ['ROCK', 'PAPER', 'SCISSOR'];
        var computerChoice = choices[ Math.floor(Math.random() * 3) ];

        computerChoose(computerChoice);
        userChoose(userInput);
        clearInterval(showRandomInterval);

        if (userInput === 'ROCK' ) {
            if (computerChoice === 'ROCK') {
                dialogText('Rock to rock...Seems we need to fight more to see who is winner');
                $('.rockTorock').show(1000);

            }
            else if (computerChoice === 'PAPER') {
                dialogText('My paper wrapped your rock!..HaHaHa!');
                $('.rockIswrapped').show(1000);

            }
            else if (computerChoice === 'SCISSOR') {
                dialogText('Oh no... your rock is too strong..please show some mercy!');
                $('.rockBeatscissor').show(1000);

            }
        }
        if (userInput === 'PAPER') {
            if (computerChoice === 'ROCK') {
                dialogText('I am struggling to make my rock to run away from your paper..');
                $('.paperWraprock').show(1000);

            }
            else if (computerChoice === 'PAPER') {
                dialogText('Two papers....what we can do with two papers...? Cannot even fight!');
                $('.paperAndpaper').show(1000);

            }
            else if(computerChoice === 'SCISSOR') {
                dialogText('Scissor..Scissor..I am going to cut cut cut!');
                $('.scissorAttack').show(1000);
            }
            setTimeout(function(){
                    $('.restart').show();
                },1000);
        }
        if (userInput === 'SCISSOR') {
            if (computerChoice === 'ROCK') {
                dialogText('My rock is much stronger than your scissor! What you gonna do ?');
                $('.rockCrashuser').show(1000);

            }
            else if (computerChoice === 'PAPER') {
                dialogText('No No No! Please do not cut my paper');
                $('.beggingCat').show(1000);

            }
            else if (computerChoice === 'SCISSOR') {
                dialogText('Both of us are scissor..How about be my friend? PEACE!');
                $('.friend').show(1000);
            }
        }
        setTimeout(function(){
            $('.restart').show();
        }, 2000);
}

function userChoose(choice) {
    window.humanChoice = choice;

    $('.userside').hide();
    if (choice === 'ROCK'){
       $('.userside.rock').show();
    }
    else if (choice === 'PAPER'){
        $('.userside.paper').show();
    }
    else if (choice === 'SCISSOR'){
        $('.userside.scissor').show();
    }
}

function computerChoose(choice) {
    window.computerChoice = choice;

    $('.computerside').hide();
    if (choice === 'ROCK'){
       $('.computerside.rock').show();
    }
    else if (choice === 'PAPER'){
        $('.computerside.paper').show();
    }
    else if (choice === 'SCISSOR'){
        $('.computerside.scissor').show();
    }
}

function dialogText(text){
    $('#dialog').text(text).show(1000);
}

function showRandom(){
    var choices = ['ROCK', 'PAPER', 'SCISSOR'];
    var computerChoice = choices[ Math.floor(Math.random() * 3) ];
    var humanChoice = choices[ Math.floor(Math.random() * 3) ];

    if (window.computerChoice === computerChoice){
        if(computerChoice === 'ROCK') {
            computerChoice = 'PAPER'
        }
        else if(computerChoice === 'PAPER') {
            computerChoice = 'SCISSOR'
        }
        else if(computerChoice === 'SCISSOR') {
            computerChoice = 'ROCK'
        }
    }

    if (window.humanChoice === humanChoice) {
        if (humanChoice === 'ROCK') {
            humanChoice = 'SCISSOR'
        }
        else if (humanChoice === 'PAPER') {
            humanChoice = 'ROCK'
        }
        else if (humanChoice === 'SCISSOR'){
            humanChoice = 'PAPER'
        }
    }
    computerChoose(computerChoice);
    userChoose(humanChoice);
}

$('document').ready(function(){
        $('.cat,.boy').hide(500);
        $('.cat2,.boy2').show(1000);
        setTimeout(function(){$('.cat2,.boy2').hide();}, 900);
        $('.cat3,.boy3').show(2000);

        $('.restart').click(againClick);
        setTimeout(function(){
            $('.start').show();
        }, 3000);
        $('.start').click(function(){
            setTimeout(function(){
                $('.BUTTON').show(1000);
            }, 2000);
            $('.start').hide();
            $('.graphic').fadeOut();
            window.showRandomInterval = setInterval(showRandom, 90);
        });

});

function againClick(){
   $('.cinematic').hide(100);
   $('#dialog').hide(100);
   $('.restart').hide();
   window.showRandomInterval = setInterval(showRandom, 80);
}

/*window.onload = function(){
    var hoverSound = document.getElementById('hoverSound');
    var start = document.getElementsByClassName('start');

    start.onmousehover=function(){
        hoverSound.play();
        return false;
    };
}; */
<commit_msg>Removed unnecessary function <commit_after>function rockPaperScissor(userInput) {
        var choices = ['ROCK', 'PAPER', 'SCISSOR'];
        var computerChoice = choices[ Math.floor(Math.random() * 3) ];

        computerChoose(computerChoice);
        userChoose(userInput);
        clearInterval(showRandomInterval);

        if (userInput === 'ROCK' ) {
            if (computerChoice === 'ROCK') {
                dialogText('Rock to rock...Seems we need to fight more to see who is winner');
                $('.rockTorock').show(1000);

            }
            else if (computerChoice === 'PAPER') {
                dialogText('My paper wrapped your rock!..HaHaHa!');
                $('.rockIswrapped').show(1000);

            }
            else if (computerChoice === 'SCISSOR') {
                dialogText('Oh no... your rock is too strong..please show some mercy!');
                $('.rockBeatscissor').show(1000);

            }
        }
        if (userInput === 'PAPER') {
            if (computerChoice === 'ROCK') {
                dialogText('I am struggling to make my rock to run away from your paper..');
                $('.paperWraprock').show(1000);

            }
            else if (computerChoice === 'PAPER') {
                dialogText('Two papers....what we can do with two papers...? Cannot even fight!');
                $('.paperAndpaper').show(1000);

            }
            else if(computerChoice === 'SCISSOR') {
                dialogText('Scissor..Scissor..I am going to cut cut cut!');
                $('.scissorAttack').show(1000);
            }
        }

        if (userInput === 'SCISSOR') {
            if (computerChoice === 'ROCK') {
                dialogText('My rock is much stronger than your scissor! What you gonna do ?');
                $('.rockCrashuser').show(1000);

            }
            else if (computerChoice === 'PAPER') {
                dialogText('No No No! Please do not cut my paper');
                $('.beggingCat').show(1000);

            }
            else if (computerChoice === 'SCISSOR') {
                dialogText('Both of us are scissor..How about be my friend? PEACE!');
                $('.friend').show(1000);
            }
        }
        setTimeout(function(){
            $('.restart').show();
        }, 2000);
}

function userChoose(choice) {
    window.humanChoice = choice;

    $('.userside').hide();
    if (choice === 'ROCK'){
       $('.userside.rock').show();
    }
    else if (choice === 'PAPER'){
        $('.userside.paper').show();
    }
    else if (choice === 'SCISSOR'){
        $('.userside.scissor').show();
    }
}

function computerChoose(choice) {
    window.computerChoice = choice;

    $('.computerside').hide();
    if (choice === 'ROCK'){
       $('.computerside.rock').show();
    }
    else if (choice === 'PAPER'){
        $('.computerside.paper').show();
    }
    else if (choice === 'SCISSOR'){
        $('.computerside.scissor').show();
    }
}

function dialogText(text){
    $('#dialog').text(text).show(1000);
}

function showRandom(){
    var choices = ['ROCK', 'PAPER', 'SCISSOR'];
    var computerChoice = choices[ Math.floor(Math.random() * 3) ];
    var humanChoice = choices[ Math.floor(Math.random() * 3) ];

    if (window.computerChoice === computerChoice){
        if(computerChoice === 'ROCK') {
            computerChoice = 'PAPER'
        }
        else if(computerChoice === 'PAPER') {
            computerChoice = 'SCISSOR'
        }
        else if(computerChoice === 'SCISSOR') {
            computerChoice = 'ROCK'
        }
    }

    if (window.humanChoice === humanChoice) {
        if (humanChoice === 'ROCK') {
            humanChoice = 'SCISSOR'
        }
        else if (humanChoice === 'PAPER') {
            humanChoice = 'ROCK'
        }
        else if (humanChoice === 'SCISSOR'){
            humanChoice = 'PAPER'
        }
    }
    computerChoose(computerChoice);
    userChoose(humanChoice);
}

$('document').ready(function(){
        $('.cat,.boy').hide(500);
        $('.cat2,.boy2').show(1000);
        setTimeout(function(){$('.cat2,.boy2').hide();}, 900);
        $('.cat3,.boy3').show(2000);

        $('.restart').click(againClick);
        setTimeout(function(){
            $('.start').show();
        }, 3000);
        $('.start').click(function(){
            setTimeout(function(){
                $('.BUTTON').show(1000);
            }, 2000);
            $('.start').hide();
            $('.graphic').fadeOut();
            window.showRandomInterval = setInterval(showRandom, 90);
        });

});

function againClick(){
   $('.cinematic').hide(100);
   $('#dialog').hide(100);
   $('.restart').hide();
   window.showRandomInterval = setInterval(showRandom, 80);
}

/*window.onload = function(){
    var hoverSound = document.getElementById('hoverSound');
    var start = document.getElementsByClassName('start');

    start.onmousehover=function(){
        hoverSound.play();
        return false;
    };
}; */
<|endoftext|>


<commit_before>// @flow

import type { GlobalTheme } from '../types/themes';
import type { AppState } from '../redux/redux-setup';

import * as React from 'react';
import { StyleSheet } from 'react-native';
import { createSelector } from 'reselect';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';

import { selectBackgroundIsDark } from '../navigation/nav-selectors';
import { NavContext } from '../navigation/navigation-context';

const light = Object.freeze({
  redButton: '#BB8888',
  greenButton: '#88BB88',
  mintButton: '#44CC99',
  redText: '#AA0000',
  greenText: 'green',
  link: '#036AFF',
  panelBackground: '#E9E9EF',
  panelBackgroundLabel: '#888888',
  panelForeground: 'white',
  panelForegroundBorder: '#CCCCCC',
  panelForegroundLabel: 'black',
  panelForegroundSecondaryLabel: '#333333',
  panelForegroundTertiaryLabel: '#888888',
  panelIosHighlightUnderlay: '#EEEEEEDD',
  panelSecondaryForeground: '#F5F5F5',
  panelSecondaryForegroundBorder: '#D1D1D6',
  modalForeground: 'white',
  modalForegroundBorder: '#CCCCCC',
  modalForegroundLabel: 'black',
  modalForegroundSecondaryLabel: '#888888',
  modalForegroundTertiaryLabel: '#AAAAAA',
  modalBackground: '#EEEEEE',
  modalBackgroundLabel: '#333333',
  modalBackgroundSecondaryLabel: '#AAAAAA',
  modalIosHighlightUnderlay: '#CCCCCCDD',
  modalSubtext: '#CCCCCC',
  modalSubtextLabel: '#555555',
  modalButton: '#BBBBBB',
  modalButtonLabel: 'black',
  modalContrastBackground: 'black',
  modalContrastForegroundLabel: 'white',
  modalContrastOpacity: 0.7,
  listForegroundLabel: 'black',
  listForegroundSecondaryLabel: '#333333',
  listForegroundTertiaryLabel: '#666666',
  listForegroundQuaternaryLabel: '#AAAAAA',
  listBackground: 'white',
  listBackgroundLabel: 'black',
  listBackgroundSecondaryLabel: '#444444',
  listBackgroundTernaryLabel: '#999999',
  listSeparator: '#EEEEEE',
  listSeparatorLabel: '#555555',
  listInputBar: '#E2E2E2',
  listInputBorder: '#AAAAAAAA',
  listInputButton: '#888888',
  listInputBackground: '#DDDDDD',
  listIosHighlightUnderlay: '#DDDDDDDD',
  listSearchBackground: '#DDDDDD',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#DDDDDDBB',
  navigationCard: '#FFFFFF',
});
export type Colors = $Exact<typeof light>;

const colorsPropType = PropTypes.objectOf(
  PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
);

const dark: Colors = Object.freeze({
  redButton: '#FF4444',
  greenButton: '#44BB44',
  mintButton: '#44CC99',
  redText: '#FF4444',
  greenText: '#44FF44',
  link: '#129AFF',
  panelBackground: '#1C1C1E',
  panelBackgroundLabel: '#C7C7CC',
  panelForeground: '#3A3A3C',
  panelForegroundBorder: '#2C2C2E',
  panelForegroundLabel: 'white',
  panelForegroundSecondaryLabel: '#CCCCCC',
  panelForegroundTertiaryLabel: '#AAAAAA',
  panelIosHighlightUnderlay: '#444444DD',
  panelSecondaryForeground: '#333333',
  panelSecondaryForegroundBorder: '#666666',
  modalForeground: '#1C1C1E',
  modalForegroundBorder: '#1C1C1E',
  modalForegroundLabel: 'white',
  modalForegroundSecondaryLabel: '#AAAAAA',
  modalForegroundTertiaryLabel: '#666666',
  modalBackground: '#2C2C2E',
  modalBackgroundLabel: '#CCCCCC',
  modalBackgroundSecondaryLabel: '#555555',
  modalIosHighlightUnderlay: '#AAAAAA88',
  modalSubtext: '#444444',
  modalSubtextLabel: '#AAAAAA',
  modalButton: '#666666',
  modalButtonLabel: 'white',
  modalContrastBackground: 'white',
  modalContrastForegroundLabel: 'black',
  modalContrastOpacity: 0.85,
  listForegroundLabel: 'white',
  listForegroundSecondaryLabel: '#CCCCCC',
  listForegroundTertiaryLabel: '#999999',
  listForegroundQuaternaryLabel: '#555555',
  listBackground: '#1C1C1E',
  listBackgroundLabel: '#C7C7CC',
  listBackgroundSecondaryLabel: '#BBBBBB',
  listBackgroundTernaryLabel: '#888888',
  listSeparator: '#3A3A3C',
  listSeparatorLabel: '#EEEEEE',
  listInputBar: '#555555',
  listInputBorder: '#333333',
  listInputButton: '#AAAAAA',
  listInputBackground: '#38383C',
  listIosHighlightUnderlay: '#BBBBBB88',
  listSearchBackground: '#555555',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#444444DD',
  navigationCard: '#2A2A2A',
});
const colors = { light, dark };

const colorsSelector: (state: AppState) => Colors = createSelector(
  (state: AppState) => state.globalThemeInfo.activeTheme,
  (theme: ?GlobalTheme) => {
    const explicitTheme = theme ? theme : 'light';
    return colors[explicitTheme];
  },
);

const magicStrings = new Set();
for (let theme in colors) {
  for (let magicString in colors[theme]) {
    magicStrings.add(magicString);
  }
}

type Styles = { [name: string]: { [field: string]: number | string } };

type ReplaceField = (input: any) => any;
type ReplaceStyleObject = <Obj: { [key: string]: number | string }>(
  Obj,
) => $ObjMap<Obj, ReplaceField>;

export type StyleSheetOf<S: Styles> = $Call<
  typeof StyleSheet.create,
  $ObjMap<S, ReplaceStyleObject>,
>;

function stylesFromColors<IS: Styles>(
  obj: IS,
  themeColors: Colors,
): StyleSheetOf<IS> {
  const result = {};
  for (let key in obj) {
    const style = obj[key];
    const filledInStyle = { ...style };
    for (let styleKey in style) {
      const styleValue = style[styleKey];
      if (magicStrings.has(styleValue)) {
        const mapped = themeColors[styleValue];
        if (mapped) {
          filledInStyle[styleKey] = mapped;
        }
      }
    }
    result[key] = filledInStyle;
  }
  return StyleSheet.create(result);
}

function styleSelector<IS: Styles>(
  obj: IS,
): (state: AppState) => StyleSheetOf<IS> {
  return createSelector(colorsSelector, (themeColors: Colors) =>
    stylesFromColors(obj, themeColors),
  );
}

function useOverlayStyles<IS: Styles>(obj: IS): StyleSheetOf<IS> {
  const navContext = React.useContext(NavContext);
  const navigationState = navContext && navContext.state;

  const theme = useSelector(
    (state: AppState) => state.globalThemeInfo.activeTheme,
  );

  const backgroundIsDark = React.useMemo(
    () => selectBackgroundIsDark(navigationState, theme),
    [navigationState, theme],
  );
  const syntheticTheme = backgroundIsDark ? 'dark' : 'light';

  return React.useMemo(() => stylesFromColors(obj, colors[syntheticTheme]), [
    obj,
    syntheticTheme,
  ]);
}

export {
  colorsPropType,
  colors,
  colorsSelector,
  styleSelector,
  useOverlayStyles,
};
<commit_msg>[native] useStyles<commit_after>// @flow

import type { GlobalTheme } from '../types/themes';
import type { AppState } from '../redux/redux-setup';

import * as React from 'react';
import { StyleSheet } from 'react-native';
import { createSelector } from 'reselect';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';

import { selectBackgroundIsDark } from '../navigation/nav-selectors';
import { NavContext } from '../navigation/navigation-context';

const light = Object.freeze({
  redButton: '#BB8888',
  greenButton: '#88BB88',
  mintButton: '#44CC99',
  redText: '#AA0000',
  greenText: 'green',
  link: '#036AFF',
  panelBackground: '#E9E9EF',
  panelBackgroundLabel: '#888888',
  panelForeground: 'white',
  panelForegroundBorder: '#CCCCCC',
  panelForegroundLabel: 'black',
  panelForegroundSecondaryLabel: '#333333',
  panelForegroundTertiaryLabel: '#888888',
  panelIosHighlightUnderlay: '#EEEEEEDD',
  panelSecondaryForeground: '#F5F5F5',
  panelSecondaryForegroundBorder: '#D1D1D6',
  modalForeground: 'white',
  modalForegroundBorder: '#CCCCCC',
  modalForegroundLabel: 'black',
  modalForegroundSecondaryLabel: '#888888',
  modalForegroundTertiaryLabel: '#AAAAAA',
  modalBackground: '#EEEEEE',
  modalBackgroundLabel: '#333333',
  modalBackgroundSecondaryLabel: '#AAAAAA',
  modalIosHighlightUnderlay: '#CCCCCCDD',
  modalSubtext: '#CCCCCC',
  modalSubtextLabel: '#555555',
  modalButton: '#BBBBBB',
  modalButtonLabel: 'black',
  modalContrastBackground: 'black',
  modalContrastForegroundLabel: 'white',
  modalContrastOpacity: 0.7,
  listForegroundLabel: 'black',
  listForegroundSecondaryLabel: '#333333',
  listForegroundTertiaryLabel: '#666666',
  listForegroundQuaternaryLabel: '#AAAAAA',
  listBackground: 'white',
  listBackgroundLabel: 'black',
  listBackgroundSecondaryLabel: '#444444',
  listBackgroundTernaryLabel: '#999999',
  listSeparator: '#EEEEEE',
  listSeparatorLabel: '#555555',
  listInputBar: '#E2E2E2',
  listInputBorder: '#AAAAAAAA',
  listInputButton: '#888888',
  listInputBackground: '#DDDDDD',
  listIosHighlightUnderlay: '#DDDDDDDD',
  listSearchBackground: '#DDDDDD',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#DDDDDDBB',
  navigationCard: '#FFFFFF',
});
export type Colors = $Exact<typeof light>;

const colorsPropType = PropTypes.objectOf(
  PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
);

const dark: Colors = Object.freeze({
  redButton: '#FF4444',
  greenButton: '#44BB44',
  mintButton: '#44CC99',
  redText: '#FF4444',
  greenText: '#44FF44',
  link: '#129AFF',
  panelBackground: '#1C1C1E',
  panelBackgroundLabel: '#C7C7CC',
  panelForeground: '#3A3A3C',
  panelForegroundBorder: '#2C2C2E',
  panelForegroundLabel: 'white',
  panelForegroundSecondaryLabel: '#CCCCCC',
  panelForegroundTertiaryLabel: '#AAAAAA',
  panelIosHighlightUnderlay: '#444444DD',
  panelSecondaryForeground: '#333333',
  panelSecondaryForegroundBorder: '#666666',
  modalForeground: '#1C1C1E',
  modalForegroundBorder: '#1C1C1E',
  modalForegroundLabel: 'white',
  modalForegroundSecondaryLabel: '#AAAAAA',
  modalForegroundTertiaryLabel: '#666666',
  modalBackground: '#2C2C2E',
  modalBackgroundLabel: '#CCCCCC',
  modalBackgroundSecondaryLabel: '#555555',
  modalIosHighlightUnderlay: '#AAAAAA88',
  modalSubtext: '#444444',
  modalSubtextLabel: '#AAAAAA',
  modalButton: '#666666',
  modalButtonLabel: 'white',
  modalContrastBackground: 'white',
  modalContrastForegroundLabel: 'black',
  modalContrastOpacity: 0.85,
  listForegroundLabel: 'white',
  listForegroundSecondaryLabel: '#CCCCCC',
  listForegroundTertiaryLabel: '#999999',
  listForegroundQuaternaryLabel: '#555555',
  listBackground: '#1C1C1E',
  listBackgroundLabel: '#C7C7CC',
  listBackgroundSecondaryLabel: '#BBBBBB',
  listBackgroundTernaryLabel: '#888888',
  listSeparator: '#3A3A3C',
  listSeparatorLabel: '#EEEEEE',
  listInputBar: '#555555',
  listInputBorder: '#333333',
  listInputButton: '#AAAAAA',
  listInputBackground: '#38383C',
  listIosHighlightUnderlay: '#BBBBBB88',
  listSearchBackground: '#555555',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#444444DD',
  navigationCard: '#2A2A2A',
});
const colors = { light, dark };

const colorsSelector: (state: AppState) => Colors = createSelector(
  (state: AppState) => state.globalThemeInfo.activeTheme,
  (theme: ?GlobalTheme) => {
    const explicitTheme = theme ? theme : 'light';
    return colors[explicitTheme];
  },
);

const magicStrings = new Set();
for (let theme in colors) {
  for (let magicString in colors[theme]) {
    magicStrings.add(magicString);
  }
}

type Styles = { [name: string]: { [field: string]: number | string } };

type ReplaceField = (input: any) => any;
type ReplaceStyleObject = <Obj: { [key: string]: number | string }>(
  Obj,
) => $ObjMap<Obj, ReplaceField>;

export type StyleSheetOf<S: Styles> = $ObjMap<S, ReplaceStyleObject>;

function stylesFromColors<IS: Styles>(
  obj: IS,
  themeColors: Colors,
): StyleSheetOf<IS> {
  const result = {};
  for (let key in obj) {
    const style = obj[key];
    const filledInStyle = { ...style };
    for (let styleKey in style) {
      const styleValue = style[styleKey];
      if (magicStrings.has(styleValue)) {
        const mapped = themeColors[styleValue];
        if (mapped) {
          filledInStyle[styleKey] = mapped;
        }
      }
    }
    result[key] = filledInStyle;
  }
  return StyleSheet.create(result);
}

function styleSelector<IS: Styles>(
  obj: IS,
): (state: AppState) => StyleSheetOf<IS> {
  return createSelector(colorsSelector, (themeColors: Colors) =>
    stylesFromColors(obj, themeColors),
  );
}

function useStyles<IS: Styles>(obj: IS): StyleSheetOf<IS> {
  const theme = useSelector(
    (state: AppState) => state.globalThemeInfo.activeTheme,
  );
  const explicitTheme = theme ? theme : 'light';
  return React.useMemo(() => stylesFromColors(obj, colors[explicitTheme]), [
    obj,
    explicitTheme,
  ]);
}

function useOverlayStyles<IS: Styles>(obj: IS): StyleSheetOf<IS> {
  const navContext = React.useContext(NavContext);
  const navigationState = navContext && navContext.state;

  const theme = useSelector(
    (state: AppState) => state.globalThemeInfo.activeTheme,
  );

  const backgroundIsDark = React.useMemo(
    () => selectBackgroundIsDark(navigationState, theme),
    [navigationState, theme],
  );
  const syntheticTheme = backgroundIsDark ? 'dark' : 'light';

  return React.useMemo(() => stylesFromColors(obj, colors[syntheticTheme]), [
    obj,
    syntheticTheme,
  ]);
}

export {
  colorsPropType,
  colors,
  colorsSelector,
  styleSelector,
  useStyles,
  useOverlayStyles,
};
<|endoftext|>


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

var React = require('react');
var Swarm = require('swarm');
var Spec = Swarm.Spec;

var TodoList = require('./model/TodoList');
var TodoItem = require('./model/TodoItem');

Swarm.env.debug = true;

var TodoAppView = require('./view/TodoAppView.jsx');

function TodoApp (ssnid, listId) {
    this.path = [];
    this.ssnid = ssnid;
    this.loading = true;

    this.initSwarm();
    this.installListeners();
    this.parseUri();
}

TodoApp.prototype.initSwarm = function () {
    //this.storage = null;
    this.storage = new Swarm.SharedWebStorage('webst',{persistent:true});
    this.wsServerUri = 'ws://'+window.location.host;
    this.host = Swarm.env.localhost = new Swarm.Host(this.ssnid,'',this.storage);
    this.host.connect(this.wsServerUri, {delay: 50});
};

TodoApp.prototype.parseUri = function () {
    var path = window.location.pathname + window.location.hash;
    var rootListId = null;
    var itemIds = [];
    var m;
    Spec.reQTokExt.lastIndex = 0;
    while (m = Spec.reQTokExt.exec(path)) {
        var id = m && m[2];
        if (!rootListId) {
            rootListId = id;
        } else {
            itemIds.push(id);
        }
    }
    if (!rootListId) {
        var list = new TodoList();
        var item = new TodoItem();
        list.addObject(item);
        this.forward(list._id, [item._id]);
    } else {
        this.forward(rootListId, itemIds);
    }
};

TodoApp.prototype.installListeners = function () {
    var self = this;
    document.addEventListener('keydown', function (ev) {
        if (self.loading) return;

        switch (ev.keyCode) {
            case 9:  self.forward();break; // tab
            case 27: self.back(1);  break; // esc
            case 40: self.down();   break; // down arrow
            case 38: self.up();     break; // up arrow
            case 45: self.toggle(); break; // insert
            case 107:                      // numpad plus
            case 13: self.create(); break; // enter
            case 109:self.delete(); break; // numpad minus
            default: return true;
        }
        ev.preventDefault();
        return false;
    });
};

TodoApp.prototype.getItem = function (itemId) {
    if (!itemId) {
        var state = this.path[this.path.length-1];
        itemId = state.itemId;
    }
    return this.host.get('/TodoItem#'+itemId);
};

TodoApp.prototype.getList = function (listId) {
    if (!listId) {
        var state = this.path[this.path.length-1];
        listId = state.listId;
    }
    return this.host.get('/TodoList#'+listId);
};

TodoApp.prototype.buildLocationPath = function () {
    var res = [];
    for (var i = 0, l = this.path.length; i < l; i++) {
        var state = this.path[i];
        if (i === 0) {
            res.push('/' + state.listId);
        }
        res.push('/' + state.itemId);
    }
    return res.join('');
};

TodoApp.prototype.refresh = function () {
    // rerender DOM
    React.renderComponent(
        TodoAppView ({
            key: 'root',
            app: this,
            UIState: this.path
        }),
        document.getElementById('todoapp')
    );
    // recover focus
    var item = this.getItem();
    var edit = document.getElementById(item._id);
    if (edit) {
        edit.focus();
        // TODO scroll into view
    }
    // set URI
    var locationPath = this.buildLocationPath();
    window.history.replaceState({},"",window.location.origin + locationPath);
};

// Suddenly jump to some entry in some list.
// Invoked by onClick and onHashChange
TodoApp.prototype.go = function (listId, itemId) {
    if (this.loading) return;

    // find in history
    var path = this.path;
    var backSteps = 0;
    while (backSteps < path.length && path[path.length-backSteps-1].listId !== listId) {
        backSteps++;
    }
    this.back(backSteps);
    this.selectItem(itemId);
};

TodoApp.prototype.back = function (steps) {
    for (var i = 0; i < steps && i < this.path.length; i++) {
        this.path.pop();
    }
    this.refresh();
};

TodoApp.prototype.forward = function (listId, itemIds) {
    var self = this;
    var fwdList;
    if (!listId) {
        var item = this.getItem();
        listId = item.childList;
    }
    if (!listId) {
        fwdList = new TodoList();
        listId = fwdList._id;
        item.set({childList: listId});
    } else {
        fwdList = this.host.get('/TodoList#'+listId); // TODO fn+id sig
    }
    var oldPath = window.location.pathname + window.location.hash;
    this.loading = true;
    // we may need to fetch the data from the server so we use a callback, yes
    fwdList.onObjectStateReady(function () {
        if (window.location.pathname + window.location.hash != oldPath) {
            console.log('route changed');
            self.path = [];
            self.parseUri();
            return; // the user has likely navigated away while the data was loading
        }
        if (!fwdList.length()) {
            fwdList.addObject(new TodoItem({text:'just do it'}));
        }
        if (!itemIds) {
            itemIds = [fwdList.objectAt(0)._id];
        } else if ('string' === typeof itemIds) {
            itemIds = [itemIds];
        }

        var itemId = itemIds.shift();
        self.path.push({
            listId: listId,
            itemId: itemId
        });
        if (itemIds.length) {
            item = fwdList.getObject(itemId);
            self.forward(item.childList, itemIds);
            return;
        }
        self.loading = false;
        self.refresh();
        // TODO max delay
    });
};

TodoApp.prototype.selectItem = function (itemId) {
    if (this.loading) return;

    var list = this.getList();
    if (itemId.constructor===Number) {
        var i = itemId;
        if (i<0) { i=0; }
        if (i>=list.length()) { i=list.length()-1; } // TODO .length
        itemId = i>=0 ? list.objectAt(i)._id : '';
    } if (itemId._id) {
        itemId = itemId._id;
    }
    var state = this.path[this.path.length-1];
    state.itemId = itemId;
    this.refresh();
};

TodoApp.prototype.up = function () {
    if (this.loading) return;

    var list = this.getList();
    var item = this.getItem();
    var i = list.indexOf(item);
    if (i>0) {
        this.selectItem(i-1);
    }
};

TodoApp.prototype.down = function () {
    if (this.loading) return;

    var list = this.getList();
    var item = this.getItem();
    var i = list.indexOf(item);
    if (i+1<list.length()) {
        this.selectItem(i+1);
    }
};

TodoApp.prototype.toggle = function () {
    if (this.loading) return;

    var item = this.getItem();
    if (item) {
        item.set({completed:!item.completed});
    }
};

TodoApp.prototype.create = function () {
    if (this.loading) return;

    var item = this.getItem();
    var list = this.getList();
    if (list && item) {
        var newItem = new TodoItem({text:''});
        list.insertAfter(newItem, item);
        this.selectItem(newItem);
    }
};

TodoApp.prototype.delete = function (listId, itemId) {
    if (this.loading) return;

    var list = this.getList(listId);
    var item = this.getItem(itemId);
    var pos = list.indexOf(item);
    if (list && item && pos!==-1) {
        list.remove(item);
        this.selectItem(pos);
    }
};

module.exports = window.TodoApp = TodoApp;
<commit_msg>prevent navigation back from root list;<commit_after>/**
 * Copyright 2013-2014 Victor Grishchenko
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @jsx React.DOM
 */

var React = require('react');
var Swarm = require('swarm');
var Spec = Swarm.Spec;

var TodoList = require('./model/TodoList');
var TodoItem = require('./model/TodoItem');

Swarm.env.debug = true;

var TodoAppView = require('./view/TodoAppView.jsx');

function TodoApp (ssnid, listId) {
    this.path = [];
    this.ssnid = ssnid;
    this.loading = true;

    this.initSwarm();
    this.installListeners();
    this.parseUri();
}

TodoApp.prototype.initSwarm = function () {
    //this.storage = null;
    this.storage = new Swarm.SharedWebStorage('webst',{persistent:true});
    this.wsServerUri = 'ws://'+window.location.host;
    this.host = Swarm.env.localhost = new Swarm.Host(this.ssnid,'',this.storage);
    this.host.connect(this.wsServerUri, {delay: 50});
};

TodoApp.prototype.parseUri = function () {
    var path = window.location.pathname + window.location.hash;
    var rootListId = null;
    var itemIds = [];
    var m;
    Spec.reQTokExt.lastIndex = 0;
    while (m = Spec.reQTokExt.exec(path)) {
        var id = m && m[2];
        if (!rootListId) {
            rootListId = id;
        } else {
            itemIds.push(id);
        }
    }
    if (!rootListId) {
        var list = new TodoList();
        var item = new TodoItem();
        list.addObject(item);
        this.forward(list._id, [item._id]);
    } else {
        this.forward(rootListId, itemIds);
    }
};

TodoApp.prototype.installListeners = function () {
    var self = this;
    document.addEventListener('keydown', function (ev) {
        if (self.loading) return;

        switch (ev.keyCode) {
            case 9:  self.forward();break; // tab
            case 27: self.back(1);  break; // esc
            case 40: self.down();   break; // down arrow
            case 38: self.up();     break; // up arrow
            case 45: self.toggle(); break; // insert
            case 107:                      // numpad plus
            case 13: self.create(); break; // enter
            case 109:self.delete(); break; // numpad minus
            default: return true;
        }
        ev.preventDefault();
        return false;
    });
};

TodoApp.prototype.getItem = function (itemId) {
    if (!itemId) {
        var state = this.path[this.path.length-1];
        itemId = state.itemId;
    }
    return this.host.get('/TodoItem#'+itemId);
};

TodoApp.prototype.getList = function (listId) {
    if (!listId) {
        var state = this.path[this.path.length-1];
        listId = state.listId;
    }
    return this.host.get('/TodoList#'+listId);
};

TodoApp.prototype.buildLocationPath = function () {
    var res = [];
    for (var i = 0, l = this.path.length; i < l; i++) {
        var state = this.path[i];
        if (i === 0) {
            res.push('/' + state.listId);
        }
        res.push('/' + state.itemId);
    }
    return res.join('');
};

TodoApp.prototype.refresh = function () {
    // rerender DOM
    React.renderComponent(
        TodoAppView ({
            key: 'root',
            app: this,
            UIState: this.path
        }),
        document.getElementById('todoapp')
    );
    // recover focus
    var item = this.getItem();
    var edit = document.getElementById(item._id);
    if (edit) {
        edit.focus();
        // TODO scroll into view
    }
    // set URI
    var locationPath = this.buildLocationPath();
    window.history.replaceState({},"",window.location.origin + locationPath);
};

// Suddenly jump to some entry in some list.
// Invoked by onClick and onHashChange
TodoApp.prototype.go = function (listId, itemId) {
    if (this.loading) return;

    // find in history
    var path = this.path;
    var backSteps = 0;
    while (backSteps < path.length && path[path.length-backSteps-1].listId !== listId) {
        backSteps++;
    }
    this.back(backSteps);
    this.selectItem(itemId);
};

TodoApp.prototype.back = function (steps) {
    if (this.path.length <= 1) return;

    for (var i = 0; i < steps && i < this.path.length; i++) {
        this.path.pop();
    }
    this.refresh();
};

TodoApp.prototype.forward = function (listId, itemIds) {
    var self = this;
    var fwdList;
    if (!listId) {
        var item = this.getItem();
        listId = item.childList;
    }
    if (!listId) {
        fwdList = new TodoList();
        listId = fwdList._id;
        item.set({childList: listId});
    } else {
        fwdList = this.host.get('/TodoList#'+listId); // TODO fn+id sig
    }
    var oldPath = window.location.pathname + window.location.hash;
    this.loading = true;
    // we may need to fetch the data from the server so we use a callback, yes
    fwdList.onObjectStateReady(function () {
        if (window.location.pathname + window.location.hash != oldPath) {
            console.log('route changed');
            self.path = [];
            self.parseUri();
            return; // the user has likely navigated away while the data was loading
        }
        if (!fwdList.length()) {
            fwdList.addObject(new TodoItem({text:'just do it'}));
        }
        if (!itemIds) {
            itemIds = [fwdList.objectAt(0)._id];
        } else if ('string' === typeof itemIds) {
            itemIds = [itemIds];
        }

        var itemId = itemIds.shift();
        self.path.push({
            listId: listId,
            itemId: itemId
        });
        if (itemIds.length) {
            item = fwdList.getObject(itemId);
            self.forward(item.childList, itemIds);
            return;
        }
        self.loading = false;
        self.refresh();
        // TODO max delay
    });
};

TodoApp.prototype.selectItem = function (itemId) {
    if (this.loading) return;

    var list = this.getList();
    if (itemId.constructor===Number) {
        var i = itemId;
        if (i<0) { i=0; }
        if (i>=list.length()) { i=list.length()-1; } // TODO .length
        itemId = i>=0 ? list.objectAt(i)._id : '';
    } if (itemId._id) {
        itemId = itemId._id;
    }
    var state = this.path[this.path.length-1];
    state.itemId = itemId;
    this.refresh();
};

TodoApp.prototype.up = function () {
    if (this.loading) return;

    var list = this.getList();
    var item = this.getItem();
    var i = list.indexOf(item);
    if (i>0) {
        this.selectItem(i-1);
    }
};

TodoApp.prototype.down = function () {
    if (this.loading) return;

    var list = this.getList();
    var item = this.getItem();
    var i = list.indexOf(item);
    if (i+1<list.length()) {
        this.selectItem(i+1);
    }
};

TodoApp.prototype.toggle = function () {
    if (this.loading) return;

    var item = this.getItem();
    if (item) {
        item.set({completed:!item.completed});
    }
};

TodoApp.prototype.create = function () {
    if (this.loading) return;

    var item = this.getItem();
    var list = this.getList();
    if (list && item) {
        var newItem = new TodoItem({text:''});
        list.insertAfter(newItem, item);
        this.selectItem(newItem);
    }
};

TodoApp.prototype.delete = function (listId, itemId) {
    if (this.loading) return;

    var list = this.getList(listId);
    var item = this.getItem(itemId);
    var pos = list.indexOf(item);
    if (list && item && pos!==-1) {
        list.remove(item);
        this.selectItem(pos);
    }
};

module.exports = window.TodoApp = TodoApp;
<|endoftext|>


<commit_before>module.exports = {
  verbose: false,
  plugins: {
    local: {
      disabled: true,
      browsers: ['firefox']
    },
    sauce: {
      disabled: false,
      browsers: [
        {
          "browserName": "internet explorer",
          "platform":    "Windows 8.1",
          "version":     "11"
        },
        {
          "browserName": "internet explorer",
          "platform":    "Windows 7",
          "version":     "11"
        },
        {
          "browserName": "internet explorer",
          "platform":    "Windows 7",
          "version":     "10"
        },

        {
          "browserName": "chrome",
          "platform":    "Windows 8.1",
          "version":     "beta"
        },
        {
          "browserName": "chrome",
          "platform":    "Windows 8.1",
          "version":     ""
        },
        {
          "browserName": "chrome",
          "platform":    "OS X 10.10",
          "version":     ""
        },
        {
          "browserName": "chrome",
          "platform":    "Linux",
          "version":     ""
        },

        {
          "browserName": "firefox",
          "platform":    "Windows 8.1",
          "version":     ""
        },
        {
          "browserName": "firefox",
          "platform":    "OS X 10.10",
          "version":     ""
        },
        {
          "browserName": "firefox",
          "platform":    "Linux",
          "version":     ""
        },

        {
          "browserName": "safari",
          "platform":    "OS X 10.9",
          "version":     "7"
        }
      ]
    }
  },
};
<commit_msg>Not supporting IE 10 at this point;<commit_after>module.exports = {
  verbose: false,
  plugins: {
    local: {
      disabled: true,
      browsers: ['firefox']
    },
    sauce: {
      disabled: false,
      browsers: [
        {
          "browserName": "internet explorer",
          "platform":    "Windows 8.1",
          "version":     "11"
        },
        {
          "browserName": "internet explorer",
          "platform":    "Windows 7",
          "version":     "11"
        },

        {
          "browserName": "chrome",
          "platform":    "Windows 8.1",
          "version":     "beta"
        },
        {
          "browserName": "chrome",
          "platform":    "Windows 8.1",
          "version":     ""
        },
        {
          "browserName": "chrome",
          "platform":    "OS X 10.10",
          "version":     ""
        },
        {
          "browserName": "chrome",
          "platform":    "Linux",
          "version":     ""
        },

        {
          "browserName": "firefox",
          "platform":    "Windows 8.1",
          "version":     ""
        },
        {
          "browserName": "firefox",
          "platform":    "OS X 10.10",
          "version":     ""
        },
        {
          "browserName": "firefox",
          "platform":    "Linux",
          "version":     ""
        },

        {
          "browserName": "safari",
          "platform":    "OS X 10.9",
          "version":     "7"
        }
      ]
    }
  },
};
<|endoftext|>


<commit_before>/**
*
*	POLYMER: wct.conf.js
*
*
*	DESCRIPTION:
*		- Web component tester config file for running tests.
*
*
*	NOTES:
*		[1]
*
*
*	TODO:
*		[1]
*
*
*	LICENSE:
*		MIT
*
*	Copyright (c) 2014. Athan Reines.
*
*
*	AUTHOR:
*		Athan Reines. kgryte@gmail.com. 2014.
*
*/

'use strict';

// MODULES //

var path = require( 'path' ),
	browsers = require( './sauce-browsers.json' );

// CONFIG //

var config,
	root,
	dir;

// Serve from the directory above the current directory to allow loading of sibling dependencies:
root = path.resolve( __dirname, '../' );

// Set the location of the test suites:
dir = path.basename( __dirname );
dir = path.join( dir, 'test' );

// Create the web component test configuration...
config = {

	// Root directory from which files should be served:
	'root': root,

	// Location of test suites:
	'suites': [
		dir
	],

	// Browsers on which to test:
	'browsers': [
		'chrome'
	],

	// Whether the local or remote browsers should be targeted:
	'remote': false,

	// Whether the browser should remain open after running tests:
	'persistent': false,

	// Duration before which an idle test times out:
	'testTimeout': 90000,

	// Display test results in expanded form:
	'expanded': false,

	// Output verbosity:
	'verbose': false,

	// Output stream:
	'output': process.stdout,

	// Whether the output stream should be treated as TTY:
	'ttyOutput': undefined,

	// Additional scripts which should be included in the generated tests (see Selenium: https://code.google.com/p/selenium/wiki/DesiredCapabilities and Sauce: https://docs.saucelabs.com/reference/test-configuration/):
	'extraScripts': [],

	// Additional browser options for Selenium or Sauce:
	'browserOptions': {},

	// Sauce labs configuration:
	'sauce': {
		'username': undefined,
		'accessKey': undefined,

		// Tunnel id to reuse for tests:
		'tunnelId': undefined,

		// Advanced tunnel options (https://github.com/bermi/sauce-connect-launcher#advanced-usage):
		'tunnelOptions': {}
	},

	// Test plugins:
	'plugins': {
		'web-component-tester-istanbul': {
			'dir': './reports/coverage',
			'reporters': [
				'text-summary',
				'lcov'
			],
			'include': [],
			'exclude': []
		}
	}
}; // end CONFIG

// EXPORTS //

module.exports = config;
<commit_msg>[UPDATE] firefox.<commit_after>/**
*
*	POLYMER: wct.conf.js
*
*
*	DESCRIPTION:
*		- Web component tester config file for running tests.
*
*
*	NOTES:
*		[1]
*
*
*	TODO:
*		[1]
*
*
*	LICENSE:
*		MIT
*
*	Copyright (c) 2014. Athan Reines.
*
*
*	AUTHOR:
*		Athan Reines. kgryte@gmail.com. 2014.
*
*/

'use strict';

// MODULES //

var path = require( 'path' ),
	browsers = require( './sauce-browsers.json' );

// CONFIG //

var config,
	root,
	dir;

// Serve from the directory above the current directory to allow loading of sibling dependencies:
root = path.resolve( __dirname, '../' );

// Set the location of the test suites:
dir = path.basename( __dirname );
dir = path.join( dir, 'test' );

// Create the web component test configuration...
config = {

	// Root directory from which files should be served:
	'root': root,

	// Location of test suites:
	'suites': [
		dir
	],

	// Browsers on which to test:
	'browsers': [
		'firefox'
	],

	// Whether the local or remote browsers should be targeted:
	'remote': false,

	// Whether the browser should remain open after running tests:
	'persistent': false,

	// Duration before which an idle test times out:
	'testTimeout': 90000,

	// Display test results in expanded form:
	'expanded': false,

	// Output verbosity:
	'verbose': false,

	// Output stream:
	'output': process.stdout,

	// Whether the output stream should be treated as TTY:
	'ttyOutput': undefined,

	// Additional scripts which should be included in the generated tests (see Selenium: https://code.google.com/p/selenium/wiki/DesiredCapabilities and Sauce: https://docs.saucelabs.com/reference/test-configuration/):
	'extraScripts': [],

	// Additional browser options for Selenium or Sauce:
	'browserOptions': {},

	// Sauce labs configuration:
	'sauce': {
		'username': undefined,
		'accessKey': undefined,

		// Tunnel id to reuse for tests:
		'tunnelId': undefined,

		// Advanced tunnel options (https://github.com/bermi/sauce-connect-launcher#advanced-usage):
		'tunnelOptions': {}
	},

	// Test plugins:
	'plugins': {
		'web-component-tester-istanbul': {
			'dir': './reports/coverage',
			'reporters': [
				'text-summary',
				'lcov'
			],
			'include': [],
			'exclude': []
		}
	}
}; // end CONFIG

// EXPORTS //

module.exports = config;
<|endoftext|>


<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

(() => {
  "use strict";

  // States.
  const BUFFERING = "buffering";
  const ENDED = "ended";
  const ERRORED = "errored";
  const PAUSED = "paused";
  const PLAYING = "playing";

  // State transitions.
  const TRANSITIONS = {
    buffer: {
      paused: BUFFERING
    },
    end: {
      playing: ENDED,
      paused: ENDED
    },
    error: {
      buffering: ERRORED,
      playing: ERRORED,
      paused: ERRORED
    },
    pause: {
      buffering: PAUSED,
      playing: PAUSED
    },
    play: {
      buffering: PLAYING,
      ended: PLAYING,
      paused: PLAYING
    }
  };

  function generateMarkup(isAudioOnly) {
    return `

        <button id="play-pause-button"></button>
        <input id="progress" type="range" value="0" min="0" max="100" step="1"></input>

          #1
           / #2

        <button id="volume-switch"></button>
        <input id="volume-level" type="range" value="100" min="0" max="100" step="1"></input>
        ${isAudioOnly ? "" : '<button id="fullscreen-switch" class="fullscreen"></button>'}

    `;
  }

  function camelCase(str) {
    const rdashes = /-(.)/g;
    return str.replace(rdashes, (str, p1) => {
      return p1.toUpperCase();
    });
  }

  function formatTime(time, showHours = false) {
    // Format the duration as "h:mm:ss" or "m:ss"
    time = Math.round(time / 1000);

    const hours = Math.floor(time / 3600);
    const mins = Math.floor((time % 3600) / 60);
    const secs = Math.floor(time % 60);

    const formattedHours =
      hours || showHours ? `${hours.toString().padStart(2, "0")}:` : "";

    return `${formattedHours}${mins
      .toString()
      .padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
  }

  class MediaControls {
    constructor() {
      this.nonce = Date.now();
      // Get the instance of the shadow root where these controls live.
      this.controls = document.servoGetMediaControls("@@@id@@@");
      // Get the instance of the host of these controls.
      this.media = this.controls.host;

      this.mutationObserver = new MutationObserver(() => {
        // We can only get here if the `controls` attribute is removed.
        this.cleanup();
      });
      this.mutationObserver.observe(this.media, {
        attributeFilter: ["controls"]
      });

      this.isAudioOnly = this.media.localName == "audio";

      // Create root element and load markup.
      this.root = document.createElement("div");
      this.root.classList.add("root");
      this.root.innerHTML = generateMarkup(this.isAudioOnly);
      this.controls.appendChild(this.root);

      const elementNames = [
        "duration",
        "play-pause-button",
        "position-duration-box",
        "position-text",
        "progress",
        "volume-switch",
        "volume-level"
      ];

      if (!this.isAudioOnly) {
        elementNames.push("fullscreen-switch");
      }

      // Import elements.
      this.elements = {};
      elementNames.forEach(id => {
        this.elements[camelCase(id)] = this.controls.getElementById(id);
      });

      // Init position duration box.
      const positionTextNode = this.elements.positionText;
      const durationSpan = this.elements.duration;
      const durationFormat = durationSpan.textContent;
      const positionFormat = positionTextNode.textContent;

      durationSpan.classList.add("duration");
      durationSpan.setAttribute("role", "none");

      Object.defineProperties(this.elements.positionDurationBox, {
        durationSpan: {
          value: durationSpan
        },
        position: {
          get: () => {
            return positionTextNode.textContent;
          },
          set: v => {
            positionTextNode.textContent = positionFormat.replace("#1", v);
          }
        },
        duration: {
          get: () => {
            return durationSpan.textContent;
          },
          set: v => {
            durationSpan.textContent = v ? durationFormat.replace("#2", v) : "";
          }
        },
        show: {
          value: (currentTime, duration) => {
            const self = this.elements.positionDurationBox;
            if (self.position != currentTime) {
              self.position = currentTime;
            }
            if (self.duration != duration) {
              self.duration = duration;
            }
            self.classList.remove("hidden");
          }
        }
      });

      // Add event listeners.
      this.mediaEvents = [
        "play",
        "pause",
        "ended",
        "volumechange",
        "loadeddata",
        "loadstart",
        "timeupdate",
        "progress",
        "playing",
        "waiting",
        "canplay",
        "canplaythrough",
        "seeking",
        "seeked",
        "emptied",
        "loadedmetadata",
        "error",
        "suspend"
      ];
      this.mediaEvents.forEach(event => {
        this.media.addEventListener(event, this);
      });

      this.controlEvents = [
        { el: this.elements.playPauseButton, type: "click" },
        { el: this.elements.volumeSwitch, type: "click" },
        { el: this.elements.volumeLevel, type: "input" }
      ];

      if (!this.isAudioOnly) {
        this.controlEvents.push({ el: this.elements.fullscreenSwitch, type: "click" });
      }

      this.controlEvents.forEach(({ el, type }) => {
        el.addEventListener(type, this);
      });

      // Create state transitions.
      //
      // It exposes one method per transition. i.e. this.pause(), this.play(), etc.
      // For each transition, we check that the transition is possible and call
      // the `onStateChange` handler.
      for (let name in TRANSITIONS) {
        if (!TRANSITIONS.hasOwnProperty(name)) {
          continue;
        }
        this[name] = () => {
          const from = this.state;

          // Checks if the transition is valid in the current state.
          if (!TRANSITIONS[name][from]) {
            const error = `Transition "${name}" invalid for the current state "${from}"`;
            console.error(error);
            throw new Error(error);
          }

          const to = TRANSITIONS[name][from];

          if (from == to) {
            return;
          }

          // Transition to the next state.
          this.state = to;
          this.onStateChange(from);
        };
      }

      // Set initial state.
      this.state = this.media.paused ? PAUSED : PLAYING;
      this.onStateChange(null);
    }

    cleanup() {
      this.mutationObserver.disconnect();
      this.mediaEvents.forEach(event => {
        this.media.removeEventListener(event, this);
      });
      this.controlEvents.forEach(({ el, type }) => {
        el.removeEventListener(type, this);
      });
    }

    // State change handler
    onStateChange(from) {
      this.render(from);
    }

    render(from = this.state) {
      if (!this.isAudioOnly) {
        // XXX This should ideally use clientHeight/clientWidth,
        //     but for some reason I couldn't figure out yet,
        //     using it breaks layout.
        this.root.style.height = this.media.videoHeight;
        this.root.style.width = this.media.videoWidth;
      }

      // Error
      if (this.state == ERRORED) {
        //XXX render errored state
        return;
      }

      if (this.state != from) {
        // Play/Pause button.
        const playPauseButton = this.elements.playPauseButton;
        playPauseButton.classList.remove(from);
        playPauseButton.classList.add(this.state);
      }

      // Progress.
      const positionPercent =
        (this.media.currentTime / this.media.duration) * 100;
      if (Number.isFinite(positionPercent)) {
        this.elements.progress.value = positionPercent;
      } else {
        this.elements.progress.value = 0;
      }

      // Current time and duration.
      let currentTime = formatTime(0);
      let duration = formatTime(0);
      if (!isNaN(this.media.currentTime) && !isNaN(this.media.duration)) {
        currentTime = formatTime(Math.round(this.media.currentTime * 1000));
        duration = formatTime(Math.round(this.media.duration * 1000));
      }
      this.elements.positionDurationBox.show(currentTime, duration);

      // Volume.
      this.elements.volumeSwitch.className =
        this.media.muted || !this.media.volume ? "muted" : "volumeup";
      const volumeLevelValue = this.media.muted
        ? 0
        : Math.round(this.media.volume * 100);
      if (this.elements.volumeLevel.value != volumeLevelValue) {
        this.elements.volumeLevel.value = volumeLevelValue;
      }
    }

    handleEvent(event) {
      if (!event.isTrusted) {
        console.warn(`Drop untrusted event ${event.type}`);
        return;
      }

      if (this.mediaEvents.includes(event.type)) {
        this.onMediaEvent(event);
      } else {
        this.onControlEvent(event);
      }
    }

    onControlEvent(event) {
      switch (event.type) {
        case "click":
          switch (event.currentTarget) {
            case this.elements.playPauseButton:
              this.playOrPause();
              break;
            case this.elements.volumeSwitch:
              this.toggleMuted();
              break;
            case this.elements.fullscreenSwitch:
                this.toggleFullscreen();
                break;
          }
          break;
        case "input":
          switch (event.currentTarget) {
            case this.elements.volumeLevel:
              this.changeVolume();
              break;
          }
          break;
        default:
          throw new Error(`Unknown event ${event.type}`);
      }
    }

    // HTMLMediaElement event handler
    onMediaEvent(event) {
      switch (event.type) {
        case "ended":
          this.end();
          break;
        case "play":
        case "pause":
          // Transition to PLAYING or PAUSED state.
          this[event.type]();
          break;
        case "volumechange":
        case "timeupdate":
        case "resize":
          this.render();
          break;
        case "loadedmetadata":
          break;
      }
    }

    /* Media actions */

    playOrPause() {
      switch (this.state) {
        case PLAYING:
          this.media.pause();
          break;
        case BUFFERING:
        case ENDED:
        case PAUSED:
          this.media.play();
          break;
        default:
          throw new Error(`Invalid state ${this.state}`);
      }
    }

    toggleMuted() {
      this.media.muted = !this.media.muted;
    }

    toggleFullscreen() {
        this.media.requestFullscreen();
    }

    changeVolume() {
      const volume = parseInt(this.elements.volumeLevel.value);
      if (!isNaN(volume)) {
        this.media.volume = volume / 100;
      }
    }
  }

  new MediaControls();
})();

<commit_msg>Add fullscreen exit to toggle callback<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

(() => {
  "use strict";

  // States.
  const BUFFERING = "buffering";
  const ENDED = "ended";
  const ERRORED = "errored";
  const PAUSED = "paused";
  const PLAYING = "playing";

  // State transitions.
  const TRANSITIONS = {
    buffer: {
      paused: BUFFERING
    },
    end: {
      playing: ENDED,
      paused: ENDED
    },
    error: {
      buffering: ERRORED,
      playing: ERRORED,
      paused: ERRORED
    },
    pause: {
      buffering: PAUSED,
      playing: PAUSED
    },
    play: {
      buffering: PLAYING,
      ended: PLAYING,
      paused: PLAYING
    }
  };

  function generateMarkup(isAudioOnly) {
    return `

        <button id="play-pause-button"></button>
        <input id="progress" type="range" value="0" min="0" max="100" step="1"></input>

          #1
           / #2

        <button id="volume-switch"></button>
        <input id="volume-level" type="range" value="100" min="0" max="100" step="1"></input>
        ${isAudioOnly ? "" : '<button id="fullscreen-switch" class="fullscreen"></button>'}

    `;
  }

  function camelCase(str) {
    const rdashes = /-(.)/g;
    return str.replace(rdashes, (str, p1) => {
      return p1.toUpperCase();
    });
  }

  function formatTime(time, showHours = false) {
    // Format the duration as "h:mm:ss" or "m:ss"
    time = Math.round(time / 1000);

    const hours = Math.floor(time / 3600);
    const mins = Math.floor((time % 3600) / 60);
    const secs = Math.floor(time % 60);

    const formattedHours =
      hours || showHours ? `${hours.toString().padStart(2, "0")}:` : "";

    return `${formattedHours}${mins
      .toString()
      .padStart(2, "0")}:${secs.toString().padStart(2, "0")}`;
  }

  class MediaControls {
    constructor() {
      this.nonce = Date.now();
      // Get the instance of the shadow root where these controls live.
      this.controls = document.servoGetMediaControls("@@@id@@@");
      // Get the instance of the host of these controls.
      this.media = this.controls.host;

      this.mutationObserver = new MutationObserver(() => {
        // We can only get here if the `controls` attribute is removed.
        this.cleanup();
      });
      this.mutationObserver.observe(this.media, {
        attributeFilter: ["controls"]
      });

      this.isAudioOnly = this.media.localName == "audio";

      // Create root element and load markup.
      this.root = document.createElement("div");
      this.root.classList.add("root");
      this.root.innerHTML = generateMarkup(this.isAudioOnly);
      this.controls.appendChild(this.root);

      const elementNames = [
        "duration",
        "play-pause-button",
        "position-duration-box",
        "position-text",
        "progress",
        "volume-switch",
        "volume-level"
      ];

      if (!this.isAudioOnly) {
        elementNames.push("fullscreen-switch");
      }

      // Import elements.
      this.elements = {};
      elementNames.forEach(id => {
        this.elements[camelCase(id)] = this.controls.getElementById(id);
      });

      // Init position duration box.
      const positionTextNode = this.elements.positionText;
      const durationSpan = this.elements.duration;
      const durationFormat = durationSpan.textContent;
      const positionFormat = positionTextNode.textContent;

      durationSpan.classList.add("duration");
      durationSpan.setAttribute("role", "none");

      Object.defineProperties(this.elements.positionDurationBox, {
        durationSpan: {
          value: durationSpan
        },
        position: {
          get: () => {
            return positionTextNode.textContent;
          },
          set: v => {
            positionTextNode.textContent = positionFormat.replace("#1", v);
          }
        },
        duration: {
          get: () => {
            return durationSpan.textContent;
          },
          set: v => {
            durationSpan.textContent = v ? durationFormat.replace("#2", v) : "";
          }
        },
        show: {
          value: (currentTime, duration) => {
            const self = this.elements.positionDurationBox;
            if (self.position != currentTime) {
              self.position = currentTime;
            }
            if (self.duration != duration) {
              self.duration = duration;
            }
            self.classList.remove("hidden");
          }
        }
      });

      // Add event listeners.
      this.mediaEvents = [
        "play",
        "pause",
        "ended",
        "volumechange",
        "loadeddata",
        "loadstart",
        "timeupdate",
        "progress",
        "playing",
        "waiting",
        "canplay",
        "canplaythrough",
        "seeking",
        "seeked",
        "emptied",
        "loadedmetadata",
        "error",
        "suspend"
      ];
      this.mediaEvents.forEach(event => {
        this.media.addEventListener(event, this);
      });

      this.controlEvents = [
        { el: this.elements.playPauseButton, type: "click" },
        { el: this.elements.volumeSwitch, type: "click" },
        { el: this.elements.volumeLevel, type: "input" }
      ];

      if (!this.isAudioOnly) {
        this.controlEvents.push({ el: this.elements.fullscreenSwitch, type: "click" });
      }

      this.controlEvents.forEach(({ el, type }) => {
        el.addEventListener(type, this);
      });

      // Create state transitions.
      //
      // It exposes one method per transition. i.e. this.pause(), this.play(), etc.
      // For each transition, we check that the transition is possible and call
      // the `onStateChange` handler.
      for (let name in TRANSITIONS) {
        if (!TRANSITIONS.hasOwnProperty(name)) {
          continue;
        }
        this[name] = () => {
          const from = this.state;

          // Checks if the transition is valid in the current state.
          if (!TRANSITIONS[name][from]) {
            const error = `Transition "${name}" invalid for the current state "${from}"`;
            console.error(error);
            throw new Error(error);
          }

          const to = TRANSITIONS[name][from];

          if (from == to) {
            return;
          }

          // Transition to the next state.
          this.state = to;
          this.onStateChange(from);
        };
      }

      // Set initial state.
      this.state = this.media.paused ? PAUSED : PLAYING;
      this.onStateChange(null);
    }

    cleanup() {
      this.mutationObserver.disconnect();
      this.mediaEvents.forEach(event => {
        this.media.removeEventListener(event, this);
      });
      this.controlEvents.forEach(({ el, type }) => {
        el.removeEventListener(type, this);
      });
    }

    // State change handler
    onStateChange(from) {
      this.render(from);
    }

    render(from = this.state) {
      if (!this.isAudioOnly) {
        // XXX This should ideally use clientHeight/clientWidth,
        //     but for some reason I couldn't figure out yet,
        //     using it breaks layout.
        this.root.style.height = this.media.videoHeight;
        this.root.style.width = this.media.videoWidth;
      }

      // Error
      if (this.state == ERRORED) {
        //XXX render errored state
        return;
      }

      if (this.state != from) {
        // Play/Pause button.
        const playPauseButton = this.elements.playPauseButton;
        playPauseButton.classList.remove(from);
        playPauseButton.classList.add(this.state);
      }

      // Progress.
      const positionPercent =
        (this.media.currentTime / this.media.duration) * 100;
      if (Number.isFinite(positionPercent)) {
        this.elements.progress.value = positionPercent;
      } else {
        this.elements.progress.value = 0;
      }

      // Current time and duration.
      let currentTime = formatTime(0);
      let duration = formatTime(0);
      if (!isNaN(this.media.currentTime) && !isNaN(this.media.duration)) {
        currentTime = formatTime(Math.round(this.media.currentTime * 1000));
        duration = formatTime(Math.round(this.media.duration * 1000));
      }
      this.elements.positionDurationBox.show(currentTime, duration);

      // Volume.
      this.elements.volumeSwitch.className =
        this.media.muted || !this.media.volume ? "muted" : "volumeup";
      const volumeLevelValue = this.media.muted
        ? 0
        : Math.round(this.media.volume * 100);
      if (this.elements.volumeLevel.value != volumeLevelValue) {
        this.elements.volumeLevel.value = volumeLevelValue;
      }
    }

    handleEvent(event) {
      if (!event.isTrusted) {
        console.warn(`Drop untrusted event ${event.type}`);
        return;
      }

      if (this.mediaEvents.includes(event.type)) {
        this.onMediaEvent(event);
      } else {
        this.onControlEvent(event);
      }
    }

    onControlEvent(event) {
      switch (event.type) {
        case "click":
          switch (event.currentTarget) {
            case this.elements.playPauseButton:
              this.playOrPause();
              break;
            case this.elements.volumeSwitch:
              this.toggleMuted();
              break;
            case this.elements.fullscreenSwitch:
                this.toggleFullscreen();
                break;
          }
          break;
        case "input":
          switch (event.currentTarget) {
            case this.elements.volumeLevel:
              this.changeVolume();
              break;
          }
          break;
        default:
          throw new Error(`Unknown event ${event.type}`);
      }
    }

    // HTMLMediaElement event handler
    onMediaEvent(event) {
      switch (event.type) {
        case "ended":
          this.end();
          break;
        case "play":
        case "pause":
          // Transition to PLAYING or PAUSED state.
          this[event.type]();
          break;
        case "volumechange":
        case "timeupdate":
        case "resize":
          this.render();
          break;
        case "loadedmetadata":
          break;
      }
    }

    /* Media actions */

    playOrPause() {
      switch (this.state) {
        case PLAYING:
          this.media.pause();
          break;
        case BUFFERING:
        case ENDED:
        case PAUSED:
          this.media.play();
          break;
        default:
          throw new Error(`Invalid state ${this.state}`);
      }
    }

    toggleMuted() {
      this.media.muted = !this.media.muted;
    }

    toggleFullscreen() {
        const fullscreenEnabled = document.fullscreenEnabled && document.fullscreenElement;

        if (fullscreenEnabled) {
            document.exitFullscreen();
        } else {
            this.media.requestFullscreen();
        }
    }

    changeVolume() {
      const volume = parseInt(this.elements.volumeLevel.value);
      if (!isNaN(volume)) {
        this.media.volume = volume / 100;
      }
    }
  }

  new MediaControls();
})();

<|endoftext|>


<commit_before>var wishlist = function () {
  this.data;
};

wishlist.prototype.load = function(code, callback) {
  var _this = this;
  $.get("/wishlist/" + code, function (data) {
    _this.data = data;
    callback(_this.data);
  });
};

wishlist.prototype.save = function(data, callback) {
  data.code = this.data.code;
  $.post("/wishlist/", data, function () {
    callback();
  });
};

wishlist.prototype.createNew = function(callback) {
  var _this = this;
  var def = { title : "Default" };

  $.post("/wishlist/", def, function(data) {
    _this.data = data;
    callback();
  });
};

wishlist.prototype.wishlistAsHTML = function($header, $box) {
  $header.append($('<input type="text" class="title">').val(this.data.title));
  var ul = $('
');
  $box.append(ul);
  if(this.data.wishes) {
    $.each(this.data.wishes, function (i, v) {
      var li = $('
');
      var desc = $('<input type="text" class="description">').val(v.description);
      var url = $('<input type="text" class="url">').val(v.url);
      ul.append(li);
      li.append(desc);
      li.append(url);
    });
  }

  // Make one last empty set of boxes
  desc = $('<input type="text" class="description" value="">');
  url = $('<input type="text" class="url">');
  li = $('
');
  ul.append(li);
  li.append(desc);
  li.append(url);

  return box;
};

wishlist.prototype.wishlistFromHTML = function ($header,$content) {
  var result = {};
  result.title = $header.find('.title').val();
  var $wishes = $('.wish');
  result.wishes = [];

  $.each($wishes, function (i) {
    var description = $(this).find('.description').val();
    var url = $(this).find('.url').val();

    if (description) {
      result.wishes.push({
        "description": description,
        "url": url
      });
    }
  });

  return result;
};

var app = function () {
  this.$content = $('div#box');
  this.$header = $('div#title-box');
  this.$content.empty(); // remove the "activate javascript" message
  this.$create = $('#create-wishlist');
  this.clickers();
};

app.prototype.newWishlist = function () {
  var _this = this;
  this.wishlist = new wishlist();
  this.wishlist.createNew(function () {
    _this.$content.empty();
    _this.$header.empty();
    _this.wishlist.wishlistAsHTML(_this.$header,_this.$content);
    _this.wishClickers(); // set the change events on wish items
    window.history.pushState(null, "lol", _this.wishlist.data.code);
  });
};

app.prototype.saveWishlist = function () {
  var _this = this;
  var data = this.wishlist.wishlistFromHTML(this.$content);
  this.wishlist.save(data, function() {});
};

app.prototype.loadWishlist = function (code) {
  var _this = this;
  this.wishlist = new wishlist();
  this.wishlist.load(code, function (data) {
    _this.$content.empty();
    _this.$header.empty();
    _this.wishlist.wishlistAsHTML(_this.$header,_this.$content);
    _this.wishClickers(); // reload the change events on wish items
  });
};

app.prototype.clickers = function () {
  var _this = this;
  this.$create.click(function () {
    _this.newWishlist();
  });
};

app.prototype.wishClickers = function () {
  var _this = this;

  var $title = this.$header.find('.title');
  $title.data('oldVal', $title.val());
  $title.unbind('change');
  $title.change(function () {
    if($(this).data('oldVal') !== $(this).val() && $(this).val()) {
      _this.saveWishlist();
    }
  });

  this.$content.find('.wish').each(function () {
    var $desc = $(this).find('.description');
    var $url = $(this).find('.url');
    $desc.data('oldVal', $desc.val());
    $url.data('oldVal', $url.val());

    $desc.unbind('change');
    $desc.change(function () {
      if ($(this).data('oldVal') !== $(this).val()) {
        $(this).data('oldVal', $(this).val());

        if ($(this).val()) {
          _this.saveWishlist();

          if($(this).parent().hasClass('last')) {
            desc = $('<input type="text" class="description" value="">');
            url = $('<input type="text" class="url">');
            li = $('
');
            $(this).parent().after(li);
            li.append(desc);
            li.append(url);
            _this.wishClickers();

            $(this).parent().removeClass('last');
          }
        }
        else {
          if (!$(this).parent().hasClass('last')) {
            $(this).parent().remove();
            _this.saveWishlist();
          }
        }
      }
    });

    $url.unbind('click');
    $url.change(function () {
      if ($(this).data('oldVal') !== $(this).val()) {
        $(this).data('oldVal', $(this).val());

        if ($(this).prev().val()){
          _this.saveWishlist();
        }
      }
    });
  });
};
<commit_msg>Forgot to pass $header to savewishlist<commit_after>var wishlist = function () {
  this.data;
};

wishlist.prototype.load = function(code, callback) {
  var _this = this;
  $.get("/wishlist/" + code, function (data) {
    _this.data = data;
    callback(_this.data);
  });
};

wishlist.prototype.save = function(data, callback) {
  data.code = this.data.code;
  $.post("/wishlist/", data, function () {
    callback();
  });
};

wishlist.prototype.createNew = function(callback) {
  var _this = this;
  var def = { title : "Default" };

  $.post("/wishlist/", def, function(data) {
    _this.data = data;
    callback();
  });
};

wishlist.prototype.wishlistAsHTML = function($header, $box) {
  $header.append($('<input type="text" class="title">').val(this.data.title));
  var ul = $('
');
  $box.append(ul);
  if(this.data.wishes) {
    $.each(this.data.wishes, function (i, v) {
      var li = $('
');
      var desc = $('<input type="text" class="description">').val(v.description);
      var url = $('<input type="text" class="url">').val(v.url);
      ul.append(li);
      li.append(desc);
      li.append(url);
    });
  }

  // Make one last empty set of boxes
  desc = $('<input type="text" class="description" value="">');
  url = $('<input type="text" class="url">');
  li = $('
');
  ul.append(li);
  li.append(desc);
  li.append(url);

  return box;
};

wishlist.prototype.wishlistFromHTML = function ($header,$content) {
  var result = {};
  result.title = $header.find('.title').val();
  var $wishes = $('.wish');
  result.wishes = [];

  $.each($wishes, function (i) {
    var description = $(this).find('.description').val();
    var url = $(this).find('.url').val();

    if (description) {
      result.wishes.push({
        "description": description,
        "url": url
      });
    }
  });

  return result;
};

var app = function () {
  this.$content = $('div#box');
  this.$header = $('div#title-box');
  this.$content.empty(); // remove the "activate javascript" message
  this.$create = $('#create-wishlist');
  this.clickers();
};

app.prototype.newWishlist = function () {
  var _this = this;
  this.wishlist = new wishlist();
  this.wishlist.createNew(function () {
    _this.$content.empty();
    _this.$header.empty();
    _this.wishlist.wishlistAsHTML(_this.$header,_this.$content);
    _this.wishClickers(); // set the change events on wish items
    window.history.pushState(null, "lol", _this.wishlist.data.code);
  });
};

app.prototype.saveWishlist = function () {
  var _this = this;
  var data = this.wishlist.wishlistFromHTML(this.$header,this.$content);
  this.wishlist.save(data, function() {});
};

app.prototype.loadWishlist = function (code) {
  var _this = this;
  this.wishlist = new wishlist();
  this.wishlist.load(code, function (data) {
    _this.$content.empty();
    _this.$header.empty();
    _this.wishlist.wishlistAsHTML(_this.$header,_this.$content);
    _this.wishClickers(); // reload the change events on wish items
  });
};

app.prototype.clickers = function () {
  var _this = this;
  this.$create.click(function () {
    _this.newWishlist();
  });
};

app.prototype.wishClickers = function () {
  var _this = this;

  var $title = this.$header.find('.title');
  $title.data('oldVal', $title.val());
  $title.unbind('change');
  $title.change(function () {
    if($(this).data('oldVal') !== $(this).val() && $(this).val()) {
      _this.saveWishlist();
    }
  });

  this.$content.find('.wish').each(function () {
    var $desc = $(this).find('.description');
    var $url = $(this).find('.url');
    $desc.data('oldVal', $desc.val());
    $url.data('oldVal', $url.val());

    $desc.unbind('change');
    $desc.change(function () {
      if ($(this).data('oldVal') !== $(this).val()) {
        $(this).data('oldVal', $(this).val());

        if ($(this).val()) {
          _this.saveWishlist();

          if($(this).parent().hasClass('last')) {
            desc = $('<input type="text" class="description" value="">');
            url = $('<input type="text" class="url">');
            li = $('
');
            $(this).parent().after(li);
            li.append(desc);
            li.append(url);
            _this.wishClickers();

            $(this).parent().removeClass('last');
          }
        }
        else {
          if (!$(this).parent().hasClass('last')) {
            $(this).parent().remove();
            _this.saveWishlist();
          }
        }
      }
    });

    $url.unbind('click');
    $url.change(function () {
      if ($(this).data('oldVal') !== $(this).val()) {
        $(this).data('oldVal', $(this).val());

        if ($(this).prev().val()){
          _this.saveWishlist();
        }
      }
    });
  });
};
<|endoftext|>