<commit_before>/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

"use strict";
define(['ApplicationChannel', 'util/Sequence', 'util/Obj', 'view/GrowlNotification', 'util/I18N', 'FileManager', 'lib/jquery', 'lib/d3'],
    function (channel, sequence, obj, growl, I18N) {
        var svgId = sequence.next('svg');
        var deleteNotificationId = sequence.next('delete-notif');

        channel.bind('ui-actions', 'window-delete-pressed', function () {
            channel.send('ui-actions', 'delete-photos-trigger', {});
        });

        function translate(g, dx, dy) {
            g.attr('transform', function (d) {
                d.x = d.x + (dx ? dx : 0);
                d.y = d.y + (dy ? dy : 0);
                return 'translate(' + d.x + ',' + d.y + ')';
            });
        }

        function setDragBehaviour(g) {
            var drag = d3.behavior.drag()
                .origin(Object)
                .on("drag", function () {
                    var d3Event = d3.event;
                    translate(g, d3Event.dx, d3Event.dy);
                })
                .on("dragend", function () {
                    // TODO: "g.data(function(d) {})" is not working. Why?
                    // We won't create the element 'i'. This is just a workaround to get the node's data.
                    g.attr('i', function(d) {
                        channel.send('ui-actions', 'drag-photo', {
                            photoId: d.photoId,
                            nx: d.x,
                            ny: d.y
                        });
                    })
                });
            g.call(drag);
        }

        channel.bind('ui-actions', 'container-rendered', function (data) {
            var svg = d3.select('#' + data.containerId)
                .append('svg')
                .classed('svg-container', true)
                .attr('id', svgId)
                .attr('width', '1px')
                .attr('height', '1px');

            $('#' + svgId).on('dragover', function (evt) {
                evt.preventDefault();
            });

            $('#' + svgId).on('drop', function (evt) {
                evt.preventDefault();
                channel.send('ui-actions', 'file-drop', {
                    evt: evt
                });
            });
        });

        channel.bind('ui-actions', 'container-resized', function (data) {
            d3.select('#' + svgId)
                .attr('height', data.containerHeight + 'px')
                .attr('width', data.containerWidth + 'px');
        });

        channel.bind('file-manager', 'files-updated', function (data) {
            createFileItems(data);
        });

        function setSelectBehaviour(g) {
            g.on('click', function () {
                // TODO: "g.data(function(d) {})" is not working. Why?
                // We won't create the element 'i'. This is just a workaround to get the node's data.
                d3.select(this).attr('i', function(d) {
                    channel.send('ui-actions', 'file-selection', {
                        photoUid: d.photoId
                    });
                });

                growl.showNotification({
                    id: deleteNotificationId,
                    message: I18N.get('photo.delete.tip')
                });
            });
        }

        function createFileItems(data) {
            var svg = d3.select('#' + svgId);
            svg.selectAll('g').remove();

            var gSelection = svg.selectAll('g')
                .data(data)
                .enter()
                .append('g')
                .attr('id', function (d) {
                    return d.localId;
                })
                .classed('file-item', true);

            gSelection.append('rect')
                .attr('x', 0)
                .attr('y', 0)
                .attr('rx', 20)
                .attr('ry', 20)
                .attr('height', '202px')
                .attr('width', '202px')
                .classed('selected', function (d) {
                    return !(!d.isSelected);
                });

            gSelection.append('image')
                .attr('x', 10)
                .attr('y', 10)
                .attr('height', '180px')
                .attr('width', '180px')
                .attr('xlink:href', function (d) {
                    return d.href;
                });

            obj.forEach(data, function (d) {
                var g = d3.select('#' + d.localId);

                if (d.photoId) {
                    setDragBehaviour(g);
                }
                translate(g);
                setSelectBehaviour(g);
            });
        }
    }
)
;<commit_msg>-cosmetic<commit_after>/**
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

"use strict";
define(['ApplicationChannel', 'util/Sequence', 'util/Obj', 'view/GrowlNotification', 'util/I18N', 'FileManager', 'lib/jquery', 'lib/d3'],
    function (channel, sequence, obj, growl, I18N) {
        var svgId = sequence.next('svg');
        var deleteNotificationId = sequence.next('delete-notif');

        channel.bind('ui-actions', 'window-delete-pressed', function () {
            channel.send('ui-actions', 'delete-photos-trigger', {});
        });

        function translate(g, dx, dy) {
            g.attr('transform', function (d) {
                d.x = d.x + (dx ? dx : 0);
                d.y = d.y + (dy ? dy : 0);
                return 'translate(' + d.x + ',' + d.y + ')';
            });
        }

        function setDragBehaviour(g) {
            var drag = d3.behavior.drag()
                .origin(Object)
                .on("drag", function () {
                    var d3Event = d3.event;
                    translate(g, d3Event.dx, d3Event.dy);
                })
                .on("dragend", function () {
                    // TODO: "g.data(function(d) {})" is not working. Why?
                    // We won't create the element 'i'. This is just a workaround to get the node's data.
                    g.attr('i', function(d) {
                        channel.send('ui-actions', 'drag-photo', {
                            photoId: d.photoId,
                            nx: d.x,
                            ny: d.y
                        });
                    })
                });
            g.call(drag);
        }

        channel.bind('ui-actions', 'container-rendered', function (data) {
            var svg = d3.select('#' + data.containerId)
                .append('svg')
                .classed('svg-container', true)
                .attr('id', svgId)
                .attr('width', '1px')
                .attr('height', '1px');

            $('#' + svgId).on('dragover', function (evt) {
                evt.preventDefault();
            });

            $('#' + svgId).on('drop', function (evt) {
                evt.preventDefault();
                channel.send('ui-actions', 'file-drop', {
                    evt: evt
                });
            });
        });

        channel.bind('ui-actions', 'container-resized', function (data) {
            d3.select('#' + svgId)
                .attr('height', data.containerHeight + 'px')
                .attr('width', data.containerWidth + 'px');
        });

        channel.bind('file-manager', 'files-updated', function (data) {
            createFileItems(data);
        });

        function setSelectBehaviour(g) {
            g.on('click', function () {
                // TODO: "g.data(function(d) {})" is not working. Why?
                // We won't create the element 'i'. This is just a workaround to get the node's data.
                d3.select(this).attr('i', function(d) {
                    channel.send('ui-actions', 'file-selection', {
                        photoUid: d.photoId
                    });
                });

                growl.showNotification({
                    id: deleteNotificationId,
                    message: I18N.get('photo.delete.tip')
                });
            });
        }

        function createFileItems(data) {
            var svg = d3.select('#' + svgId);
            svg.selectAll('g').remove();

            var gSelection = svg.selectAll('g')
                .data(data)
                .enter()
                .append('g')
                .attr('id', function (d) {
                    return d.localId;
                })
                .classed('file-item', true);

            gSelection.append('rect')
                .attr('x', 0)
                .attr('y', 0)
                .attr('rx', 20)
                .attr('ry', 20)
                .attr('height', '202px')
                .attr('width', '202px')
                .classed('selected', function (d) {
                    return !(!d.isSelected);
                });

            gSelection.append('image')
                .attr('x', 10)
                .attr('y', 10)
                .attr('height', '180px')
                .attr('width', '180px')
                .attr('xlink:href', function (d) {
                    return d.href;
                });

            obj.forEach(data, function (d) {
                var g = d3.select('#' + d.localId);

                if (d.photoId) {
                    setDragBehaviour(g);
                }
                translate(g);
                setSelectBehaviour(g);
            });
        }
    }
);<|endoftext|>


<commit_before>/**
A container of AlerMessages. This container is perfect for top-of-form
messages. It defaults to beeing hidden. When you add a message it
becomes invisible, and when you remove all messages, it hides itself
automatically.
*/
Ext.define('devilry_extjsextras.AlertMessageList', {
    extend: 'Ext.panel.Panel',
    requires: [
        'devilry_extjsextras.AlertMessage'
    ],
    alias: 'widget.alertmessagelist',
    cls: 'devilry_extjsextras_alertmessagelist',
    hidden: true,
    frame: false,
    border: false,
    bodyStyle: 'background-color: transparent !important;',

    messageDefaults: {},

    initComponent: function() {
        this.on('remove', this._onRemove, this);
        this.callParent(arguments);
        this.addListener({
            scope: this,
            closed: this.onClose
        });
    },

    onClose: function(alertmessage) {
        this.remove(alertmessage, true);
    },

    /** Create and add a ``devilry_extjsextras.AlertMessage``. The config parameter is
     * forwarded to the AlertMessage constructor. */
    add: function(config) {
        var messageConfig = {};
        Ext.apply(messageConfig, this.messageDefaults);
        Ext.apply(messageConfig, config);
        var message = Ext.widget('alertmessage', messageConfig);
        this.callParent([message]);
        message.enableBubble('closed');
        this.show();
    },

    _onRemove: function() {
        var messages = this.query('alertmessage');
        if(messages.length === 0) {
            this.hide();
        }
    },

    /** Add many messages of the same type.
     *
     * @param messages Array of messages (strings).
     * @param type The type of the message (see ``devilry_extjsextras.AlertMessage.type``).
     * */
    addMany: function(messages, type) {
        Ext.Array.each(messages, function(message) {
            this.add({
                message: message,
                type: type
            });
        }, this);
    }
});
<commit_msg>devilry_extjsextras...AlertMessageList: Set layout.<commit_after>/**
A container of AlerMessages. This container is perfect for top-of-form
messages. It defaults to beeing hidden. When you add a message it
becomes invisible, and when you remove all messages, it hides itself
automatically.
*/
Ext.define('devilry_extjsextras.AlertMessageList', {
    extend: 'Ext.panel.Panel',
    requires: [
        'devilry_extjsextras.AlertMessage'
    ],
    alias: 'widget.alertmessagelist',
    cls: 'devilry_extjsextras_alertmessagelist',
    hidden: true,
    frame: false,
    border: false,
    bodyStyle: 'background-color: transparent !important;',

    layout: 'anchor',

    initComponent: function() {
        this.on('remove', this._onRemove, this);
        this.callParent(arguments);
        this.addListener({
            scope: this,
            closed: this.onClose
        });
    },

    onClose: function(alertmessage) {
        this.remove(alertmessage, true);
    },

    /** Create and add a ``devilry_extjsextras.AlertMessage``. The config parameter is
     * forwarded to the AlertMessage constructor. */
    add: function(config) {
        var messageConfig = {};
        Ext.apply(messageConfig, this.messageDefaults);
        Ext.apply(messageConfig, config);
        var message = Ext.widget('alertmessage', messageConfig);
        this.callParent([message]);
        message.enableBubble('closed');
        this.show();
    },

    _onRemove: function() {
        var messages = this.query('alertmessage');
        if(messages.length === 0) {
            this.hide();
        }
    },

    /** Add many messages of the same type.
     *
     * @param messages Array of messages (strings).
     * @param type The type of the message (see ``devilry_extjsextras.AlertMessage.type``).
     * */
    addMany: function(messages, type) {
        Ext.Array.each(messages, function(message) {
            this.add({
                message: message,
                type: type
            });
        }, this);
    }
});
<|endoftext|>


<commit_before>import { connect } from 'react-redux'
import { ensureState } from 'redux-optimistic-ui'
import { createSelector } from 'reselect'
import { Searcher } from 'fast-fuzzy'
import LibraryView from './LibraryView'
import {
  scrollArtists,
  toggleArtistExpanded,
  toggleArtistResultExpanded,
} from '../modules/library'

const getArtists = (state) => state.artists
const getSongs = (state) => state.songs
const getFilterStr = (state) => state.library.filterStr.trim().toLowerCase()
const getFilterStarred = (state) => state.library.filterStarred
const getStarredArtists = (state) => state.user.starredArtists
const getStarredSongs = (state) => state.user.starredSongs
const getQueue = (state) => ensureState(state.queue)
const getCurrentQueueId = (state) => state.status.queueId

const getQueuedSongs = createSelector(
  [getQueue, getCurrentQueueId],
  (queue, curId) => {
    const curPos = queue.result.indexOf(curId)
    return queue.result.slice(curPos + 1).map(queueId => queue.entities[queueId].songId)
  }
)

const getFilterKeywords = createSelector(
  [getFilterStr],
  (str) => str.length ? str.split(' ') : []
)

const getAlphaPickerMap = createSelector(
  [getArtists],
  (artists) => {
    const map = {}
    const chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
    let c = 1 // skip '#' since it should always be 0 (top)

    // prefill with zeros
    chars.forEach(char => { map[char] = 0 })

    artists.result.forEach((artistId, i) => {
      if (artists.entities[artistId].name.toUpperCase().startsWith(chars[c])) {
        map[chars[c]] = i
        c++
      }
    })

    return map
  })

// library filters
// ---------------------

const getArtistSearcher = createSelector(
  [getArtists],
  (artists) => new Searcher(artists.result, {
    keySelector: artistId => artists.entities[artistId].name,
    threshold: 0.8,
  })
)

const getSongSearcher = createSelector(
  [getSongs],
  (songs) => new Searcher(songs.result, {
    keySelector: songId => songs.entities[songId].title,
    threshold: 0.8,
  })
)

// #1: keyword filters
const getArtistsByKeyword = createSelector(
  [getArtists, getFilterStr, getArtistSearcher],
  (artists, str, searcher) => {
    if (!str) return artists.result

    return searcher.search(str, {
      returnMatchData: true,
    }).map(match => match.item)
  })

const getSongsByKeyword = createSelector(
  [getSongs, getFilterStr, getSongSearcher],
  (songs, str, searcher) => {
    if (!str) return songs.result

    return searcher.search(str, {
      returnMatchData: true,
    }).map(match => match.item)
  })

// #2: starred/hidden filters
const getArtistsByView = createSelector(
  [getArtistsByKeyword, getFilterStarred, getStarredArtists],
  (artistsWithKeyword, filterStarred, starredArtists) =>
    artistsWithKeyword.filter(artistId => {
      return filterStarred ? starredArtists.includes(artistId) : true
    })
)

const getSongsByView = createSelector(
  [getSongsByKeyword, getFilterStarred, getStarredSongs],
  (songsWithKeyword, filterStarred, starredSongs) =>
    songsWithKeyword.filter(songId => {
      return filterStarred ? starredSongs.includes(songId) : true
    })
)

const mapStateToProps = (state) => {
  return {
    artists: state.artists.entities,
    songs: state.songs.entities,
    queuedSongIds: getQueuedSongs(state),
    starredSongs: state.user.starredSongs,
    expandedArtists: state.library.expandedArtists,
    alphaPickerMap: getAlphaPickerMap(state),
    scrollTop: state.library.scrollTop,
    isSearching: !!getFilterKeywords(state).length || getFilterStarred(state),
    isLibraryEmpty: state.songs.result.length === 0,
    isShowingSongInfo: !(state.library.songInfoSongId === null),
    ui: state.ui,
    // SearchResults view
    songsResult: getSongsByView(state),
    artistsResult: getArtistsByView(state),
    filterKeywords: getFilterKeywords(state),
    filterStarred: getFilterStarred(state),
    expandedArtistResults: state.library.expandedArtistResults,
  }
}

const mapActionCreators = {
  toggleArtistExpanded,
  toggleArtistResultExpanded,
  scrollArtists,
}

export default connect(mapStateToProps, mapActionCreators)(LibraryView)
<commit_msg>Fix missing ensureState()<commit_after>import { connect } from 'react-redux'
import { ensureState } from 'redux-optimistic-ui'
import { createSelector } from 'reselect'
import { Searcher } from 'fast-fuzzy'
import LibraryView from './LibraryView'
import {
  scrollArtists,
  toggleArtistExpanded,
  toggleArtistResultExpanded,
} from '../modules/library'

const getArtists = (state) => state.artists
const getSongs = (state) => state.songs
const getFilterStr = (state) => state.library.filterStr.trim().toLowerCase()
const getFilterStarred = (state) => state.library.filterStarred
const getStarredArtists = (state) => ensureState(state.user.starredArtists)
const getStarredSongs = (state) => ensureState(state.user.starredSongs)
const getQueue = (state) => ensureState(state.queue)
const getCurrentQueueId = (state) => state.status.queueId

const getQueuedSongs = createSelector(
  [getQueue, getCurrentQueueId],
  (queue, curId) => {
    const curPos = queue.result.indexOf(curId)
    return queue.result.slice(curPos + 1).map(queueId => queue.entities[queueId].songId)
  }
)

const getFilterKeywords = createSelector(
  [getFilterStr],
  (str) => str.length ? str.split(' ') : []
)

const getAlphaPickerMap = createSelector(
  [getArtists],
  (artists) => {
    const map = {}
    const chars = '#ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
    let c = 1 // skip '#' since it should always be 0 (top)

    // prefill with zeros
    chars.forEach(char => { map[char] = 0 })

    artists.result.forEach((artistId, i) => {
      if (artists.entities[artistId].name.toUpperCase().startsWith(chars[c])) {
        map[chars[c]] = i
        c++
      }
    })

    return map
  })

// library filters
// ---------------------

const getArtistSearcher = createSelector(
  [getArtists],
  (artists) => new Searcher(artists.result, {
    keySelector: artistId => artists.entities[artistId].name,
    threshold: 0.8,
  })
)

const getSongSearcher = createSelector(
  [getSongs],
  (songs) => new Searcher(songs.result, {
    keySelector: songId => songs.entities[songId].title,
    threshold: 0.8,
  })
)

// #1: keyword filters
const getArtistsByKeyword = createSelector(
  [getArtists, getFilterStr, getArtistSearcher],
  (artists, str, searcher) => {
    if (!str) return artists.result

    return searcher.search(str, {
      returnMatchData: true,
    }).map(match => match.item)
  })

const getSongsByKeyword = createSelector(
  [getSongs, getFilterStr, getSongSearcher],
  (songs, str, searcher) => {
    if (!str) return songs.result

    return searcher.search(str, {
      returnMatchData: true,
    }).map(match => match.item)
  })

// #2: starred/hidden filters
const getArtistsByView = createSelector(
  [getArtistsByKeyword, getFilterStarred, getStarredArtists],
  (artistsWithKeyword, filterStarred, starredArtists) =>
    artistsWithKeyword.filter(artistId => {
      return filterStarred ? starredArtists.includes(artistId) : true
    })
)

const getSongsByView = createSelector(
  [getSongsByKeyword, getFilterStarred, getStarredSongs],
  (songsWithKeyword, filterStarred, starredSongs) =>
    songsWithKeyword.filter(songId => {
      return filterStarred ? starredSongs.includes(songId) : true
    })
)

const mapStateToProps = (state) => {
  return {
    artists: state.artists.entities,
    songs: state.songs.entities,
    queuedSongIds: getQueuedSongs(state),
    starredSongs: getStarredSongs(state),
    expandedArtists: state.library.expandedArtists,
    alphaPickerMap: getAlphaPickerMap(state),
    scrollTop: state.library.scrollTop,
    isSearching: !!getFilterKeywords(state).length || getFilterStarred(state),
    isLibraryEmpty: state.songs.result.length === 0,
    isShowingSongInfo: !(state.library.songInfoSongId === null),
    ui: state.ui,
    // SearchResults view
    songsResult: getSongsByView(state),
    artistsResult: getArtistsByView(state),
    filterKeywords: getFilterKeywords(state),
    filterStarred: getFilterStarred(state),
    expandedArtistResults: state.library.expandedArtistResults,
  }
}

const mapActionCreators = {
  toggleArtistExpanded,
  toggleArtistResultExpanded,
  scrollArtists,
}

export default connect(mapStateToProps, mapActionCreators)(LibraryView)
<|endoftext|>


<commit_before>import d3 from 'd3';
import style from 'PVWStyle/InfoVizNative/FieldSelector.mcss';

import CompositeClosureHelper from '../../../Common/Core/CompositeClosureHelper';
import template from './template.html';

// ----------------------------------------------------------------------------
// Global
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// Field Selector
// ----------------------------------------------------------------------------

function fieldSelector(publicAPI, model) {
  // private variables
  const hideField = {
    minMax: false,
    hist: false,
    minMaxWidth: 0,
    histWidth: 0,
  };

  // public API
  publicAPI.resize = () => {
    publicAPI.render();
  };

  publicAPI.setContainer = el => {
    if (model.container) {
      while (model.container.firstChild) {
        model.container.removeChild(model.container.firstChild);
      }
      model.container = null;
    }

    model.container = el;

    if (el) {
      d3.select(model.container)
        .style('overflow-y', 'auto')
        .style('overflow-x', 'hidden');
      d3.select(model.container).html(template);
      d3.select(model.container).select('.fieldSelector').classed(style.fieldSelector, true);

      model.fieldShowHistogram = model.fieldShowHistogram && (model.provider.isA('Histogram1DProvider'));
      // append headers for histogram columns
      if (model.fieldShowHistogram) {
        const header = d3.select(model.container).select('thead').select('tr');
        header.append('th').text('Min').classed(style.jsHistMin, true);
        header.append('th').text('Histogram').classed(style.jsSparkline, true);
        header.append('th').text('Max').classed(style.jsHistMax, true);
      }
      publicAPI.render();
    }
  };

  publicAPI.render = () => {
    if (!model.container) {
      return;
    }

    const legendSize = 15;

    // Apply style
    d3.select(model.container).select('thead').classed(style.thead, true);
    d3.select(model.container).select('tbody').classed(style.tbody, true);
    d3.select(model.container)
      .select('th.field-selector-mode')
      .on('click', d => {
        model.displayUnselected = !model.displayUnselected;
        publicAPI.render();
      })
      .select('i')
      // apply class - 'false' should come first to not remove common base class.
      .classed(!model.displayUnselected ? style.allFieldsIcon : style.selectedFieldsIcon, false)
      .classed(model.displayUnselected ? style.allFieldsIcon : style.selectedFieldsIcon, true);

    const data = model.displayUnselected ? model.provider.getFieldNames() : model.provider.getActiveFieldNames();
    // Update header label
    d3.select(model.container)
      .select('th.field-selector-label')
      .text(model.displayUnselected ? `All Variables (${data.length})` : `Selected Variables (${data.length})`)
      .on('click', d => {
        model.displayUnselected = !model.displayUnselected;
        publicAPI.render();
      });

    // test for too-long rows
    const hideMore = model.container.scrollWidth > model.container.clientWidth;
    if (hideMore) {
      if (!hideField.minMax) {
        hideField.minMax = true;
        hideField.minMaxWidth = model.container.scrollWidth;
        // if we hide min/max, we may also need to hide hist, so trigger another resize
        setTimeout(publicAPI.resize, 0);
      } else if (!hideField.hist) {
        hideField.hist = true;
        hideField.histWidth = model.container.scrollWidth;
      }
    } else if (hideField.minMax) {
      // if we've hidden something, see if we can re-show it.
      if (hideField.hist) {
        if (model.container.scrollWidth - hideField.histWidth > 0) {
          hideField.hist = false;
          hideField.histWidth = 0;
          // if we show hist, we may also need to show min/max, so trigger another resize
          setTimeout(publicAPI.resize, 0);
        }
      } else if (hideField.minMax) {
        if (model.container.scrollWidth - hideField.minMaxWidth > 0) {
          hideField.minMax = false;
          hideField.minMaxWidth = 0;
        }
      }
    }
    const header = d3.select(model.container).select('thead').select('tr');
    header.selectAll(`.${style.jsHistMin}`)
      .style('display', hideField.minMax ? 'none' : null);
    header.selectAll(`.${style.jsSparkline}`)
      .style('display', hideField.hist ? 'none' : null);
    header.selectAll(`.${style.jsHistMax}`)
      .style('display', hideField.minMax ? 'none' : null);

    // Handle variables
    const variablesContainer = d3
      .select(model.container)
      .select('tbody.fields')
      .selectAll('tr')
      .data(data);

    variablesContainer.enter().append('tr');
    variablesContainer.exit().remove();

    // Apply on each data item
    function renderField(fieldName, index) {
      const field = model.provider.getField(fieldName);
      const fieldContainer = d3.select(this);
      let legendCell = fieldContainer.select(`.${style.jsLegend}`);
      let fieldCell = fieldContainer.select(`.${style.jsFieldName}`);

      // Apply style to row (selected/unselected)
      fieldContainer
        .classed(!field.active ? style.selectedRow : style.unselectedRow, false)
        .classed(field.active ? style.selectedRow : style.unselectedRow, true)
        .on('click', name => {
          model.provider.toggleFieldSelection(name);
        });

      // Create missing DOM element if any
      if (legendCell.empty()) {
        legendCell = fieldContainer
          .append('td')
          .classed(style.legend, true);

        fieldCell = fieldContainer
          .append('td')
          .classed(style.fieldName, true);
      }

      // Apply legend
      if (model.provider.isA('LegendProvider')) {
        const { color, shape } = model.provider.getLegend(fieldName);
        legendCell
          .html(`<svg class='${style.legendSvg}' width='${legendSize}' height='${legendSize}'
                  fill='${color}' stroke='black'><use xlink:href='${shape}'/></svg>`);
      } else {
        legendCell
          .html('')
          .select('i')
          .classed(!field.active ? style.selectedRow : style.unselectedRow, false)
          .classed(field.active ? style.selectedRow : style.unselectedRow, true);
      }

      // Apply field name
      fieldCell.text(fieldName);

      if (model.fieldShowHistogram) {
        let minCell = fieldContainer.select(`.${style.jsHistMin}`);
        let histCell = fieldContainer.select(`.${style.jsSparkline}`);
        let maxCell = fieldContainer.select(`.${style.jsHistMax}`);

        if (histCell.empty()) {
          minCell = fieldContainer.append('td').classed(style.jsHistMin, true);
          histCell = fieldContainer.append('td').classed(style.sparkline, true);
          maxCell = fieldContainer.append('td').classed(style.jsHistMax, true);
          histCell.append('svg')
            .classed(style.sparklineSvg, true)
            .attr('width', model.fieldHistWidth)
            .attr('height', model.fieldHistHeight);
        }

        // make sure our data is ready. If not, render will be called when loaded.
        if (model.provider.loadHistogram1D(fieldName)) {
          const hobj = model.provider.getHistogram1D(fieldName);
          if (hobj !== null) {
            histCell
              .style('display', hideField.hist ? 'none' : null);
            // only do work if histogram is displayed.
            if (!hideField.hist) {
              const cmax = 1.0 * d3.max(hobj.counts);
              const hsize = hobj.counts.length;
              const hdata = histCell.select('svg')
                .selectAll(`.${style.jsHistRect}`).data(hobj.counts);

              hdata.enter().append('rect');
              // changes apply to both enter and update data join:
              hdata
                .attr('class', (d, i) => (i % 2 === 0 ? style.histRectEven : style.histRectOdd))
                .attr('pname', fieldName)
                .attr('y', d => model.fieldHistHeight * (1.0 - (d / cmax)))
                .attr('x', (d, i) => (model.fieldHistWidth / hsize) * i)
                .attr('height', d => model.fieldHistHeight * (d / cmax))
                .attr('width', model.fieldHistWidth / hsize);

              hdata.exit().remove();

              if (model.provider.isA('HistogramBinHoverProvider')) {
                histCell.select('svg')
                  .on('mousemove', function inner(d, i) {
                    const mCoords = d3.mouse(this);
                    const binNum = Math.floor((mCoords[0] / model.fieldHistWidth) * hsize);
                    const state = {};
                    state[fieldName] = [binNum];
                    model.provider.setHoverState({ state });
                  })
                  .on('mouseout', (d, i) => {
                    const state = {};
                    state[fieldName] = [-1];
                    model.provider.setHoverState({ state });
                  });
              }
            }

            const formatter = d3.format('.3s');
            minCell.text(formatter(hobj.min))
              .style('display', hideField.minMax ? 'none' : null);
            maxCell.text(formatter(hobj.max))
              .style('display', hideField.minMax ? 'none' : null);
          }
        }
      }
    }

    // Render all fields
    variablesContainer
      .each(renderField);
  };

  function handleHoverUpdate(data) {
    const svg = d3.select(model.container);
    Object.keys(data.state).forEach(pName => {
      const binList = data.state[pName];
      svg.selectAll(`rect[pname='${pName}']`)
        .classed(style.histoHilite, (d, i) => binList.indexOf(-1) === -1)
        .classed(style.binHilite, (d, i) => binList.indexOf(i) >= 0);
    });
  }

  // Make sure default values get applied
  publicAPI.setContainer(model.container);

  model.subscriptions.push({ unsubscribe: publicAPI.setContainer });
  model.subscriptions.push(model.provider.onFieldChange(publicAPI.render));
  if (model.fieldShowHistogram) {
    // event from Histogram Provider
    model.subscriptions.push(model.provider.onHistogram1DReady(publicAPI.render));
  }

  if (model.provider.isA('HistogramBinHoverProvider')) {
    model.subscriptions.push(model.provider.onHoverBinChange(handleHoverUpdate));
  }
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  container: null,
  provider: null,
  displayUnselected: true,
  fieldShowHistogram: true,
  fieldHistWidth: 120,
  fieldHistHeight: 15,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  CompositeClosureHelper.destroy(publicAPI, model);
  CompositeClosureHelper.isA(publicAPI, model, 'VizComponent');
  CompositeClosureHelper.get(publicAPI, model, ['provider', 'container', 'fieldShowHistogram']);
  CompositeClosureHelper.set(publicAPI, model, ['fieldShowHistogram']);

  fieldSelector(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = CompositeClosureHelper.newInstance(extend);

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<commit_msg>fix(FieldSelector): Do not modify style on parent container<commit_after>import d3 from 'd3';
import style from 'PVWStyle/InfoVizNative/FieldSelector.mcss';

import CompositeClosureHelper from '../../../Common/Core/CompositeClosureHelper';
import template from './template.html';

// ----------------------------------------------------------------------------
// Global
// ----------------------------------------------------------------------------

// ----------------------------------------------------------------------------
// Field Selector
// ----------------------------------------------------------------------------

function fieldSelector(publicAPI, model) {
  // private variables
  const hideField = {
    minMax: false,
    hist: false,
    minMaxWidth: 0,
    histWidth: 0,
  };

  // public API
  publicAPI.resize = () => {
    publicAPI.render();
  };

  publicAPI.setContainer = el => {
    if (model.container) {
      while (model.container.firstChild) {
        model.container.removeChild(model.container.firstChild);
      }
      model.container = null;
    }

    model.container = el;

    if (el) {
      d3.select(model.container)
      d3.select(model.container).html(template);
      d3.select(model.container).select('.fieldSelector').classed(style.fieldSelector, true);

      model.fieldShowHistogram = model.fieldShowHistogram && (model.provider.isA('Histogram1DProvider'));
      // append headers for histogram columns
      if (model.fieldShowHistogram) {
        const header = d3.select(model.container).select('thead').select('tr');
        header.append('th').text('Min').classed(style.jsHistMin, true);
        header.append('th').text('Histogram').classed(style.jsSparkline, true);
        header.append('th').text('Max').classed(style.jsHistMax, true);
      }
      publicAPI.render();
    }
  };

  publicAPI.render = () => {
    if (!model.container) {
      return;
    }

    const legendSize = 15;

    // Apply style
    d3.select(model.container).select('thead').classed(style.thead, true);
    d3.select(model.container).select('tbody').classed(style.tbody, true);
    d3.select(model.container)
      .select('th.field-selector-mode')
      .on('click', d => {
        model.displayUnselected = !model.displayUnselected;
        publicAPI.render();
      })
      .select('i')
      // apply class - 'false' should come first to not remove common base class.
      .classed(!model.displayUnselected ? style.allFieldsIcon : style.selectedFieldsIcon, false)
      .classed(model.displayUnselected ? style.allFieldsIcon : style.selectedFieldsIcon, true);

    const data = model.displayUnselected ? model.provider.getFieldNames() : model.provider.getActiveFieldNames();
    // Update header label
    d3.select(model.container)
      .select('th.field-selector-label')
      .text(model.displayUnselected ? `All Variables (${data.length})` : `Selected Variables (${data.length})`)
      .on('click', d => {
        model.displayUnselected = !model.displayUnselected;
        publicAPI.render();
      });

    // test for too-long rows
    const hideMore = model.container.scrollWidth > model.container.clientWidth;
    if (hideMore) {
      if (!hideField.minMax) {
        hideField.minMax = true;
        hideField.minMaxWidth = model.container.scrollWidth;
        // if we hide min/max, we may also need to hide hist, so trigger another resize
        setTimeout(publicAPI.resize, 0);
      } else if (!hideField.hist) {
        hideField.hist = true;
        hideField.histWidth = model.container.scrollWidth;
      }
    } else if (hideField.minMax) {
      // if we've hidden something, see if we can re-show it.
      if (hideField.hist) {
        if (model.container.scrollWidth - hideField.histWidth > 0) {
          hideField.hist = false;
          hideField.histWidth = 0;
          // if we show hist, we may also need to show min/max, so trigger another resize
          setTimeout(publicAPI.resize, 0);
        }
      } else if (hideField.minMax) {
        if (model.container.scrollWidth - hideField.minMaxWidth > 0) {
          hideField.minMax = false;
          hideField.minMaxWidth = 0;
        }
      }
    }
    const header = d3.select(model.container).select('thead').select('tr');
    header.selectAll(`.${style.jsHistMin}`)
      .style('display', hideField.minMax ? 'none' : null);
    header.selectAll(`.${style.jsSparkline}`)
      .style('display', hideField.hist ? 'none' : null);
    header.selectAll(`.${style.jsHistMax}`)
      .style('display', hideField.minMax ? 'none' : null);

    // Handle variables
    const variablesContainer = d3
      .select(model.container)
      .select('tbody.fields')
      .selectAll('tr')
      .data(data);

    variablesContainer.enter().append('tr');
    variablesContainer.exit().remove();

    // Apply on each data item
    function renderField(fieldName, index) {
      const field = model.provider.getField(fieldName);
      const fieldContainer = d3.select(this);
      let legendCell = fieldContainer.select(`.${style.jsLegend}`);
      let fieldCell = fieldContainer.select(`.${style.jsFieldName}`);

      // Apply style to row (selected/unselected)
      fieldContainer
        .classed(!field.active ? style.selectedRow : style.unselectedRow, false)
        .classed(field.active ? style.selectedRow : style.unselectedRow, true)
        .on('click', name => {
          model.provider.toggleFieldSelection(name);
        });

      // Create missing DOM element if any
      if (legendCell.empty()) {
        legendCell = fieldContainer
          .append('td')
          .classed(style.legend, true);

        fieldCell = fieldContainer
          .append('td')
          .classed(style.fieldName, true);
      }

      // Apply legend
      if (model.provider.isA('LegendProvider')) {
        const { color, shape } = model.provider.getLegend(fieldName);
        legendCell
          .html(`<svg class='${style.legendSvg}' width='${legendSize}' height='${legendSize}'
                  fill='${color}' stroke='black'><use xlink:href='${shape}'/></svg>`);
      } else {
        legendCell
          .html('')
          .select('i')
          .classed(!field.active ? style.selectedRow : style.unselectedRow, false)
          .classed(field.active ? style.selectedRow : style.unselectedRow, true);
      }

      // Apply field name
      fieldCell.text(fieldName);

      if (model.fieldShowHistogram) {
        let minCell = fieldContainer.select(`.${style.jsHistMin}`);
        let histCell = fieldContainer.select(`.${style.jsSparkline}`);
        let maxCell = fieldContainer.select(`.${style.jsHistMax}`);

        if (histCell.empty()) {
          minCell = fieldContainer.append('td').classed(style.jsHistMin, true);
          histCell = fieldContainer.append('td').classed(style.sparkline, true);
          maxCell = fieldContainer.append('td').classed(style.jsHistMax, true);
          histCell.append('svg')
            .classed(style.sparklineSvg, true)
            .attr('width', model.fieldHistWidth)
            .attr('height', model.fieldHistHeight);
        }

        // make sure our data is ready. If not, render will be called when loaded.
        if (model.provider.loadHistogram1D(fieldName)) {
          const hobj = model.provider.getHistogram1D(fieldName);
          if (hobj !== null) {
            histCell
              .style('display', hideField.hist ? 'none' : null);
            // only do work if histogram is displayed.
            if (!hideField.hist) {
              const cmax = 1.0 * d3.max(hobj.counts);
              const hsize = hobj.counts.length;
              const hdata = histCell.select('svg')
                .selectAll(`.${style.jsHistRect}`).data(hobj.counts);

              hdata.enter().append('rect');
              // changes apply to both enter and update data join:
              hdata
                .attr('class', (d, i) => (i % 2 === 0 ? style.histRectEven : style.histRectOdd))
                .attr('pname', fieldName)
                .attr('y', d => model.fieldHistHeight * (1.0 - (d / cmax)))
                .attr('x', (d, i) => (model.fieldHistWidth / hsize) * i)
                .attr('height', d => model.fieldHistHeight * (d / cmax))
                .attr('width', model.fieldHistWidth / hsize);

              hdata.exit().remove();

              if (model.provider.isA('HistogramBinHoverProvider')) {
                histCell.select('svg')
                  .on('mousemove', function inner(d, i) {
                    const mCoords = d3.mouse(this);
                    const binNum = Math.floor((mCoords[0] / model.fieldHistWidth) * hsize);
                    const state = {};
                    state[fieldName] = [binNum];
                    model.provider.setHoverState({ state });
                  })
                  .on('mouseout', (d, i) => {
                    const state = {};
                    state[fieldName] = [-1];
                    model.provider.setHoverState({ state });
                  });
              }
            }

            const formatter = d3.format('.3s');
            minCell.text(formatter(hobj.min))
              .style('display', hideField.minMax ? 'none' : null);
            maxCell.text(formatter(hobj.max))
              .style('display', hideField.minMax ? 'none' : null);
          }
        }
      }
    }

    // Render all fields
    variablesContainer
      .each(renderField);
  };

  function handleHoverUpdate(data) {
    const svg = d3.select(model.container);
    Object.keys(data.state).forEach(pName => {
      const binList = data.state[pName];
      svg.selectAll(`rect[pname='${pName}']`)
        .classed(style.histoHilite, (d, i) => binList.indexOf(-1) === -1)
        .classed(style.binHilite, (d, i) => binList.indexOf(i) >= 0);
    });
  }

  // Make sure default values get applied
  publicAPI.setContainer(model.container);

  model.subscriptions.push({ unsubscribe: publicAPI.setContainer });
  model.subscriptions.push(model.provider.onFieldChange(publicAPI.render));
  if (model.fieldShowHistogram) {
    // event from Histogram Provider
    model.subscriptions.push(model.provider.onHistogram1DReady(publicAPI.render));
  }

  if (model.provider.isA('HistogramBinHoverProvider')) {
    model.subscriptions.push(model.provider.onHoverBinChange(handleHoverUpdate));
  }
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  container: null,
  provider: null,
  displayUnselected: true,
  fieldShowHistogram: true,
  fieldHistWidth: 120,
  fieldHistHeight: 15,
};

// ----------------------------------------------------------------------------

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  CompositeClosureHelper.destroy(publicAPI, model);
  CompositeClosureHelper.isA(publicAPI, model, 'VizComponent');
  CompositeClosureHelper.get(publicAPI, model, ['provider', 'container', 'fieldShowHistogram']);
  CompositeClosureHelper.set(publicAPI, model, ['fieldShowHistogram']);

  fieldSelector(publicAPI, model);
}

// ----------------------------------------------------------------------------

export const newInstance = CompositeClosureHelper.newInstance(extend);

// ----------------------------------------------------------------------------

export default { newInstance, extend };
<|endoftext|>


<commit_before>// Copyright 2009, Martin Kuckert
// Note: This software is NOT (yet) open source!

// simulate firebug
if(!window.console) {
	var console={log:function(){}};
}

/**
 * @class Tetris
 */
/**
 * @constructor
 * @param String|HtmlElement|Curly.Canvas|CanvasRenderingContext2D
 * @param Object
 */
var Tetris=function(canvasElement, config) {
	if(!(canvasElement instanceof Curly.Canvas)) {
		canvasElement=new Curly.Canvas(canvasElement);
	}

	this.canvas=canvasElement;

	Curly.extend(this, config||{});

	this.init();
};
Curly.extendClass(Tetris, Object, {
	/**
	 * @var Curly.Canvas canvas The canvas to draw the game on
	 */
	canvas: null,
	/**
	 * @var Array timer The interval timer resources
	 */
	timer: null,
	/**
	 * @var Integer rows The number of rows in the game grid.
	 */
	rows: 12,
	/**
	 * @var Integer cols The number of columns in the game grid.
	 */
	cols: 8,
	/**
	 * @var Integer speed The current game speed.
	 */
	speed: 2,
	/**
	 * @var Array boxes An array of an array of all the tetris boxes
	 */
	boxes: null,
	/**
	 * @var Tetris.Element curEl The current tetris box element.
	 */
	curEl: undefined,
	/**
	 * @var Tetris.Element nextEl The next tetris box element.
	 */
	nextEl: null,
	/**
	 * @var Array elCtrs List of constructors of all possible box elements.
	 */
	elCtrs: [],
	/**
	 * @var Integer size The size of a tetris box in pixel.
	 */
	size: 20,
	/**
	 * @var Integer loopState The current loop state
	 */
	loopState: 0,
	/**
	 * @var Object moveState The current movement state. Used for the next movement calculation
	 */
	moveState: null,
	/**
	 * @var Array colors List of all possible box colors.
	 */
	colors: ['red', 'blue', 'green'],
	/**
	 * @var Array finishedRows List of currently detected finished rows
	 */
	finishedRows: [],
	/**
	 * @var Integer finishedRowsAnimation State of the animation of detected finished rows
	 */
	finishedRowsAnimation: 0,
	/**
	 * Starts the main loop of the game.
	 *
	 * @return void
	 */
	run: function() {
		var self=this;
		this.timer=[
			window.setTimeout(function() {
				self.loop();
			}, 1000/this.speed),
			window.setTimeout(function() {
				self.dump();
			}, 50)
		];
	},
	/**
	 * Stops the main loop of the game.
	 *
	 * @return void
	 */
	stop: function() {
		for(var i=0; i<this.timer.length; i++) {
			window.clearTimeout(this.timer[i]);
		}
		this.timer=[];
	},
	/**
	 * Initializes the game.
	 *
	 * @return void
	 */
	init: function() {
		this.boxes=new Array(this.rows);
		for(var y=0, x; y<this.rows; y++) {
			this.boxes[y]=new Array(this.cols);
		}

		if(this.elCtrs.length<=0) {
			for(var i=0, n=Tetris.ALL_CTRS.length; i<n; i++) {
				this.elCtrs.push(Tetris.ALL_CTRS[i]);
			}
		}

		// Create first elements
		this.nextEl=this.createRandEl();
		this.render();

		// Bind event listener
		this.moveState={
			moveleft: false,
			moveright: false,
			movedown: false,
			rotateleft: false,
			rotateright: false
		};
		var self=this;
		document.addEventListener('keydown', function(e) {
			self.onKeyDown(e);
		}, true);
	},
	/**
	 * Eventlistener for the keydown event.
	 *
	 * @return void
	 * @param Event
	 */
	onKeyDown: function(event) {
		var m=this.moveState;

		// TODO: Don't hardcode this!
		switch(event.keyCode) {
			case 65:
				m.moveleft=true;
				m.moveright=false;
				break;
			case 68:
				m.moveleft=false;
				m.moveright=true;
				break;
			case 83:
				m.movedown=true;
				break;
			case 81:
				m.rotateleft=true;
				m.rotateright=false;
				break;
			case 69:
				m.rotateleft=false;
				m.rotateright=true;
				break;
			case 77:
				if(!this.curEl) {
					break;
				}
				this.curEl.rotateClockwise();
				this.render();
				break;
			case 78:
				if(!this.curEl) {
					break;
				}
				this.curEl.rotateAntiClockwise();
				this.render();
				break;
		}
		this.moveState=m;
	},
	/**
	 * Initializes the given element.
	 *
	 * @return void
	 * @param Tetris.ElementBase
	 */
	initElement: function(el) {
		el.h=el.boxes.length;
		el.w=el.boxes[0].length;
		el.x=Math.floor((this.cols-el.w)/2);
		el.y=0;
	},
	/**
	 * Inserts the given element into the boxes array
	 *
	 * @return void
	 * @param Tetris.ElementBase
	 */
	insertElement: function(el) {
		var b=this.boxes;
		for(var i=0, j; i<el.h; i++) {
			for(j=0; j<el.w; j++) {
				if(el.boxes[i][j]!==undefined) {
					b[i+el.y][j+el.x]=el.color;
				}
			}
		}
	},
	/**
	 * Runs one main loop iteration.
	 *
	 * @return void
	 */
	loop: function() {
		if(this.finishedRowsAnimation===0) {
			switch(this.loopState) {
				case Tetris.LOOPSTATE_INIT:
					this.stateInit();
					break;
				case Tetris.LOOPSTATE_MOVE:
					this.stateMove();
					break;
			}

			this.findFinishedRows();
		}
		else if(this.finishedRowsAnimation>4) {
			this.removeFinishedRows();
		}

		this.render();

		this.loopState=(this.loopState+1)%Tetris.LOOPSTATES;

		var self=this;
		this.timer[0]=window.setTimeout(function() {
			self.loop();
		}, 1000/this.speed);
	},
	/**
	 * Checks for a collision of the given element.
	 *
	 * @return boolean true, if there's a collision
	 * @param Tetris.ElementBase The element
	 */
	checkCollision: function(el) {
		if(el.x<0 || el.y<0 || el.x+el.w>this.cols || el.y+el.h>this.rows) {
			return true;
		}

		var b=this.boxes, i, j;
		for(i=0; i<el.h; i++) {
			// Check the element boxes for a collision
			for(j=0; j<el.w; j++) {
				if(el.boxes[i][j] && b[i+el.y][j+el.x]) {
					console.log('Collision at '+(j+el.x)+', '+(i+el.y)+' detected');
					return true;
				}
			}
		}

		return false;
	},
	/**
	 * Processes the LOOPSTATE_INIT
	 *
	 * @return void
	 */
	stateInit: function() {
		// Create new element
		if(this.curEl===undefined) {
			this.curEl=this.nextEl;
			this.nextEl=this.createRandEl();
			this.initElement(this.curEl);
		}
		// Move current element
		else {
			this.moveState.movedown=true;
			this.stateMove();
		}
	},
	/**
	 * Processes the LOOPSTATE_MOVE
	 *
	 * @return void
	 */
	stateMove: function() {
		var el=this.curEl, m=this.moveState;
		if(el===undefined) {
			return;
		}

		var y=el.y, i, j, changed=false;

		// Move down
		if(m.movedown===true) {
			y++;
			changed=true;
			m.movedown=false;
		}

		// Only move left or right, if we move down
		if(changed) {
			if(m.moveleft===true) {
				if(el.x>0) {
					el.x--;

					if(this.checkCollision(el)) {
						el.x++;
					}
					else {
						changed=true;
					}
				}

				m.moveleft=false;
			}
			else if(m.moveright===true) {
				if((el.x+el.w)<this.cols) {
					el.x++;

					if(this.checkCollision(el)) {
						el.x--;
					}
					else {
						changed=true;
					}
				}

				m.moveright=false;
			}
		}

		// Perform movement
		if(changed) {
			this.moveY(el, y);
		}
	},
	/**
	 * Performs an element move on the y axis. If there's a collision, the element is inserted and
	 * the current element removed.
	 *
	 * @return void
	 * @param Tetris.ElementBase el
	 * @param integer y
	 */
	moveY: function(el, y) {
		var oldY=el.y, forceInsert=false;
		el.y=y;
		if(this.checkCollision(el)) {
			el.y=oldY;
			forceInsert=true;
		}
		if(forceInsert || el.h+y>=this.rows) {
			this.curEl=undefined;
			this.insertElement(el);
		}
	},
	/**
	 * Dumps the current state.
	 *
	 * @return void
	 */
	dump: function() {
		var t=[];

		t.push('movestate:');
		var m=this.moveState;
		t.push(' right '+m.moveright);
		t.push(' left '+m.moveleft);
		t.push(' down '+m.movedown);

		var dump=document.getElementById('dump').textContent=t.join("\n");

		var self=this;
		this.timer[1]=window.setTimeout(function() {
			self.dump();
		}, 50);
	},
	/**
	 * Finds rows full of boxes.
	 *
	 * @return void
	 */
	findFinishedRows: function() {
		var i, j, found, b=this.boxes, finished=[];
		for(i=0; i<this.rows; i++) {
			found=true;
			for(j=0; j<this.cols; j++) {
				if(b[i][j]===undefined) {
					found=false;
					break;
				}
			}

			if(found) {
				finished.push(i);
			}
		}

		if(finished.length>0) {
			this.finishedRowsAnimation=1;
		}

		this.finishedRows=finished;
	},
	/**
	 * Removes finished rows
	 *
	 * @return void
	 */
	removeFinishedRows: function() {
		var fr=this.finishedRows;
		var n=fr.length;
		for(var i=n-1; i>=0; i--) {
			this.boxes.splice(fr[i], 1);
		}

		for(var i=0; i<n; i++) {
			this.boxes.unshift(new Array(this.cols));
		}

		this.finishedRows=[];
		this.finishedRowsAnimation=0;
	},
	/**
	 * Renders the current game state.
	 *
	 * @return void
	 */
	render: function() {
		var b=this.boxes, x, y, s=this.size;
		this.canvas.
			clear().
			statefulPath(0, 0).
			setState('fillStyle', 'black').
			rect(this.cols*s, this.rows*s).
			draw();

		var path=this.canvas.
			overwriteState('strokeStyle', '#333').
			path(0, 0);

		for(x=1; x<this.cols; x++) {
			path.
				moveTo(x*s, 0).
				lineTo(x*s, this.rows*s);
		}
		for(y=1; y<this.rows; y++) {
			path.
				moveTo(0, y*s).
				lineTo(this.cols*s, y*s);
		}

		path.draw(false, true);

		// Render static boxes
		for(y=0; y<this.rows; y++) {
			for(x=0; x<this.cols; x++) {
				if(b[y][x]) {
					this.renderBox(x, y, b[y][x], this.canvas);
				}
			}
		}

		// Render current element
		var el=this.curEl;
		if(el) {
			for(y=0; y<el.h; y++) {
				for(x=0; x<el.w; x++) {
					if(!el.boxes[y][x]) {
						continue;
					}
					this.renderBox(x+el.x, y+el.y, el.color, this.canvas);
				}
			}
		}

		if(this.finishedRowsAnimation>0) {
			var fr=this.finishedRows;
			var color=this.finishedRowsAnimation%2==1 ? 'rgba(255,0,0,0.5)' : 'rgba(0,0,255,0.5)';

			for(var i=0, j; i<fr.length; i++) {
				for(j=0; j<this.cols; j++) {
					this.renderBox(j, fr[i], color, this.canvas);
				}
			}

			this.finishedRowsAnimation++;
		}
	},
	/**
	 * Renders a single box.
	 *
	 * @return void
	 * @param integer x position of the box
	 * @param integer y position of the box
	 * @param string the color to use
	 * @param Curly.Canvas the canvas context
	 */
	renderBox: function(x, y, color, canvas) {
		var s=this.size;
		var xs=x*s, ys=y*s;

		var path=canvas.statefulPath(xs, ys);
		path.setState('fillStyle', color).
			rect(s, s).
		// left light
			moveTo(xs, ys).
			setState('alpha', 0.6).
			setState('fillStyle', 'white').
			rect(1, s).
		// top light
			moveTo(xs, ys).
			rect(s, 1).
		// right shadow
			moveTo(xs+s-1, ys).
			setState('alpha', 0.6).
			setState('fillStyle', 'black').
			rect(1, s).
		// bottom shadow
			moveTo(xs, ys+s-1).
			rect(s, 1).
		// draw
			draw(true, false);
		canvas.overwriteState(Curly.Canvas.State.DEFAULTS);
	},
	/**
	 * Creates a new random tetris box element.
	 *
	 * @return void
	 */
	createRandEl: function() {
		var el=Math.floor(Math.random()*this.elCtrs.length);
		var c=Math.floor(Math.random()*this.colors.length);
		el=new this.elCtrs[el]();
		el.game=this;
		el.color=this.colors[c];
		console.log('create el: '+el.name+' '+c);
		return el;
	}
});

Tetris.LOOPSTATE_INIT=0;
Tetris.LOOPSTATE_MOVE=1;
Tetris.LOOPSTATES=2;<commit_msg>Fixed different drop speed of stones.<commit_after>// Copyright 2009, Martin Kuckert
// Note: This software is NOT (yet) open source!

// simulate firebug
if(!window.console) {
	var console={log:function(){}};
}

/**
 * @class Tetris
 */
/**
 * @constructor
 * @param String|HtmlElement|Curly.Canvas|CanvasRenderingContext2D
 * @param Object
 */
var Tetris=function(canvasElement, config) {
	if(!(canvasElement instanceof Curly.Canvas)) {
		canvasElement=new Curly.Canvas(canvasElement);
	}

	this.canvas=canvasElement;

	Curly.extend(this, config||{});

	this.init();
};
Curly.extendClass(Tetris, Object, {
	/**
	 * @var Curly.Canvas canvas The canvas to draw the game on
	 */
	canvas: null,
	/**
	 * @var Array timer The interval timer resources
	 */
	timer: null,
	/**
	 * @var Integer rows The number of rows in the game grid.
	 */
	rows: 12,
	/**
	 * @var Integer cols The number of columns in the game grid.
	 */
	cols: 8,
	/**
	 * @var Integer speed The current game speed.
	 */
	speed: 2,
	/**
	 * @var Array boxes An array of an array of all the tetris boxes
	 */
	boxes: null,
	/**
	 * @var Tetris.Element curEl The current tetris box element.
	 */
	curEl: undefined,
	/**
	 * @var Tetris.Element nextEl The next tetris box element.
	 */
	nextEl: null,
	/**
	 * @var Array elCtrs List of constructors of all possible box elements.
	 */
	elCtrs: [],
	/**
	 * @var Integer size The size of a tetris box in pixel.
	 */
	size: 20,
	/**
	 * @var Integer loopState The current loop state
	 */
	loopState: 0,
	/**
	 * @var Object moveState The current movement state. Used for the next movement calculation
	 */
	moveState: null,
	/**
	 * @var Array colors List of all possible box colors.
	 */
	colors: ['red', 'blue', 'green'],
	/**
	 * @var Array finishedRows List of currently detected finished rows
	 */
	finishedRows: [],
	/**
	 * @var Integer finishedRowsAnimation State of the animation of detected finished rows
	 */
	finishedRowsAnimation: 0,
	/**
	 * Starts the main loop of the game.
	 *
	 * @return void
	 */
	run: function() {
		var self=this;
		this.timer=[
			window.setInterval(function() {
				self.loop();
			}, 1000/this.speed),
			window.setInterval(function() {
				self.dump();
			}, 50)
		];
	},
	/**
	 * Stops the main loop of the game.
	 *
	 * @return void
	 */
	stop: function() {
		for(var i=0; i<this.timer.length; i++) {
			window.clearInterval(this.timer[i]);
		}
		this.timer=[];
	},
	/**
	 * Initializes the game.
	 *
	 * @return void
	 */
	init: function() {
		this.boxes=new Array(this.rows);
		for(var y=0, x; y<this.rows; y++) {
			this.boxes[y]=new Array(this.cols);
		}

		if(this.elCtrs.length<=0) {
			for(var i=0, n=Tetris.ALL_CTRS.length; i<n; i++) {
				this.elCtrs.push(Tetris.ALL_CTRS[i]);
			}
		}

		// Create first elements
		this.nextEl=this.createRandEl();
		this.render();

		// Bind event listener
		this.moveState={
			moveleft: false,
			moveright: false,
			movedown: false,
			rotateleft: false,
			rotateright: false
		};
		var self=this;
		document.addEventListener('keydown', function(e) {
			self.onKeyDown(e);
		}, true);
	},
	/**
	 * Eventlistener for the keydown event.
	 *
	 * @return void
	 * @param Event
	 */
	onKeyDown: function(event) {
		var m=this.moveState;

		// TODO: Don't hardcode this!
		switch(event.keyCode) {
			case 65:
				m.moveleft=true;
				m.moveright=false;
				break;
			case 68:
				m.moveleft=false;
				m.moveright=true;
				break;
			case 83:
				m.movedown=true;
				break;
			case 81:
				m.rotateleft=true;
				m.rotateright=false;
				break;
			case 69:
				m.rotateleft=false;
				m.rotateright=true;
				break;
			case 77:
				if(!this.curEl) {
					break;
				}
				this.curEl.rotateClockwise();
				this.render();
				break;
			case 78:
				if(!this.curEl) {
					break;
				}
				this.curEl.rotateAntiClockwise();
				this.render();
				break;
		}
		this.moveState=m;
	},
	/**
	 * Initializes the given element.
	 *
	 * @return void
	 * @param Tetris.ElementBase
	 */
	initElement: function(el) {
		el.h=el.boxes.length;
		el.w=el.boxes[0].length;
		el.x=Math.floor((this.cols-el.w)/2);
		el.y=0;
	},
	/**
	 * Inserts the given element into the boxes array
	 *
	 * @return void
	 * @param Tetris.ElementBase
	 */
	insertElement: function(el) {
		var b=this.boxes;
		for(var i=0, j; i<el.h; i++) {
			for(j=0; j<el.w; j++) {
				if(el.boxes[i][j]!==undefined) {
					b[i+el.y][j+el.x]=el.color;
				}
			}
		}
	},
	/**
	 * Runs one main loop iteration.
	 *
	 * @return void
	 */
	loop: function() {
		if(this.finishedRowsAnimation===0) {
			switch(this.loopState) {
				case Tetris.LOOPSTATE_INIT:
					this.stateInit();
					break;
				case Tetris.LOOPSTATE_MOVE:
					this.stateMove();
					break;
			}

			this.findFinishedRows();
		}
		else if(this.finishedRowsAnimation>4) {
			this.removeFinishedRows();
		}

		this.render();

		this.loopState=(this.loopState+1)%Tetris.LOOPSTATES;
	},
	/**
	 * Checks for a collision of the given element.
	 *
	 * @return boolean true, if there's a collision
	 * @param Tetris.ElementBase The element
	 */
	checkCollision: function(el) {
		if(el.x<0 || el.y<0 || el.x+el.w>this.cols || el.y+el.h>this.rows) {
			return true;
		}

		var b=this.boxes, i, j;
		for(i=0; i<el.h; i++) {
			// Check the element boxes for a collision
			for(j=0; j<el.w; j++) {
				if(el.boxes[i][j] && b[i+el.y][j+el.x]) {
					console.log('Collision at '+(j+el.x)+', '+(i+el.y)+' detected');
					return true;
				}
			}
		}

		return false;
	},
	/**
	 * Processes the LOOPSTATE_INIT
	 *
	 * @return void
	 */
	stateInit: function() {
		// Create new element
		if(this.curEl===undefined) {
			this.curEl=this.nextEl;
			this.nextEl=this.createRandEl();
			this.initElement(this.curEl);
		}
		// Move current element
		else {
			this.moveState.movedown=true;
			this.stateMove();
		}
	},
	/**
	 * Processes the LOOPSTATE_MOVE
	 *
	 * @return void
	 */
	stateMove: function() {
		var el=this.curEl, m=this.moveState;
		if(el===undefined) {
			return;
		}

		var y=el.y, i, j, changed=false;

		// Move down
		if(m.movedown===true) {
			y++;
			changed=true;
			m.movedown=false;
		}

		// Only move left or right, if we move down
		if(changed) {
			if(m.moveleft===true) {
				if(el.x>0) {
					el.x--;

					if(this.checkCollision(el)) {
						el.x++;
					}
					else {
						changed=true;
					}
				}

				m.moveleft=false;
			}
			else if(m.moveright===true) {
				if((el.x+el.w)<this.cols) {
					el.x++;

					if(this.checkCollision(el)) {
						el.x--;
					}
					else {
						changed=true;
					}
				}

				m.moveright=false;
			}
		}

		// Perform movement
		if(changed) {
			this.moveY(el, y);
		}
	},
	/**
	 * Performs an element move on the y axis. If there's a collision, the element is inserted and
	 * the current element removed.
	 *
	 * @return void
	 * @param Tetris.ElementBase el
	 * @param integer y
	 */
	moveY: function(el, y) {
		var oldY=el.y, forceInsert=false;
		el.y=y;
		if(this.checkCollision(el)) {
			el.y=oldY;
			forceInsert=true;
		}
		if(forceInsert || el.h+y>=this.rows) {
			this.curEl=undefined;
			this.insertElement(el);
		}
	},
	/**
	 * Dumps the current state.
	 *
	 * @return void
	 */
	dump: function() {
		var t=[];

		t.push('movestate:');
		var m=this.moveState;
		t.push(' right '+m.moveright);
		t.push(' left '+m.moveleft);
		t.push(' down '+m.movedown);

		var dump=document.getElementById('dump').textContent=t.join("\n");
	},
	/**
	 * Finds rows full of boxes.
	 *
	 * @return void
	 */
	findFinishedRows: function() {
		var i, j, found, b=this.boxes, finished=[];
		for(i=0; i<this.rows; i++) {
			found=true;
			for(j=0; j<this.cols; j++) {
				if(b[i][j]===undefined) {
					found=false;
					break;
				}
			}

			if(found) {
				finished.push(i);
			}
		}

		if(finished.length>0) {
			this.finishedRowsAnimation=1;
		}

		this.finishedRows=finished;
	},
	/**
	 * Removes finished rows
	 *
	 * @return void
	 */
	removeFinishedRows: function() {
		var fr=this.finishedRows;
		var n=fr.length;
		for(var i=n-1; i>=0; i--) {
			this.boxes.splice(fr[i], 1);
		}

		for(var i=0; i<n; i++) {
			this.boxes.unshift(new Array(this.cols));
		}

		this.finishedRows=[];
		this.finishedRowsAnimation=0;
	},
	/**
	 * Renders the current game state.
	 *
	 * @return void
	 */
	render: function() {
		var b=this.boxes, x, y, s=this.size;
		this.canvas.
			clear().
			statefulPath(0, 0).
			setState('fillStyle', 'black').
			rect(this.cols*s, this.rows*s).
			draw();

		var path=this.canvas.
			overwriteState('strokeStyle', '#333').
			path(0, 0);

		for(x=1; x<this.cols; x++) {
			path.
				moveTo(x*s, 0).
				lineTo(x*s, this.rows*s);
		}
		for(y=1; y<this.rows; y++) {
			path.
				moveTo(0, y*s).
				lineTo(this.cols*s, y*s);
		}

		path.draw(false, true);

		// Render static boxes
		for(y=0; y<this.rows; y++) {
			for(x=0; x<this.cols; x++) {
				if(b[y][x]) {
					this.renderBox(x, y, b[y][x], this.canvas);
				}
			}
		}

		// Render current element
		var el=this.curEl;
		if(el) {
			for(y=0; y<el.h; y++) {
				for(x=0; x<el.w; x++) {
					if(!el.boxes[y][x]) {
						continue;
					}
					this.renderBox(x+el.x, y+el.y, el.color, this.canvas);
				}
			}
		}

		if(this.finishedRowsAnimation>0) {
			var fr=this.finishedRows;
			var color=this.finishedRowsAnimation%2==1 ? 'rgba(255,0,0,0.5)' : 'rgba(0,0,255,0.5)';

			for(var i=0, j; i<fr.length; i++) {
				for(j=0; j<this.cols; j++) {
					this.renderBox(j, fr[i], color, this.canvas);
				}
			}

			this.finishedRowsAnimation++;
		}
	},
	/**
	 * Renders a single box.
	 *
	 * @return void
	 * @param integer x position of the box
	 * @param integer y position of the box
	 * @param string the color to use
	 * @param Curly.Canvas the canvas context
	 */
	renderBox: function(x, y, color, canvas) {
		var s=this.size;
		var xs=x*s, ys=y*s;

		var path=canvas.statefulPath(xs, ys);
		path.setState('fillStyle', color).
			rect(s, s).
		// left light
			moveTo(xs, ys).
			setState('alpha', 0.6).
			setState('fillStyle', 'white').
			rect(1, s).
		// top light
			moveTo(xs, ys).
			rect(s, 1).
		// right shadow
			moveTo(xs+s-1, ys).
			setState('alpha', 0.6).
			setState('fillStyle', 'black').
			rect(1, s).
		// bottom shadow
			moveTo(xs, ys+s-1).
			rect(s, 1).
		// draw
			draw(true, false);
		canvas.overwriteState(Curly.Canvas.State.DEFAULTS);
	},
	/**
	 * Creates a new random tetris box element.
	 *
	 * @return void
	 */
	createRandEl: function() {
		var el=Math.floor(Math.random()*this.elCtrs.length);
		var c=Math.floor(Math.random()*this.colors.length);
		el=new this.elCtrs[el]();
		el.game=this;
		el.color=this.colors[c];
		console.log('create el: '+el.name+' '+c);
		return el;
	}
});

Tetris.LOOPSTATE_INIT=0;
Tetris.LOOPSTATE_MOVE=1;
Tetris.LOOPSTATES=2;<|endoftext|>


<commit_before>Persist = (function() {
  var VERSION = '0.1.0', P, B, esc, init, empty, ec;

  // easycookie 0.2.1 (http://pablotron.org/software/easy_cookie/)
  ec = (function(){var EPOCH='Thu, 01-Jan-1970 00:00:01 GMT',RATIO=1000*60*60*24,KEYS=['expires','path','domain'],esc=escape,un=unescape,doc=document,me;var get_now=function(){var r=new Date();r.setTime(r.getTime());return r;}
var cookify=function(c_key,c_val){var i,key,val,r=[],opt=(arguments.length>2)?arguments[2]:{};r.push(esc(c_key)+'='+esc(c_val));for(i=0;i<KEYS.length;i++){key=KEYS[i];if(val=opt[key])
r.push(key+'='+val);}
if(opt.secure)
r.push('secure');return r.join('; ');}
var alive=function(){var k='__EC_TEST__',v=new Date();v=v.toGMTString();this.set(k,v);this.enabled=(this.remove(k)==v);return this.enabled;}
me={set:function(key,val){var opt=(arguments.length>2)?arguments[2]:{},now=get_now(),expire_at,cfg={};if(opt.expires){opt.expires*=RATIO;cfg.expires=new Date(now.getTime()+opt.expires);cfg.expires=cfg.expires.toGMTString();}
var keys=['path','domain','secure'];for(i=0;i<keys.length;i++)
if(opt[keys[i]])
cfg[keys[i]]=opt[keys[i]];var r=cookify(key,val,cfg);doc.cookie=r;return val;},has:function(key){key=esc(key);var c=doc.cookie,ofs=c.indexOf(key+'='),len=ofs+key.length+1,sub=c.substring(0,key.length);return((!ofs&&key!=sub)||ofs<0)?false:true;},get:function(key){key=esc(key);var c=doc.cookie,ofs=c.indexOf(key+'='),len=ofs+key.length+1,sub=c.substring(0,key.length),end;if((!ofs&&key!=sub)||ofs<0)
return null;end=c.indexOf(';',len);if(end<0)
end=c.length;return un(c.substring(len,end));},remove:function(k){var r=me.get(k),opt={expires:EPOCH};doc.cookie=cookify(k,'',opt);return r;},keys:function(){var c=doc.cookie,ps=c.split('; '),i,p,r=[];for(i=0;i<ps.length;i++){p=ps[i].split('=');r.push(un(p[0]));}
return r;},all:function(){var c=doc.cookie,ps=c.split('; '),i,p,r=[];for(i=0;i<ps.length;i++){p=ps[i].split('=');r.push([un(p[0]),un(p[1])]);}
return r;},version:'0.2.1',enabled:false};me.enabled=alive.call(me);return me;}());

  // empty function
  empty = function() { };

  // escape spaces in name
  esc = function(str) {
    return 'PS' + str.replace(/_/g, '__').replace(/ /g, '_s');
  }

  B = {
    /*
     * Backend search order.
     *
     * Note that the search order is significant; the backends are
     * listed in order of capacity, and many browsers
     * support multiple backends, so changing the search order could
     * result in a browser choosing a less capable backend.
     */
    search_order: [
      // TODO: flash, gears, whatwg localStorage
      'db',
      'dom',
      'ie',
      'cookie'
    ],

    // list of backend methods
    methods: [
      'init',
      'get',
      'set',
      'remove',
      'load',
      'save'
    ],

    // whatwg db backend (Safari 3.1+)
    db: {
      size:   200 * 1024,

      sql: {
        create: "CREATE TABLE persist_data (k TEXT UNIQUE, v TEXT)",
        get:    "SELECT v FROM persist_data WHERE k = ?",
        set:    "INSERT INTO persist_data(k, v) VALUES (?, ?)",
        remove: "DELETE FROM persist_data WHERE k = ?"
      },

      test: function() {
        // test for openDatabase
        if (!window.openDatabase)
          return false;

        // make sure openDatabase works
        // XXX: will this leak a db handle and/or waste space?
        if (!window.openDatabase('PersistJS Test', '', '', B.db.size))
          return false;

        return true;
      },

      methods: {
        transaction: function(fn) {
          if (!this.db_created) {
            var sql = B.db.sql.create;

            this.db.transaction(function(t) {
              // create table
              t.executeSql(sql, [], function() {
                this.db_created = true;
              });
            });
          }

          this.db.transaction(fn);
        },

        init: function() {
          var desc, size;

          // init description and size
          desc = this.o.description || "Persistent storage for " + this.name;
          size = this.o.size || B.db.size;

          // create database handle
          this.db = openDatabase(this.name, this.o.version || '', desc, size);
        },

        get: function(key, fn, scope) {
          var sql = B.db.sql.get;

          // if callback isn't defined, then return
          if (!fn)
            return;

          // get callback scope
          scope = scope || this;

          // begin transaction
          this.transaction(function (t) {
            t.executeSql(sql, [key], function(r) {
              if (r.rows.length > 0)
                fn.call(scope, true, r.rows.item(0)['v']);
              else
                fn.call(scope, false, null);
            });
          });
        },

        set: function(key, val, fn, scope) {
          var rm_sql = B.db.sql.remove,
              sql    = B.db.sql.set;
          scope = scope || this;

          // begin set transaction
          this.transaction(function(t) {
            // exec remove query
            t.executeSql(rm_sql, [key], function() {
              // exec set query
              t.executeSql(sql, [key, val], function(r) {
                // run callback
                if (fn)
                  fn.call(scope, true, val);
              });
            });
          });

          return val;
        },

        // begin remove transaction
        remove: function(key, fn, scope) {
          var get_sql = this.sql.get;
              sql = this.sql.remove;

          this.transaction(function(t) {
            // if a callback was defined, then get the old
            // value before removing it
            if (fn) {
              // exec get query
              t.executeSql(get_sql, [key], function(r) {
                if (r.rows.length > 0) {
                  // key exists, get value
                  var val = r.rows.item(0)['v'];

                  // exec remove query
                  t.executeSql(sql, [key], function(r) {
                    // exec callback
                    fn.call(scope, true, val);
                  });
                } else {
                  // key does not exist, exec callback
                  fn.call(scope, false, null);
                }
              });
            } else {
              // no callback was defined, so just remove the
              // data without checking the old value

              // exec remove query
              t.executeSql(sql, [key]);
            }
          });
        }
      }
    },

    // dom backend (globalStorage, FF2+, IE8+)
    dom: {
      test: function() {
        return window.globalStorage ? true : false;
      },

      methods: {
        key: function(key) {
          return esc(this.name) + esc(key);
        },

        get: function(key, fn, scope) {
          // expand key and get scope
          key = this.key(key);
          scope = scope || this;

          if (fn)
            fn.call(scope, true, globalStorage[this.o.domain][key]);
        },

        set: function(key, val, fn, scope) {
          // expand key and get scope
          key = this.key(key);
          scope = scope || this;

          globalStorage[this.o.domain][key] = val;

          if (fn)
            fn.call(scope, true, val);
        },

        remove: function(key, fn, scope) {
          var val;

          // expand key and get scope
          key = this.key(key);
          scope = scope || this;

          // get old value
          val = globalStorage[this.o.domain][key];
          delete globalStorage[this.o.domain][key];

          if (fn)
            fn.call(scope, (val !== null), val);
        }
      }
    },

    // IE backend
    ie: {
      prefix: '_persist_data-',
      style: 'display:none; behavior:url(#default#userdata)',

      test: function() {
        // TODO: test load/save
        return document.attachEvent ? true : false;
      },

      methods: {
        init: function() {
          var id = B.ie.prefix + esc(this.name);

          // lazy-load userdata element
          var el = document.createElement('div');
          el.setAttribute('id', id);
          el.setAttribute('className', 'userData');
          el.setAttribute('style', B.ie.style);

          // append element to body
          document.body.appendChild(el);

          // save element and load data
          this.el = el;
          this.load();
        },

        load: function() {
          if (this.o.defer)
            this.el.load(esc(this.name));
        },

        get: function(key, fn, scope) {
          var val;

          // expand key
          key = esc(key);

          // load data
          if (!this.o.defer)
            this.el.load(esc(this.name));

          // get value
          val = this.el.getAttribute(key);

          // call fn
          if (fn)
            fn.call(scope || this, true, val);
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = esc(key);

          // set attribute
          this.el.setAttribute(key, val);
          if (!this.o.defer)
            this.el.save(esc(this.name));

          if (fn)
            fn.call(scope || this, true, val);
        },

        save: function() {
          if (this.o.defer) {
            // flush changes
            this.el.save(esc(this.name));
          }
        }
      }
    },

    // cookie backend
    cookie: {
      delim: ':',

      test: function() {
        // XXX: use easycookie to test if cookies are enabled
        return P.Cookie.enabled ? true : false;
      },

      methods: {
        key: function(key) {
          return this.name + B.cookie.delim + key;
        },

        get: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // get value
          val = ec.get(key);

          // call fn
          if (fn)
            fn.call(scope || this, val != null, val);
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // save value
          ec.set(key, val, this.o);

          // call fn
          if (fn)
            fn.call(scope || this, true, val);
        },

        remove: function(key, val, fn, scope) {
          var val;

          // expand key
          key = this.key(key);

          // remove cookie
          val = ec.remove(key)

          // call fn
          if (fn)
            fn.call(scope || this, val != null, val);
        }
      }
    }
  };

  // init function
  var init = function() {
    var i, l, b, key, fns = B.methods, keys = B.search_order;

    // set all functions to the empty function
    for (i = 0, l = fns.length; i < l; i++)
      P.Store.prototype[fns[i]] = empty;

    // clear type
    P.type = null;

    // loop over all backends and test for each one
    for (i = 0, l = keys.length; !P.type && i < l; i++) {
      b = B[keys[i]];

      // test for backend
      if (b.test()) {
        // found backend, save type
        P.type = keys[i];

        // extend store prototype with backend methods
        for (key in b.methods)
          P.Store.prototype[key] = b.methods[key];
      }
    }
  };

  // create top-level namespace
  P = {
    // version of persist library
    VERSION: VERSION,

    // backend type
    type: null,

    add: function(o) {
      // add to backend hash
      B[o.id] = o;

      // add backend to front of search order
      B.search_order = [o.id].concat(B.search_order);

      // re-initialize library
      init();
    },

    remove: function(id) {
      var ofs = B.search_order.indexOf(id);
      if (ofs < 0)
        return;

      // remove from search order
      B.search_order.splice(ofs, 1);

      // delete from lut
      delete B[id];

      // re-initialize library
      init();
    },

    // expose easycookie API
    Cookie: ec,

    // store API
    Store: function(name, o) {
      if (!P.type)
        throw new Error("No suitable storage found");

      o = o || {};
      this.name = name;

      // XXx: does this work?
      o.domain = o.domain || location.hostname || 'localdomain';
      this.o = o;

      // expires in 2 years
      o.expires = o.expires || 365 * 2;

      // call init function
      this.init();
    }
  };

  // init perssist and return top-level namespace
  init();
  return P;
})();
<commit_msg>use cssText instead of style<commit_after>Persist = (function() {
  var VERSION = '0.1.0', P, B, esc, init, empty, ec;

  // easycookie 0.2.1 (http://pablotron.org/software/easy_cookie/)
  ec = (function(){var EPOCH='Thu, 01-Jan-1970 00:00:01 GMT',RATIO=1000*60*60*24,KEYS=['expires','path','domain'],esc=escape,un=unescape,doc=document,me;var get_now=function(){var r=new Date();r.setTime(r.getTime());return r;}
var cookify=function(c_key,c_val){var i,key,val,r=[],opt=(arguments.length>2)?arguments[2]:{};r.push(esc(c_key)+'='+esc(c_val));for(i=0;i<KEYS.length;i++){key=KEYS[i];if(val=opt[key])
r.push(key+'='+val);}
if(opt.secure)
r.push('secure');return r.join('; ');}
var alive=function(){var k='__EC_TEST__',v=new Date();v=v.toGMTString();this.set(k,v);this.enabled=(this.remove(k)==v);return this.enabled;}
me={set:function(key,val){var opt=(arguments.length>2)?arguments[2]:{},now=get_now(),expire_at,cfg={};if(opt.expires){opt.expires*=RATIO;cfg.expires=new Date(now.getTime()+opt.expires);cfg.expires=cfg.expires.toGMTString();}
var keys=['path','domain','secure'];for(i=0;i<keys.length;i++)
if(opt[keys[i]])
cfg[keys[i]]=opt[keys[i]];var r=cookify(key,val,cfg);doc.cookie=r;return val;},has:function(key){key=esc(key);var c=doc.cookie,ofs=c.indexOf(key+'='),len=ofs+key.length+1,sub=c.substring(0,key.length);return((!ofs&&key!=sub)||ofs<0)?false:true;},get:function(key){key=esc(key);var c=doc.cookie,ofs=c.indexOf(key+'='),len=ofs+key.length+1,sub=c.substring(0,key.length),end;if((!ofs&&key!=sub)||ofs<0)
return null;end=c.indexOf(';',len);if(end<0)
end=c.length;return un(c.substring(len,end));},remove:function(k){var r=me.get(k),opt={expires:EPOCH};doc.cookie=cookify(k,'',opt);return r;},keys:function(){var c=doc.cookie,ps=c.split('; '),i,p,r=[];for(i=0;i<ps.length;i++){p=ps[i].split('=');r.push(un(p[0]));}
return r;},all:function(){var c=doc.cookie,ps=c.split('; '),i,p,r=[];for(i=0;i<ps.length;i++){p=ps[i].split('=');r.push([un(p[0]),un(p[1])]);}
return r;},version:'0.2.1',enabled:false};me.enabled=alive.call(me);return me;}());

  // empty function
  empty = function() { };

  // escape spaces in name
  esc = function(str) {
    return 'PS' + str.replace(/_/g, '__').replace(/ /g, '_s');
  }

  B = {
    /*
     * Backend search order.
     *
     * Note that the search order is significant; the backends are
     * listed in order of capacity, and many browsers
     * support multiple backends, so changing the search order could
     * result in a browser choosing a less capable backend.
     */
    search_order: [
      // TODO: flash, gears, whatwg localStorage
      'db',
      'dom',
      'ie',
      'cookie'
    ],

    // list of backend methods
    methods: [
      'init',
      'get',
      'set',
      'remove',
      'load',
      'save'
    ],

    // whatwg db backend (Safari 3.1+)
    db: {
      size:   200 * 1024,

      sql: {
        create: "CREATE TABLE persist_data (k TEXT UNIQUE, v TEXT)",
        get:    "SELECT v FROM persist_data WHERE k = ?",
        set:    "INSERT INTO persist_data(k, v) VALUES (?, ?)",
        remove: "DELETE FROM persist_data WHERE k = ?"
      },

      test: function() {
        // test for openDatabase
        if (!window.openDatabase)
          return false;

        // make sure openDatabase works
        // XXX: will this leak a db handle and/or waste space?
        if (!window.openDatabase('PersistJS Test', '', '', B.db.size))
          return false;

        return true;
      },

      methods: {
        transaction: function(fn) {
          if (!this.db_created) {
            var sql = B.db.sql.create;

            this.db.transaction(function(t) {
              // create table
              t.executeSql(sql, [], function() {
                this.db_created = true;
              });
            });
          }

          this.db.transaction(fn);
        },

        init: function() {
          var desc, size;

          // init description and size
          desc = this.o.description || "Persistent storage for " + this.name;
          size = this.o.size || B.db.size;

          // create database handle
          this.db = openDatabase(this.name, this.o.version || '', desc, size);
        },

        get: function(key, fn, scope) {
          var sql = B.db.sql.get;

          // if callback isn't defined, then return
          if (!fn)
            return;

          // get callback scope
          scope = scope || this;

          // begin transaction
          this.transaction(function (t) {
            t.executeSql(sql, [key], function(r) {
              if (r.rows.length > 0)
                fn.call(scope, true, r.rows.item(0)['v']);
              else
                fn.call(scope, false, null);
            });
          });
        },

        set: function(key, val, fn, scope) {
          var rm_sql = B.db.sql.remove,
              sql    = B.db.sql.set;
          scope = scope || this;

          // begin set transaction
          this.transaction(function(t) {
            // exec remove query
            t.executeSql(rm_sql, [key], function() {
              // exec set query
              t.executeSql(sql, [key, val], function(r) {
                // run callback
                if (fn)
                  fn.call(scope, true, val);
              });
            });
          });

          return val;
        },

        // begin remove transaction
        remove: function(key, fn, scope) {
          var get_sql = this.sql.get;
              sql = this.sql.remove;

          this.transaction(function(t) {
            // if a callback was defined, then get the old
            // value before removing it
            if (fn) {
              // exec get query
              t.executeSql(get_sql, [key], function(r) {
                if (r.rows.length > 0) {
                  // key exists, get value
                  var val = r.rows.item(0)['v'];

                  // exec remove query
                  t.executeSql(sql, [key], function(r) {
                    // exec callback
                    fn.call(scope, true, val);
                  });
                } else {
                  // key does not exist, exec callback
                  fn.call(scope, false, null);
                }
              });
            } else {
              // no callback was defined, so just remove the
              // data without checking the old value

              // exec remove query
              t.executeSql(sql, [key]);
            }
          });
        }
      }
    },

    // dom backend (globalStorage, FF2+, IE8+)
    dom: {
      test: function() {
        return window.globalStorage ? true : false;
      },

      methods: {
        key: function(key) {
          return esc(this.name) + esc(key);
        },

        get: function(key, fn, scope) {
          // expand key and get scope
          key = this.key(key);
          scope = scope || this;

          if (fn)
            fn.call(scope, true, globalStorage[this.o.domain][key]);
        },

        set: function(key, val, fn, scope) {
          // expand key and get scope
          key = this.key(key);
          scope = scope || this;

          globalStorage[this.o.domain][key] = val;

          if (fn)
            fn.call(scope, true, val);
        },

        remove: function(key, fn, scope) {
          var val;

          // expand key and get scope
          key = this.key(key);
          scope = scope || this;

          // get old value
          val = globalStorage[this.o.domain][key];
          delete globalStorage[this.o.domain][key];

          if (fn)
            fn.call(scope, (val !== null), val);
        }
      }
    },

    // IE backend
    ie: {
      prefix: '_persist_data-',
      style: 'display:none; behavior:url(#default#userdata);',

      test: function() {
        // TODO: test load/save
        return document.attachEvent ? true : false;
      },

      methods: {
        init: function() {
          var id = B.ie.prefix + esc(this.name);

          // lazy-load userdata element
          var el = document.createElement('div');
          el.setAttribute('id', id);
          el.setAttribute('className', 'userData');
          el.style.setAttribute('cssText', B.ie.style);

          // append element to body
          document.body.appendChild(el);

          // save element and load data
          this.el = el;
          this.load();
        },

        load: function() {
          if (this.o.defer)
            this.el.load(esc(this.name));
        },

        get: function(key, fn, scope) {
          var val;

          // expand key
          key = esc(key);

          // load data
          if (!this.o.defer)
            this.el.load(esc(this.name));

          // get value
          val = this.el.getAttribute(key);

          // call fn
          if (fn)
            fn.call(scope || this, true, val);
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = esc(key);

          // set attribute
          this.el.setAttribute(key, val);
          if (!this.o.defer)
            this.el.save(esc(this.name));

          if (fn)
            fn.call(scope || this, true, val);
        },

        save: function() {
          if (this.o.defer) {
            // flush changes
            this.el.save(esc(this.name));
          }
        }
      }
    },

    // cookie backend
    cookie: {
      delim: ':',

      test: function() {
        // XXX: use easycookie to test if cookies are enabled
        return P.Cookie.enabled ? true : false;
      },

      methods: {
        key: function(key) {
          return this.name + B.cookie.delim + key;
        },

        get: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // get value
          val = ec.get(key);

          // call fn
          if (fn)
            fn.call(scope || this, val != null, val);
        },

        set: function(key, val, fn, scope) {
          // expand key
          key = this.key(key);

          // save value
          ec.set(key, val, this.o);

          // call fn
          if (fn)
            fn.call(scope || this, true, val);
        },

        remove: function(key, val, fn, scope) {
          var val;

          // expand key
          key = this.key(key);

          // remove cookie
          val = ec.remove(key)

          // call fn
          if (fn)
            fn.call(scope || this, val != null, val);
        }
      }
    }
  };

  // init function
  var init = function() {
    var i, l, b, key, fns = B.methods, keys = B.search_order;

    // set all functions to the empty function
    for (i = 0, l = fns.length; i < l; i++)
      P.Store.prototype[fns[i]] = empty;

    // clear type
    P.type = null;

    // loop over all backends and test for each one
    for (i = 0, l = keys.length; !P.type && i < l; i++) {
      b = B[keys[i]];

      // test for backend
      if (b.test()) {
        // found backend, save type
        P.type = keys[i];

        // extend store prototype with backend methods
        for (key in b.methods)
          P.Store.prototype[key] = b.methods[key];
      }
    }
  };

  // create top-level namespace
  P = {
    // version of persist library
    VERSION: VERSION,

    // backend type
    type: null,

    add: function(o) {
      // add to backend hash
      B[o.id] = o;

      // add backend to front of search order
      B.search_order = [o.id].concat(B.search_order);

      // re-initialize library
      init();
    },

    remove: function(id) {
      var ofs = B.search_order.indexOf(id);
      if (ofs < 0)
        return;

      // remove from search order
      B.search_order.splice(ofs, 1);

      // delete from lut
      delete B[id];

      // re-initialize library
      init();
    },

    // expose easycookie API
    Cookie: ec,

    // store API
    Store: function(name, o) {
      if (!P.type)
        throw new Error("No suitable storage found");

      o = o || {};
      this.name = name;

      // XXx: does this work?
      o.domain = o.domain || location.hostname || 'localdomain';
      this.o = o;

      // expires in 2 years
      o.expires = o.expires || 365 * 2;

      // call init function
      this.init();
    }
  };

  // init perssist and return top-level namespace
  init();
  return P;
})();
<|endoftext|>


<commit_before>var Hapi = require('hapi');
var Path = require('path');
var routes = require('./lib/routes');

// Create server
var server = new Hapi.Server({
  connections: {
    router: { stripTrailingSlash: true },
    routes: {
      security: {
        xframe: {
          rule: 'allow-from',
          source: 'https://optimizely.com'
        }
      },
      files: { relativeTo: Path.join(__dirname, 'static') }
    }
  }
});

server.connection({
  port: process.env.PORT || 8000
});

// Route handlers
routes(server);

// Start listening
server.start(function () {
  console.log('Server running at:', server.info.uri);
});
<commit_msg>Bypass the x-frame header<commit_after>var Hapi = require('hapi');
var Path = require('path');
var routes = require('./lib/routes');

// Create server
var server = new Hapi.Server({
  connections: {
    router: { stripTrailingSlash: true },
    routes: {
      security: {
        xss: true,
        noOpen: true,
        noSniff: true
      },
      files: { relativeTo: Path.join(__dirname, 'static') }
    }
  }
});

server.connection({
  port: process.env.PORT || 8000
});

// Route handlers
routes(server);

// Start listening
server.start(function () {
  console.log('Server running at:', server.info.uri);
});
<|endoftext|>