<commit_before>'use strict';

(function() {
  window.diana = window.diana || {};
  window.diana.views = window.diana.views || {};
  var diana = window.diana;

  /**
   * Displays the <table> into which result sets are rendered. Automatically
   * fetches the result set based on router options passed via initialize().
   */
  diana.views.Timeline = Backbone.View.extend({
    // Track the most recent event ID seen by initial fetch() and automatic updates.
    newestEventId: null,

    // socket.io connection.
    socket: null,

    // Modal sub-views.
    searchView: null,

    initialize: function(options) {
      var view = this;

      this.initPrefs('Timeline', {
        autoUpdate: true,
        rowLimit: 100
      });

      this.initKeyEvents({
        16: function (event) { // SHIFT + U
          if (event.shiftKey) {
            this.toggleUpdates(event);
          }
        },
        76: function(event) { // L
          this.editRowLimit(event);
        },
        83: function(event) { // S
          this.openSearch(event);
        }
      });

      $.when(
        diana.helpers.View.deferRender(
          'timeline_table',
          null,
          function(err, out) {
            view.$el.html(out);
          }
        )
      ).done(function() {
        view.renderUpdateSensitive();
        view.fetchTimeline.call(view, view.renderTimeline);
      });

      // Suspend key event handling when sub-view modals are open.
      diana.helpers.Event.on('ModalOpen', function() {
        view.disableKeyEvents();
      });
      diana.helpers.Event.on('ModalClose', function() {
        view.enableKeyEvents();
      });
    },

    events: {
      'click #timeline-open-search': 'openSearch',
      'click #timeline-toggle-updates': 'toggleUpdates',
      'click #timeline-edit-rowlimit': 'editRowLimit'
    },

    onClose: function() {
      $(document).off('keyup', this.onKey);
      this.closeSocket();
    },

    /**
     * Open search modal.
     *
     * @param event {Object} jQuery event object.
     */
    openSearch: function(event) {
      diana.helpers.Widget.closeDropdown(event);

      var modal = $('#timeline-search-modal');

      if (modal.is(':visible')) {
        return;
      }

      if (this.searchView) {
        modal.modal('show');
      } else {
        this.searchView = new diana.views.TimelineSearch({
          el: modal,
          searchArgs: this.options.searchArgs
        });
      }
    },

    /**
     * Toggle automatic updates.
     *
     * @param event {Object} jQuery event object.
     */
    toggleUpdates: function(event) {
      diana.helpers.Widget.closeDropdown(event);
      this.setPref('autoUpdate', !this.getPref('autoUpdate'));

      if (this.prefs.autoUpdate) {
        this.startTimelineUpdate(this.newestEventId);
      } else {
        this.closeSocket();
      }

      diana.helpers.Widget.alert(
        'Updates ' + (this.prefs.autoUpdate ? 'Enabled' : 'Disabled'),
        'info',
        3
      );

      this.renderUpdateSensitive();
    },

    /**
     * Open row-limit edit modal.
     *
     * @param event {Object} jQuery event object.
     */
    editRowLimit: function(event) {
      var view = this;
      diana.helpers.Widget.closeDropdown(event);

      this.rowLimitView = new diana.views.EditSingleValue({
        default: this.prefs.rowLimit,
        help: 'Event count. Only enforced when updates are enabled.',
        placeholder: "20-100",
        title: 'Limit Size',
        onEdit: function(value) {
          view.setPref('rowLimit', parseInt(value, 10));
          view.truncateRows();
        }
      });
    },

    /**
     * Truncate table rows based on preferred limit.
     */
    truncateRows: function() {
      if (this.prefs.autoUpdate) {
        this.$('tr').slice(1 + this.prefs.rowLimit).remove();
      }
    },

    /**
     * Close auto-update socket.
     */
    closeSocket: function() {
      if (this.socket) {
        this.socket.disconnect();
        this.socket = null;
      }
    },

    /**
     * Fetch all log events matching the search arguments passed to the view.
     *
     * @param callback {Function} Fires on success/error.
     * - Receives an array with zero or more event objects.
     */
    fetchTimeline: function(callback) {
      var view = this;

      // Supply search filters/options for the collection's URL generation.
      view.collection = new diana.collections.Timeline(
        null, {searchArgs: view.options.searchArgs}
      );

      view.collection.fetch({
        success: function(collection, response) {
          if (!response.results.length) {
            dust.render('timeline_no_results', null, function(err, out) {
              view.$el.html(out);
            });
            return;
          }

          // Reveal and configure pagination links.
          var searchArgs = _.clone(view.options.searchArgs);
          var skip = parseInt(searchArgs.skip || '0', 10); // Use '0' to avoid NaN.
          var pageSize = Math.min(searchArgs.limit, diana.maxResultSize);
          if (response.info.prevPage) {
            searchArgs = _.clone(view.options.searchArgs);
            searchArgs.skip = Math.max(0, parseInt(skip, 10) - pageSize);
            if (!searchArgs.skip) {
              delete searchArgs.skip;
            }
            $('.prev-page')
              .removeClass('disabled')
              .attr('href', view.buildUrl('timeline', searchArgs));
          }
          if (response.info.nextPage) {
            searchArgs = _.clone(view.options.searchArgs);
            searchArgs.skip = parseInt(skip, 10) + pageSize;
            $('.next-page')
              .removeClass('disabled')
              .attr('href', view.buildUrl('timeline', searchArgs));
          }
          callback.call(view, response.results);
        },

        error: function(collection, response) {
          diana.helpers.Event.trigger('CritFetchError', response);
          callback.call(view, []);
        }
      });
    },

    /**
     * Render a set of events.
     *
     * - Used for both the intiial fetch and socket.io update payloads.
     *
     * @param events {Array}
     * @param options {Object}
      * - prepend {Boolean} If true, row is prepended (default=false).
      * - highlight {Boolean} If true, row receives styled class (default=false).
     */
    renderTimeline: function(events, options) {
      if (!events.length) {
        return;
      }

      options = options || {
        prepend: false,
        highlight: false
      };

      var view = this;

      // This function is used for both initial fetch and automatic updates.
      // Initial <tbody> is built off-screen and appended as a whole.
      // Automatic updates' <tr> nodes are added individually to an existing <tbody>.
      var existingTbody = $('#timeline-table tbody');
      var tbody = (existingTbody.length ? existingTbody : null) || $('<tbody>');

      // Deferred dust.render() calls later executed by $.when().
      var renderPromises = [];

      // Add empty <tr> nodes to the off-screen or live <tbody>.
      _.each(events, function(event) {
        var tr = $('<tr>');
        if (options.prepend) {
          if (options.highlight) {
            tr.addClass('timeline-update');
          }
          tbody.prepend(tr);
        } else {
          tbody.append(tr);
        }
        renderPromises.push(view.renderEvent(event, tr));
      });

      $.when(renderPromises).done(function() {
        if (existingTbody.length) {
          // tbody was already part of the DOM, so above rows were added live.
        } else {
          $('#timeline-table').append(tbody);
          view.startTimelineUpdate.call(view, events[0]._id);
        }
      });
    },

    /**
     * Render a single event in the timeline table.
     *
     * @param event {Object}
     * @param tr {Object} <tr> parent element.
     * @return {Object} jQuery Promise.
     */
    renderEvent: function(event, tr) {
      event.relTime = moment(event.time * 1000).fromNow();
      event.intReferer = this.buildUrl('timeline', this.options.searchArgs);

      return diana.helpers.View.deferRender(
        'timeline_table_row',
        event,
        function(err, out) {
          $(tr).append(out);
        }
      );
    },

    /**
     * Modify text on update toggling link in drop-down.
     */
    renderUpdateSensitive: function() {
      $('#timeline-toggle-updates').text(
        (this.prefs.autoUpdate ? 'Disable' : 'Enable') + ' Updates'
      );

      if (this.prefs.autoUpdate) {
        $('#timeline-edit-rowlimit').parent().show();
      } else {
        $('#timeline-edit-rowlimit').parent().hide();
      }
    },

    /**
     * Creates the automatic updates socket. Also handles reconnection and diverts
     * update payloads.
     *
     * @param initialId {String} All updates (if any) will be newer than this ID.
     */
    startTimelineUpdate: function(initialId) {
      var view = this;
      view.newestEventId = initialId;

      if (!this.prefs.autoUpdate || !diana.features.timelineUpdate) {
        return;
      }

      this.socket = diana.helpers.Socket.create({
        event: {
          connect: function() {
            // Start/restart automatic updates.
            view.socket.emit('startTimelineUpdate', {
              newestEventId: view.newestEventId,
              searchArgs: view.options.searchArgs
            });
          }
        }
      });

      // Update the view with events fresher than newestEventId.
      this.socket.on('timelineUpdate', function(data) {
        view.onTimelineUpdate.call(view, data);
      });
    },

    /**
     * Fires on received responses from the automatic updates socket.
     *
     * @param data {Array|Object} List of event objects on success.
     * - On error, __socket_error string is set.
     */
    onTimelineUpdate: function(data) {
      if (!data || !data.length) {
        return;
      }

      // Advance the manual cursor.
      this.newestEventId = data[0]._id;

      // Un-highlight any past updates.
      $('.timeline-update').removeClass('timeline-update');

      // Update relative dates.
      this.$('td:first-child a').each(function() {
        var a = $(this);
        a.text(moment(a.data('time') * 1000).fromNow());
      });

      this.renderTimeline(data.reverse(), {prepend: true, highlight: true});

      this.truncateRows();
    }
  });
})();
<commit_msg>Fix: Page size calculated incorrectly when no limit was requested.<commit_after>'use strict';

(function() {
  window.diana = window.diana || {};
  window.diana.views = window.diana.views || {};
  var diana = window.diana;

  /**
   * Displays the <table> into which result sets are rendered. Automatically
   * fetches the result set based on router options passed via initialize().
   */
  diana.views.Timeline = Backbone.View.extend({
    // Track the most recent event ID seen by initial fetch() and automatic updates.
    newestEventId: null,

    // socket.io connection.
    socket: null,

    // Modal sub-views.
    searchView: null,

    initialize: function(options) {
      var view = this;

      this.initPrefs('Timeline', {
        autoUpdate: true,
        rowLimit: 100
      });

      this.initKeyEvents({
        16: function (event) { // SHIFT + U
          if (event.shiftKey) {
            this.toggleUpdates(event);
          }
        },
        76: function(event) { // L
          this.editRowLimit(event);
        },
        83: function(event) { // S
          this.openSearch(event);
        }
      });

      $.when(
        diana.helpers.View.deferRender(
          'timeline_table',
          null,
          function(err, out) {
            view.$el.html(out);
          }
        )
      ).done(function() {
        view.renderUpdateSensitive();
        view.fetchTimeline.call(view, view.renderTimeline);
      });

      // Suspend key event handling when sub-view modals are open.
      diana.helpers.Event.on('ModalOpen', function() {
        view.disableKeyEvents();
      });
      diana.helpers.Event.on('ModalClose', function() {
        view.enableKeyEvents();
      });
    },

    events: {
      'click #timeline-open-search': 'openSearch',
      'click #timeline-toggle-updates': 'toggleUpdates',
      'click #timeline-edit-rowlimit': 'editRowLimit'
    },

    onClose: function() {
      $(document).off('keyup', this.onKey);
      this.closeSocket();
    },

    /**
     * Open search modal.
     *
     * @param event {Object} jQuery event object.
     */
    openSearch: function(event) {
      diana.helpers.Widget.closeDropdown(event);

      var modal = $('#timeline-search-modal');

      if (modal.is(':visible')) {
        return;
      }

      if (this.searchView) {
        modal.modal('show');
      } else {
        this.searchView = new diana.views.TimelineSearch({
          el: modal,
          searchArgs: this.options.searchArgs
        });
      }
    },

    /**
     * Toggle automatic updates.
     *
     * @param event {Object} jQuery event object.
     */
    toggleUpdates: function(event) {
      diana.helpers.Widget.closeDropdown(event);
      this.setPref('autoUpdate', !this.getPref('autoUpdate'));

      if (this.prefs.autoUpdate) {
        this.startTimelineUpdate(this.newestEventId);
      } else {
        this.closeSocket();
      }

      diana.helpers.Widget.alert(
        'Updates ' + (this.prefs.autoUpdate ? 'Enabled' : 'Disabled'),
        'info',
        3
      );

      this.renderUpdateSensitive();
    },

    /**
     * Open row-limit edit modal.
     *
     * @param event {Object} jQuery event object.
     */
    editRowLimit: function(event) {
      var view = this;
      diana.helpers.Widget.closeDropdown(event);

      this.rowLimitView = new diana.views.EditSingleValue({
        default: this.prefs.rowLimit,
        help: 'Event count. Only enforced when updates are enabled.',
        placeholder: "20-100",
        title: 'Limit Size',
        onEdit: function(value) {
          view.setPref('rowLimit', parseInt(value, 10));
          view.truncateRows();
        }
      });
    },

    /**
     * Truncate table rows based on preferred limit.
     */
    truncateRows: function() {
      if (this.prefs.autoUpdate) {
        this.$('tr').slice(1 + this.prefs.rowLimit).remove();
      }
    },

    /**
     * Close auto-update socket.
     */
    closeSocket: function() {
      if (this.socket) {
        this.socket.disconnect();
        this.socket = null;
      }
    },

    /**
     * Fetch all log events matching the search arguments passed to the view.
     *
     * @param callback {Function} Fires on success/error.
     * - Receives an array with zero or more event objects.
     */
    fetchTimeline: function(callback) {
      var view = this;

      // Supply search filters/options for the collection's URL generation.
      view.collection = new diana.collections.Timeline(
        null, {searchArgs: view.options.searchArgs}
      );

      view.collection.fetch({
        success: function(collection, response) {
          if (!response.results.length) {
            dust.render('timeline_no_results', null, function(err, out) {
              view.$el.html(out);
            });
            return;
          }

          // Reveal and configure pagination links.
          var searchArgs = _.clone(view.options.searchArgs);
          var skip = parseInt(searchArgs.skip || '0', 10); // Use '0' to avoid NaN.
          var limit = searchArgs.limit || '0', 10); // Use '0' to avoid NaN.
          var pageSize = limit ? Math.min(limit, diana.maxResultSize) : diana.maxResultSize;
          if (response.info.prevPage) {
            searchArgs = _.clone(view.options.searchArgs);
            searchArgs.skip = Math.max(0, parseInt(skip, 10) - pageSize);
            if (!searchArgs.skip) {
              delete searchArgs.skip;
            }
            $('.prev-page')
              .removeClass('disabled')
              .attr('href', view.buildUrl('timeline', searchArgs));
          }
          if (response.info.nextPage) {
            searchArgs = _.clone(view.options.searchArgs);
            searchArgs.skip = parseInt(skip, 10) + pageSize;
            $('.next-page')
              .removeClass('disabled')
              .attr('href', view.buildUrl('timeline', searchArgs));
          }
          callback.call(view, response.results);
        },

        error: function(collection, response) {
          diana.helpers.Event.trigger('CritFetchError', response);
          callback.call(view, []);
        }
      });
    },

    /**
     * Render a set of events.
     *
     * - Used for both the intiial fetch and socket.io update payloads.
     *
     * @param events {Array}
     * @param options {Object}
      * - prepend {Boolean} If true, row is prepended (default=false).
      * - highlight {Boolean} If true, row receives styled class (default=false).
     */
    renderTimeline: function(events, options) {
      if (!events.length) {
        return;
      }

      options = options || {
        prepend: false,
        highlight: false
      };

      var view = this;

      // This function is used for both initial fetch and automatic updates.
      // Initial <tbody> is built off-screen and appended as a whole.
      // Automatic updates' <tr> nodes are added individually to an existing <tbody>.
      var existingTbody = $('#timeline-table tbody');
      var tbody = (existingTbody.length ? existingTbody : null) || $('<tbody>');

      // Deferred dust.render() calls later executed by $.when().
      var renderPromises = [];

      // Add empty <tr> nodes to the off-screen or live <tbody>.
      _.each(events, function(event) {
        var tr = $('<tr>');
        if (options.prepend) {
          if (options.highlight) {
            tr.addClass('timeline-update');
          }
          tbody.prepend(tr);
        } else {
          tbody.append(tr);
        }
        renderPromises.push(view.renderEvent(event, tr));
      });

      $.when(renderPromises).done(function() {
        if (existingTbody.length) {
          // tbody was already part of the DOM, so above rows were added live.
        } else {
          $('#timeline-table').append(tbody);
          view.startTimelineUpdate.call(view, events[0]._id);
        }
      });
    },

    /**
     * Render a single event in the timeline table.
     *
     * @param event {Object}
     * @param tr {Object} <tr> parent element.
     * @return {Object} jQuery Promise.
     */
    renderEvent: function(event, tr) {
      event.relTime = moment(event.time * 1000).fromNow();
      event.intReferer = this.buildUrl('timeline', this.options.searchArgs);

      return diana.helpers.View.deferRender(
        'timeline_table_row',
        event,
        function(err, out) {
          $(tr).append(out);
        }
      );
    },

    /**
     * Modify text on update toggling link in drop-down.
     */
    renderUpdateSensitive: function() {
      $('#timeline-toggle-updates').text(
        (this.prefs.autoUpdate ? 'Disable' : 'Enable') + ' Updates'
      );

      if (this.prefs.autoUpdate) {
        $('#timeline-edit-rowlimit').parent().show();
      } else {
        $('#timeline-edit-rowlimit').parent().hide();
      }
    },

    /**
     * Creates the automatic updates socket. Also handles reconnection and diverts
     * update payloads.
     *
     * @param initialId {String} All updates (if any) will be newer than this ID.
     */
    startTimelineUpdate: function(initialId) {
      var view = this;
      view.newestEventId = initialId;

      if (!this.prefs.autoUpdate || !diana.features.timelineUpdate) {
        return;
      }

      this.socket = diana.helpers.Socket.create({
        event: {
          connect: function() {
            // Start/restart automatic updates.
            view.socket.emit('startTimelineUpdate', {
              newestEventId: view.newestEventId,
              searchArgs: view.options.searchArgs
            });
          }
        }
      });

      // Update the view with events fresher than newestEventId.
      this.socket.on('timelineUpdate', function(data) {
        view.onTimelineUpdate.call(view, data);
      });
    },

    /**
     * Fires on received responses from the automatic updates socket.
     *
     * @param data {Array|Object} List of event objects on success.
     * - On error, __socket_error string is set.
     */
    onTimelineUpdate: function(data) {
      if (!data || !data.length) {
        return;
      }

      // Advance the manual cursor.
      this.newestEventId = data[0]._id;

      // Un-highlight any past updates.
      $('.timeline-update').removeClass('timeline-update');

      // Update relative dates.
      this.$('td:first-child a').each(function() {
        var a = $(this);
        a.text(moment(a.data('time') * 1000).fromNow());
      });

      this.renderTimeline(data.reverse(), {prepend: true, highlight: true});

      this.truncateRows();
    }
  });
})();
<|endoftext|>


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

import style from './style.module.css'
import pageStyle from '../style.module.css'

export default function UsagePage() {
  return (

Firstly, when using paint worklets, we highly recommend you also use the CSS Paint Polyfill to ensure that all modern browsers can run these styles as well. Read on for instructions on implementing both worklets and the polyfill in your user interfaces.

Note: Houdini worklets must either be run via a server locally, or on HTTPS in production. In order to work with a Houdini worklet, you will need to either install it locally or use a content delivery network (CDN) like unpkg to serve the files.

There are three steps required to use a Houdini paint worklet:

Install the worklet to your file system (either via an npm module or linked through a CDN).

Add the worklet to your local CSS interface using CSS.paintWorklet.addModule().

Use the worklet in your CSS file.

Polyfill your site using the CSS Paint Polyfill

The following goes through some of the various ways to achieve the above steps. You can also find demos and guidance on how to use the worklets in your CSS file on the worklet page.

1. Install and 2. Add

Option 1: Install and register from npm

              (recommended for production)

Install your worklet from npm:

<code dangerouslySetInnerHTML={{ __html: `npm install <package-name>` }}>

            Note: The npm package name and link can be found on each worklet card.

Importing this package does not automatically inject the paint worklet. To install the worklet (step 2), you'll need to generate a URL that resolves to the package's worklet.js, and register that. You do so with:

<code dangerouslySetInnerHTML={{ __html: `CSS.paintWorklet.addModule(..file-path/worklet.js)`}}>

Option 2: Register directly from unpkg

When registering from unpkg, you can link directly to the worklet.js file without needing to locally install the worklet. Unpkg will resolve to the worklet.js as the main script, or you can specify it yourself. Unpkg will not cause CORS issues, as it is served over HTTPS.

Note that this does not register the custom properties for syntax and fallback values. Instead, they each have fallback values embedded into the worklet.

To optionally register the custom properties, include the properties.js file as well.

<code dangerouslySetInnerHTML={{ __html:
                `<script src="https://unpkg.com/<package-name>/properties.js"></script>`}}>

Installation in Bundlers

Here is an example of how to use Houdini in modern bundlers:

3. Use in CSS

Now you're ready to call the worklet by its name in your CSS file.

Most of the worklets showcased on this site have optional styling parameters which can be custimzed via CSS custom properties. These are denoted with a --. See the worklets page for more information on customizations and how to use the worklets in CSS.

4. Polyfill your site

As previously mentioned, when using the Paint worklet, you should also include the CSS Paint Polyfill.

</script>

// or with unpkg:
<script src="https://unpkg.com/css-paint-polyfill"></script>`}}>

With a bundler or ES modules:

<code dangerouslySetInnerHTML={{ __html:
          `import 'css-paint-polyfill';`}}>

  );
}
<commit_msg>change three to four steps<commit_after>/**
 * Copyright 2020 Google Inc. All Rights Reserved.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *     http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import style from './style.module.css'
import pageStyle from '../style.module.css'

export default function UsagePage() {
  return (

Firstly, when using paint worklets, we highly recommend you also use the CSS Paint Polyfill to ensure that all modern browsers can run these styles as well. Read on for instructions on implementing both worklets and the polyfill in your user interfaces.

Note: Houdini worklets must either be run via a server locally, or on HTTPS in production. In order to work with a Houdini worklet, you will need to either install it locally or use a content delivery network (CDN) like unpkg to serve the files.

There are four steps required to use a Houdini paint worklet:

Install the worklet to your file system (either via an npm module or linked through a CDN).

Add the worklet to your local CSS interface using CSS.paintWorklet.addModule().

Use the worklet in your CSS file.

Polyfill your site using the CSS Paint Polyfill

The following goes through some of the various ways to achieve the above steps. You can also find demos and guidance on how to use the worklets in your CSS file on the worklet page.

1. Install and 2. Add

Option 1: Install and register from npm

              (recommended for production)

Install your worklet from npm:

<code dangerouslySetInnerHTML={{ __html: `npm install <package-name>` }}>

            Note: The npm package name and link can be found on each worklet card.

Importing this package does not automatically inject the paint worklet. To install the worklet (step 2), you'll need to generate a URL that resolves to the package's worklet.js, and register that. You do so with:

<code dangerouslySetInnerHTML={{ __html: `CSS.paintWorklet.addModule(..file-path/worklet.js)`}}>

Option 2: Register directly from unpkg

When registering from unpkg, you can link directly to the worklet.js file without needing to locally install the worklet. Unpkg will resolve to the worklet.js as the main script, or you can specify it yourself. Unpkg will not cause CORS issues, as it is served over HTTPS.

Note that this does not register the custom properties for syntax and fallback values. Instead, they each have fallback values embedded into the worklet.

To optionally register the custom properties, include the properties.js file as well.

<code dangerouslySetInnerHTML={{ __html:
                `<script src="https://unpkg.com/<package-name>/properties.js"></script>`}}>

Installation in Bundlers

Here is an example of how to use Houdini in modern bundlers:

3. Use in CSS

Now you're ready to call the worklet by its name in your CSS file.

Most of the worklets showcased on this site have optional styling parameters which can be custimzed via CSS custom properties. These are denoted with a --. See the worklets page for more information on customizations and how to use the worklets in CSS.

4. Polyfill your site

As previously mentioned, when using the Paint worklet, you should also include the CSS Paint Polyfill.

</script>

// or with unpkg:
<script src="https://unpkg.com/css-paint-polyfill"></script>`}}>

With a bundler or ES modules:

<code dangerouslySetInnerHTML={{ __html:
          `import 'css-paint-polyfill';`}}>

  );
}
<|endoftext|>


<commit_before>

var addProductsHandler = function( currentUser ) {

  if( currentUser.adminStatus !== userAdminStates.ADMIN )
    return;

  var barcodeNumber = prompt( "Barcode Number: " );

  if( barcodeNumber == null ) {
    location.reload();
    return;
  }

  productPath = "/markup/products/" + barcodeNumber;
  productRef = ref.child( productPath );
  productRef.once( "value", function( productSnap ) {

    var changes = {};

    if( productSnap.exists() ) {
      changes[ productPath + "/quantity" ]  = productSnap.val().quantity + 1;
    } else {

      var addNewItem = confirm( "This item is not currently in the database.\n\nWould you like to add it now?" );
      if( !addNewItem ) {
        this.skip = true;
        return;
      }

      var newItemDescription = pareprompt( "Enter a description of the item:" );
      if( newItemDescription == null ) {
        this.skip = true;
        return;
      }

      var newItemPrice = parseInt( prompt( "Enter the price of the new item:" ) );
      if( newItemPrice == null ) {
        this.skip = true;
        return;
      }

      var newItem = {
        "description": newItemDescription,
        "price": newItemPrice,
        "barcodeNumber": barcodeNumber,
        "quantity": 1
      }
      changes[ productPath ] = newItem;

    }

    ref.update( changes );

  } ).then( function( productSnap ) {

    if( this.skip )
      return;

    addProductsHandler();

  } );

}

var deleteProductHandler = function( item ) {

  var shouldDelete = confirm( "Are you sure you want to remove all '" + item.description + "' records from the database?" );

  if( !shouldDelete )
    return;

  var changes = {};

  changes[ "/markup/products/" + item.barcodeNumber ] = null;

  ref.update( changes );
  location.reload();

}

var populateProductsList = function( currentUser ) {

	var productsPath = "/markup/products";
  var productsRef = ref.child( productsPath );
	productsRef.once( "value", function( productsSnap ) {

    productsSnap.forEach( function( productSnap ) {

      var item = {
        barcodeNumber: productSnap.key,
        price: productSnap.val().price,
        description: productSnap.val().description,
        quantity: productSnap.val().quantity,
        clearID: "clear" + productSnap.key
      }

      var $productDiv = $(
        '
' +
          '
' +
            item.description +
            '<button class="mdl-button mdl-js-button mdl-button--icon mdl-button--colored clearButton" style="float: right;" id="' + item.clearID + '">clear</button>' +
          '
' +
        '
'
      );

      $productDiv.click( function() {

        alert( "Description: " + item.description + "\n" +
               "Price: " + item.price + "\n" +
               "Quantity: " + item.quantity + "\n" +
               "Barcode Number: " + item.barcodeNumber );

      } );

      $productDiv.insertBefore( $( "#productsPlaceholder" ) );
      if( currentUser.adminStatus !== userAdminStates.ADMIN ) {
        $( "#" + item.clearID ).hide();
        $( "#add-products-button" ).hide();
      } else {
        $( "#" + item.clearID ).click(  function() { deleteProductHandler( item ) } );
      }

    } );

	} );

  $( "#add-products-button" ).click( function() {
    queueAdminStatusDependentFunction( ( 1 << userAdminStates.ADMIN ), addProductsHandler );
  } );

}
queueAdminStatusDependentFunction( ( ( 1 << userAdminStates.ADMIN ) | ( 1 << userAdminStates.MEMBER ) ), populateProductsList );

var bootNonMembers = function( currentUser ) {

  alert( "You must be a member of Beta Sigma Psi to acess this page." );
  location.replace( "/" );

}
queueAdminStatusDependentFunction( ( ( 1 << userAdminStates.NONMEMBER ) | ( 1 << userAdminStates.NONAUTH ) ), bootNonMembers );<commit_msg>Fix addProducts currentUser bug<commit_after>

var addProductsHandler = function( currentUser ) {

  if( currentUser.adminStatus !== userAdminStates.ADMIN )
    return;

  var barcodeNumber = prompt( "Barcode Number: " );

  if( barcodeNumber == null ) {
    location.reload();
    return;
  }

  productPath = "/markup/products/" + barcodeNumber;
  productRef = ref.child( productPath );
  productRef.once( "value", function( productSnap ) {

    var changes = {};

    if( productSnap.exists() ) {
      changes[ productPath + "/quantity" ]  = productSnap.val().quantity + 1;
    } else {

      var addNewItem = confirm( "This item is not currently in the database.\n\nWould you like to add it now?" );
      if( !addNewItem ) {
        this.skip = true;
        return;
      }

      var newItemDescription = pareprompt( "Enter a description of the item:" );
      if( newItemDescription == null ) {
        this.skip = true;
        return;
      }

      var newItemPrice = parseInt( prompt( "Enter the price of the new item:" ) );
      if( newItemPrice == null ) {
        this.skip = true;
        return;
      }

      var newItem = {
        "description": newItemDescription,
        "price": newItemPrice,
        "barcodeNumber": barcodeNumber,
        "quantity": 1
      }
      changes[ productPath ] = newItem;

    }

    ref.update( changes );

  } ).then( function( productSnap ) {

    if( this.skip )
      return;

    queueAdminStatusDependentFunction( ( 1 << userAdminStates.ADMIN ), addProductsHandler );

  } );

}

var deleteProductHandler = function( item ) {

  var shouldDelete = confirm( "Are you sure you want to remove all '" + item.description + "' records from the database?" );

  if( !shouldDelete )
    return;

  var changes = {};

  changes[ "/markup/products/" + item.barcodeNumber ] = null;

  ref.update( changes );
  location.reload();

}

var populateProductsList = function( currentUser ) {

	var productsPath = "/markup/products";
  var productsRef = ref.child( productsPath );
	productsRef.once( "value", function( productsSnap ) {

    productsSnap.forEach( function( productSnap ) {

      var item = {
        barcodeNumber: productSnap.key,
        price: productSnap.val().price,
        description: productSnap.val().description,
        quantity: productSnap.val().quantity,
        clearID: "clear" + productSnap.key
      }

      var $productDiv = $(
        '
' +
          '
' +
            item.description +
            '<button class="mdl-button mdl-js-button mdl-button--icon mdl-button--colored clearButton" style="float: right;" id="' + item.clearID + '">clear</button>' +
          '
' +
        '
'
      );

      $productDiv.click( function() {

        alert( "Description: " + item.description + "\n" +
               "Price: " + item.price + "\n" +
               "Quantity: " + item.quantity + "\n" +
               "Barcode Number: " + item.barcodeNumber );

      } );

      $productDiv.insertBefore( $( "#productsPlaceholder" ) );
      if( currentUser.adminStatus !== userAdminStates.ADMIN ) {
        $( "#" + item.clearID ).hide();
        $( "#add-products-button" ).hide();
      } else {
        $( "#" + item.clearID ).click(  function() { deleteProductHandler( item ) } );
      }

    } );

	} );

  $( "#add-products-button" ).click( function() {
    queueAdminStatusDependentFunction( ( 1 << userAdminStates.ADMIN ), addProductsHandler );
  } );

}
queueAdminStatusDependentFunction( ( ( 1 << userAdminStates.ADMIN ) | ( 1 << userAdminStates.MEMBER ) ), populateProductsList );

var bootNonMembers = function( currentUser ) {

  alert( "You must be a member of Beta Sigma Psi to acess this page." );
  location.replace( "/" );

}
queueAdminStatusDependentFunction( ( ( 1 << userAdminStates.NONMEMBER ) | ( 1 << userAdminStates.NONAUTH ) ), bootNonMembers );<|endoftext|>


<commit_before>function setVersion() {
	try	{
		var ver = JSON.parse(readVersion('version.json')).version;
		var versionLabel = document.getElementById("versionLabel");
		versionLabel.innerHTML = ver;
	}
	catch (e)
	{

	}
}

function readVersion(file)
{
	var versionRawFound = '';
    var versionRaw = new XMLHttpRequest();
    versionRaw.open("GET", file, false);
    versionRaw.onreadystatechange = function ()
    {
        if(versionRaw.readyState === 4)
        {
            if(versionRaw.status === 200 || versionRaw.status == 0)
            {
                versionRawFound = versionRaw.responseText;
            }
        }
    }
    versionRaw.send(null);

    return versionRawFound;
}

function createGuid()
{
	var guid = 'zzzzzzzz-zzzz-4zzz-tzzz-zzzzzzzzzzzz'.replace(/[zt]/g, function(c) {
    	var r = Math.random()*16|0, v = c == 'z' ? r : (r&0x3|0x8);
    	return v.toString(16);
	});
	return guid;
}

function setBoardInitial()
{
	// White

	// Rooks
	$(".block.row8.col1").html('&#9814');
	$(".block.row8.col1").click(function() { $('#statuss').append('
clicked white rook 1');});
	$(".block.row8.col8").html('&#9814');
	$(".block.row8.col8").click(function() { $('#statuss').append('
clicked white rook 2');});

	// Knights
	$(".block.row8.col2").html('♘');
	$(".block.row8.col2").click(function() { $('#statuss').append('
clicked white knight 1');});
	$(".block.row8.col7").html('♘');
	$(".block.row8.col7").click(function() { $('#statuss').append('
clicked white knight 2');});

	// Knights
	$(".block.row8.col3").html('♗');
	$(".block.row8.col3").click(function() { $('#statuss').append('
clicked white bishop 1');});
	$(".block.row8.col6").html('♗');
	$(".block.row8.col6").click(function() { $('#statuss').append('
clicked white bishop 2');});

	// Queen
	$(".block.row8.col4").html('♕');
	$(".block.row8.col4").click(function() { $('#statuss').append('
clicked the white queen');});

	// King
	$(".block.row8.col5").html('♔');
	$(".block.row8.col5").click(function() { $('#statuss').append('
clicked the white king');});

	// Pawns
	$(".block.row7.block").html('♙');
	$(".block.row7.block").click(function() {
		$('#statuss').append('
clicked a white pawn');
	});

	// Black

	// Rooks
	$(".block.row1.col1").html('&#9820');
	$(".block.row1.col1").click(function() { $('#statuss').append('
clicked black rook 1');});
	$(".block.row1.col8").html('&#9820');
	$(".block.row1.col8").click(function() { $('#statuss').append('
clicked black rook 2');});

	// Bishops
	$(".block.row1.col2").html('♞');
	$(".block.row1.col2").click(function() { $('#statuss').append('
clicked black knight 1');});
	$(".block.row1.col7").html('♞');
	$(".block.row1.col7").click(function() { $('#statuss').append('
clicked black knight 2');});

	// Knights
	$(".block.row1.col3").html('♝');
	$(".block.row1.col3").click(function() { $('#statuss').append('
clicked black bishop 1');});
	$(".block.row1.col6").html('♝');
	$(".block.row1.col6").click(function() { $('#statuss').append('
clicked black bishop 2');});

	// King
	$(".block.row1.col5").html('♚');
	$(".block.row1.col5").click(function() { $('#statuss').append('
clicked black king');});

	// Queen
	$(".block.row1.col4").html('♛');
	$(".block.row1.col4").click(function() { $('#statuss').append('
clicked black queen');});

	// Pawns
	$(".block.row2.block").html('♟');
	$(".block.row2.block").click(function() {
		$('#statuss').append('
clicked a black pawn');
	});
}

function setBoardCookie() {
	if (document.cookie.replace(/(?:(?:^|.*;\s*)instanceCookie\s*\=\s*([^;]*).*$)|^.*$/, "$1") !== "true") {
    	document.cookie = "instanceCookie=true; expires=Fri, 31 Dec 9999 23:59:59 GMT";
		document.cookie = "boardId=" + createGuid() + "; expires=Fri, 31 Dec 9999 23:59:59 GMT";
	}
}

function clearBoardCookie() {
	document.cookie = "instanceCookie=; expires=Fri, 31 Dec 9999 23:59:59 GMT";
	document.cookie = "boardId=; expires=Fri, 31 Dec 9999 23:59:59 GMT";
}

function readBoardCookie() {
	var currentBoardId = document.cookie.replace(/(?:(?:^|.*;\s*)boardId\s*\=\s*([^;]*).*$)|^.*$/, "$1");
	return currentBoardId;
}

function retrieveBoardState()
{
	// Get this jQuery madness out of here if time permits.  Plain Jane JS is more than sufficient
	var boardId = readBoardCookie();
	var apiUri = 'http://localhost:9997/board/' + boardId;

	$.getJSON(apiUri)
		.done(function(data) {
			try
			{
				$('#statuss').append('
retrieved board: ' + data.Id);
			}
			catch(e)
			{
				$('#statuss').append('
failed to deserialize json board')
			}
		})
		.fail(function() {
			$('#statuss').append('

API call to ' + apiUri + ' failed
');

		})
		.always(function() {
			//$('#statuss').append('

API call completed as promised
');
			//$('#statuss').append('
API promise completed');
		})

}

$(document).ready(function() {
	setVersion();
	setBoardInitial();

	clearBoardCookie();
	setBoardCookie();

	retrieveBoardState();
});<commit_msg>extend jQuery with flashClick<commit_after>const fadeInterval = 150;

function setVersion() {
	try	{
		var ver = JSON.parse(readVersion('version.json')).version;
		var versionLabel = document.getElementById("versionLabel");
		versionLabel.innerHTML = ver;
	}
	catch (e)
	{
		$('statuss').append('
' + e);
	}
}

function readVersion(file)
{
	var versionRawFound = '';
    var versionRaw = new XMLHttpRequest();
    versionRaw.open("GET", file, false);
    versionRaw.onreadystatechange = function ()
    {
        if(versionRaw.readyState === 4)
        {
            if(versionRaw.status === 200 || versionRaw.status == 0)
            {
                versionRawFound = versionRaw.responseText;
            }
        }
    }
    versionRaw.send(null);

    return versionRawFound;
}

function createGuid()
{
	var guid = 'zzzzzzzz-zzzz-4zzz-tzzz-zzzzzzzzzzzz'.replace(/[zt]/g, function(c) {
    	var r = Math.random()*16|0, v = c == 'z' ? r : (r&0x3|0x8);
    	return v.toString(16);
	});
	return guid;
}

// Extend jQuery to provide flash UX on pieces
jQuery.fn.extend({
  flashClick: function() {
    return this.fadeIn(fadeInterval).fadeOut(fadeInterval).fadeIn(fadeInterval);
    }});

function setBoardInitial()
{
	// White

	// Rooks
	$(".block.row8.col1").html('&#9814');
	$(".block.row8.col1").click(function() { $('#statuss').append('
clicked white rook 1');});
	$(".block.row8.col8").html('&#9814');
	$(".block.row8.col8").click(function() { $('#statuss').append('
clicked white rook 2');});

	// Knights
	$(".block.row8.col2").html('♘');
	$(".block.row8.col2").click(function() { $('#statuss').append('
clicked white knight 1');});
	$(".block.row8.col7").html('♘');
	$(".block.row8.col7").click(function() { $('#statuss').append('
clicked white knight 2');});

	// Knights
	$(".block.row8.col3").html('♗');
	$(".block.row8.col3").click(function() { $('#statuss').append('
clicked white bishop 1');});
	$(".block.row8.col6").html('♗');
	$(".block.row8.col6").click(function() { $('#statuss').append('
clicked white bishop 2');});

	// Queen
	$(".block.row8.col4").html('♕');
	$(".block.row8.col4").click(function() { $('#statuss').append('
clicked the white queen');});

	// King
	$(".block.row8.col5").html('♔');
	$(".block.row8.col5").click(function() { $('#statuss').append('
clicked the white king');});

	// Pawns
	$(".block.row7.block").html('♙');
	$(".block.row7.block").click(function() {
		$('#statuss').append('
clicked a white pawn');
	});

	// Black

	// Rooks
	$(".block.row1.col1").html('&#9820');
	$(".block.row1.col1").click(function() { $('#statuss').append('
clicked black rook 1');});
	$(".block.row1.col8").html('&#9820');
	$(".block.row1.col8").click(function() { $('#statuss').append('
clicked black rook 2');});

	// Bishops
	$(".block.row1.col2").html('♞');
	$(".block.row1.col2").click(function() { $('#statuss').append('
clicked black knight 1');});
	$(".block.row1.col7").html('♞');
	$(".block.row1.col7").click(function() { $('#statuss').append('
clicked black knight 2');});

	// Knights
	$(".block.row1.col3").html('♝');
	$(".block.row1.col3").click(function() { $('#statuss').append('
clicked black bishop 1');});
	$(".block.row1.col6").html('♝');
	$(".block.row1.col6").click(function() { $('#statuss').append('
clicked black bishop 2');});

	// King
	var blackKing = $(".block.row1.col5");
	blackKing.html('♚');
	blackKing.click(function() { $('#statuss').append('
clicked black king'); blackKing.flashClick();});

	// Queen
	var blackQueen = $(".block.row1.col4");
	blackQueen.html('♛');
	blackQueen.click(function() { $('#statuss').append('
clicked black queen'); blackQueen.flashClick();});

	// Pawns
	$(".block.row2.block").html('♟');
	$(".block.row2.block").click(function() {
		$('#statuss').append('
clicked a black pawn');
	});
}

function setBoardCookie() {
	if (document.cookie.replace(/(?:(?:^|.*;\s*)instanceCookie\s*\=\s*([^;]*).*$)|^.*$/, "$1") !== "true") {
    	document.cookie = "instanceCookie=true; expires=Fri, 31 Dec 9999 23:59:59 GMT";
		document.cookie = "boardId=" + createGuid() + "; expires=Fri, 31 Dec 9999 23:59:59 GMT";
	}
}

function clearBoardCookie() {
	document.cookie = "instanceCookie=; expires=Fri, 31 Dec 9999 23:59:59 GMT";
	document.cookie = "boardId=; expires=Fri, 31 Dec 9999 23:59:59 GMT";
}

function readBoardCookie() {
	var currentBoardId = document.cookie.replace(/(?:(?:^|.*;\s*)boardId\s*\=\s*([^;]*).*$)|^.*$/, "$1");
	return currentBoardId;
}

function retrieveBoardState()
{
	// Get this jQuery madness out of here if time permits.  Plain Jane JS is more than sufficient
	var boardId = readBoardCookie();
	var apiUri = 'http://localhost:9997/board/' + boardId;

	$.getJSON(apiUri)
		.done(function(data) {
			try
			{
				$('#statuss').append('
retrieved board: ' + data.Id);
			}
			catch(e)
			{
				$('#statuss').append('
failed to deserialize json board')
			}
		})
		.fail(function() {
			$('#statuss').append('

API call to ' + apiUri + ' failed
');

		})
		.always(function() {
			//$('#statuss').append('

API call completed as promised
');
			//$('#statuss').append('
API promise completed');
		})

}

$(document).ready(function() {
	setVersion();
	setBoardInitial();

	clearBoardCookie();
	setBoardCookie();

	retrieveBoardState();
});<|endoftext|>


<commit_before>/**
 * @author Eberhard Graether / http://egraether.com/
 */

THREE.LandscapeControls = function ( cover, camera ) {

    THREE.EventTarget.call( this );

    var _this = this;
    STATE = { NONE : -1, ROTATE : 0, PAN : 1, ZOOM : 2 };
    var statefunc = {
        0: this.rotate.bind(this),
        1: this.pan.bind(this),
        2: this.zoom.bind(this),
    }

    this.keystate = null;
    this.camera = camera;
    this.domElement = cover;

    // API
    this.enabled = true;

    // Constants
    this.rotateSpeed = .4;
    this.zoomSpeed = .002;
    this.maxRadius = 400; // makes sure axes & flatmat don't merge in depth buffer
    this.minRadius = function(mix){return 101*mix}; // limits zoom for flatmap, which disappears at r=100
    this.panSpeed = 0.3;
    this.clickTimeout = 200; // milliseconds

    // internals
    this.target = new THREE.Vector3();
    this.azimuth = 45;
    this.altitude = 75;
    this.radius = 250;

    var _state = STATE.NONE,
        _start = new THREE.Vector3(),
        _end = new THREE.Vector3();
        _touch = false;

    var _mousedowntime = 0;
    var _clicktime = 0; // Time of last click (mouseup event)
    var _indblpick = false; // In double-click and hold?
    var _picktimer = false; // timer that runs pick event

    // events

    var changeEvent = { type: 'change' };

    this.update = function (flatmix) {
        var mousechange;

        if (_touch) {
            _state = 0;
            mouseChange = _end;
        } else
            mouseChange = _end.clone().subSelf(_start);

        if (mouseChange.length() > 0 && statefunc[_state]) {
            if (statefunc[_state])
                statefunc[_state](mouseChange);
        }

        this.flatmix = flatmix;
        this.setCamera();

        _start = _end;
    };

    // listeners
    function keydown( event ) {
        if ( ! this.enabled ) return;

        if (event.keyCode == 17) {
            this.keystate = STATE.ZOOM;
        } else if (event.keyCode == 16) {
            this.keystate = STATE.PAN;
        } else {
            this.keystate = null;
        }

    };

    function keyup( event ) {
        if ( ! this.enabled ) return;
        this.keystate = null;
    };

    function mousedown( event ) {
        event.preventDefault();
        event.stopPropagation();

        if ( _state === STATE.NONE) {
            _state = this.keystate ? this.keystate : event.button;
            _start = _end = this.getMouse(event);
            if (event.button == 0) {
                _mousedowntime = new Date().getTime();
            }
            // Run double-click event if time since last click is short enough
            if ( _mousedowntime - _clicktime < this.clickTimeout && event.button == 0 ) {
                if (_picktimer) clearTimeout(_picktimer);
                var mouse2D = this.getMouse(event).clone();
                this.dispatchEvent({ type:"dblpick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM });
                _indblpick = true;
            } else {
                this.dispatchEvent({ type:"mousedown" });
            }
        }
    };

    function mouseup( event ) {
        if ( ! this.enabled ) return;

        event.preventDefault();
        event.stopPropagation();

        _state = STATE.NONE;
        if (event.button == 0) {
            _clicktime = new Date().getTime();
        }

        // Run picker if time since mousedown is short enough
        if ( _clicktime - _mousedowntime < this.clickTimeout && event.button == 0) {
            var mouse2D = this.getMouse(event).clone();
            this.dispatchEvent({ type: "mouseup" });
            this.dispatchEvent({ type:"pick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM});
        } else if ( event.button == 0 && _indblpick == true ) {
            this.dispatchEvent({ type:"undblpick" });
            _indblpick = false;
        } else {
            this.dispatchEvent({ type: "mouseup" });
        }
    };

    function mousemove( event ) {
        if ( ! this.enabled ) return;

        if ( _state === STATE.NONE ) {
            return;
        } else {
            _end = this.getMouse(event);
        }
        this.dispatchEvent( changeEvent );
    };

    function touchmove( event ) {
        $(this.domElement).append("touch");
        _touch = true;
        _end = new THREE.Vector2(event.touches[0].clientX, event.touches[1].clientY);
        this.dispatchEvent( changeEvent );
    };

    function mousewheel( event ) {
        if ( ! this.enabled ) return;

        event.preventDefault();
        event.stopPropagation();

        this.wheelzoom( event );
        this.setCamera();
        this.dispatchEvent( changeEvent );

    };

    //code from http://vetruvet.blogspot.com/2010/12/converting-single-touch-events-to-mouse.html
    var touchToMouse=function(b){if(!(b.touches.length>1)){var a=b.changedTouches[0],c="";switch(b.type){case "touchstart":c="mousedown";break;case "touchmove":c="mousemove";break;case "touchend":c="mouseup";break;default:return}var d=document.createEvent("MouseEvent");d.initMouseEvent(c,true,true,window,1,a.screenX,a.screenY,a.clientX,a.clientY,false,false,false,false,0,null);a.target.dispatchEvent(d);b.preventDefault()}};
    this.domElement.addEventListener( 'touchstart', touchToMouse );
    this.domElement.addEventListener( 'touchmove', touchToMouse );
    this.domElement.addEventListener( 'touchend', touchToMouse );

    this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );

    this.domElement.addEventListener( 'mousemove', mousemove.bind(this), false );
    this.domElement.addEventListener( 'mousedown', mousedown.bind(this), false );
    this.domElement.addEventListener( 'mouseup', mouseup.bind(this), false );
    this.domElement.addEventListener( 'mousewheel', mousewheel.bind(this), false);
    this.domElement.addEventListener( 'mouseout', mouseup.bind(this), false );

    window.addEventListener( 'keydown', keydown.bind(this), false );
    window.addEventListener( 'keyup', keyup.bind(this), false );

    this.resize($(cover).width(), $(cover).height() );
    this.flatmix = 0;
    this.setCamera();
};

THREE.LandscapeControls.prototype = {
    _limitview: function( ) {
        var flatmix = this.flatmix;
        var azlim = flatmix * 180;
        var altlim = flatmix * 90;

        this.altitude = this.altitude > 179.9999-altlim ? 179.9999-altlim : this.altitude;
        this.altitude = this.altitude < 0.0001+altlim ? 0.0001+altlim : this.altitude;

        if (azlim > this.azimuth || this.azimuth > (360 - azlim)) {
            var d1 = azlim - this.azimuth;
            var d2 = 360 - azlim - this.azimuth;
            this.azimuth = Math.abs(d1) > Math.abs(d2) ? 360-azlim : azlim;
        }

        var rad = this.radius, target = this.target.clone();
        var container = $(this.domElement.parentNode.parentNode)
        if (container.find("#zlockwhole").length > 0) {
            if (container.find("#zlockwhole")[0].checked) {
                rad  = this.flatsize / 2 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
            } else if (!container.find("#zlocknone")[0].checked) {
                rad  = this.flatsize / 4 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
                if (container.find("#zlockleft")[0].checked) {
                    target.x = flatmix * (-this.flatsize / 4) + (1 - flatmix) * target.x;
                } else if (container.find("#zlockright")[0].checked) {
                    target.x = flatmix * ( this.flatsize / 4) + (1 - flatmix) * target.x;
                }
            }
        }

        this._radius = rad;
        this._target = target;
    },

    resize: function(w, h) {
        this.domElement.style.width = w+"px";
        this.domElement.style.height = h+"px";
    },

    getMouse: function ( event ) {
        var off = $(event.target).offset();
        return new THREE.Vector2( event.clientX - off.left, event.clientY - off.top);
    },

    setCamera: function(az, alt, rad) {
        var changed = false;
        if (az !== undefined) {
            this.azimuth = ((az % 360)+360)%360;
            changed = true;
        }
        if (alt !== undefined) {
            this.altitude = alt;
            changed = true;
        }
        if (rad !== undefined) {
            this.radius = rad;
            changed = true;
        }

        this._limitview();

        var altrad = this.altitude*Math.PI / 180;
        var azirad = (this.azimuth+90)*Math.PI / 180;

        var eye = new THREE.Vector3(
            this._radius*Math.sin(altrad)*Math.cos(azirad),
            this._radius*Math.sin(altrad)*Math.sin(azirad),
            this._radius*Math.cos(altrad)
        );

        this.camera.position.add( this._target, eye );
        this.camera.lookAt( this._target );
        if (changed)
            this.dispatchEvent( {type:'change'} );
    },

    rotate: function ( mouseChange ) {
        var azdiff = -this.rotateSpeed*mouseChange.x;
        var az = this.azimuth + azdiff;
        if ( this.azlim > 0 ) {
            if ( this.azlim > az || az > (360-this.azlim)) {
                this.azimuth = azdiff < 0 ? this.azlim : 360-this.azlim;
            } else {
                this.azimuth = az - 360*Math.floor(az / 360);
            }
        } else {
            this.azimuth = az - 360*Math.floor(az / 360);
        }

        this.altitude -= this.rotateSpeed*mouseChange.y;

    // Add panning depending on flatmix
    var panMouseChange = new Object;
    panMouseChange.x = mouseChange.x * Math.pow(this.flatmix, 2);
    panMouseChange.y = mouseChange.y * Math.pow(this.flatmix, 2);
    this.pan(panMouseChange);
    },

    pan: function( mouseChange ) {
        var eye = this.camera.position.clone().subSelf(this.target);
        var right = eye.clone().crossSelf( this.camera.up );
        var up = right.clone().crossSelf(eye);
        var pan = (new THREE.Vector3()).add(
            right.setLength( this.panSpeed*mouseChange.x ),
            up.setLength( this.panSpeed*mouseChange.y ));
        this.camera.position.addSelf( pan );
        this.target.addSelf( pan );
    },

    zoom: function( mouseChange ) {
        var factor = 1.0 + mouseChange.y*this.zoomSpeed;
        this.radius *= factor;
        if (this.radius > this.maxRadius) {
            this.radius = this.maxRadius;
        }
        if (this.radius < this.minRadius(this.flatmix)) {
            this.radius = this.minRadius(this.flatmix);
        }
    },

    wheelzoom: function( wheelEvent ) {
        var factor = 1.0 + this.zoomSpeed * -1 * wheelEvent.wheelDelta/10.0;
        this.radius *= factor;
    }

}
<commit_msg>Added momentum to rotate and pan controls, with default friction of 0.5. This makes the controls feel nice and chunky.<commit_after>/**
 * @author Eberhard Graether / http://egraether.com/
 */

THREE.LandscapeControls = function ( cover, camera ) {

    THREE.EventTarget.call( this );

    var _this = this;
    STATE = { NONE : -1, ROTATE : 0, PAN : 1, ZOOM : 2 };
    var statefunc = {
        0: this.rotate.bind(this),
        1: this.pan.bind(this),
        2: this.zoom.bind(this),
    }

    this.keystate = null;
    this.camera = camera;
    this.domElement = cover;

    // API
    this.enabled = true;

    // Constants
    this.rotateSpeed = .4;
    this.zoomSpeed = .002;
    this.maxRadius = 400; // makes sure axes & flatmat don't merge in depth buffer
    this.minRadius = function(mix){return 101*mix}; // limits zoom for flatmap, which disappears at r=100
    this.panSpeed = 0.3;
    this.clickTimeout = 200; // milliseconds
    this.friction = 0.5;

    // internals
    this.target = new THREE.Vector3();
    this.azimuth = 45;
    this.altitude = 75;
    this.radius = 250;
    this._scheduled = false;

    var _state = STATE.NONE,
        _start = new THREE.Vector3(),
        _end = new THREE.Vector3();
        _touch = false;

    var _mousedowntime = 0;
    var _clicktime = 0; // Time of last click (mouseup event)
    var _indblpick = false; // In double-click and hold?
    var _picktimer = false; // timer that runs pick event

    // events

    var changeEvent = { type: 'change' };

    this.update = function (flatmix) {
        var mousechange;

        if (_touch) {
            _state = 0;
            mouseChange = _end;
        } else
            mouseChange = _end.clone().subSelf(_start);

        if (mouseChange.length() > 0 && statefunc[_state]) {
            if (statefunc[_state])
                statefunc[_state](mouseChange);
        }

        this.flatmix = flatmix;
        this.setCamera();

        _start = _end;
    };

    // listeners
    function keydown( event ) {
        if ( ! this.enabled ) return;

        if (event.keyCode == 17) {
            this.keystate = STATE.ZOOM;
        } else if (event.keyCode == 16) {
            this.keystate = STATE.PAN;
        } else {
            this.keystate = null;
        }

    };

    function keyup( event ) {
        if ( ! this.enabled ) return;
        this.keystate = null;
    };

    function mousedown( event ) {
        this.unschedule();

        event.preventDefault();
        event.stopPropagation();

        if ( _state === STATE.NONE) {
            _state = this.keystate ? this.keystate : event.button;
            _start = _end = this.getMouse(event);
            if (event.button == 0) {
                _mousedowntime = new Date().getTime();
            }
            // Run double-click event if time since last click is short enough
            if ( _mousedowntime - _clicktime < this.clickTimeout && event.button == 0 ) {
                if (_picktimer) clearTimeout(_picktimer);
                var mouse2D = this.getMouse(event).clone();
                this.dispatchEvent({ type:"dblpick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM });
                _indblpick = true;
            } else {
                this.dispatchEvent({ type:"mousedown" });
            }
        }
    };

    function mouseup( event ) {
        if ( ! this.enabled ) return;

        this.schedule( true );

        event.preventDefault();
        event.stopPropagation();

        _state = STATE.NONE;
        if (event.button == 0) {
            _clicktime = new Date().getTime();
        }

        // Run picker if time since mousedown is short enough
        if ( _clicktime - _mousedowntime < this.clickTimeout && event.button == 0) {
            var mouse2D = this.getMouse(event).clone();
            this.dispatchEvent({ type: "mouseup" });
            this.dispatchEvent({ type:"pick", x:mouse2D.x, y:mouse2D.y, keep:this.keystate == STATE.ZOOM});
        } else if ( event.button == 0 && _indblpick == true ) {
            this.dispatchEvent({ type:"undblpick" });
            _indblpick = false;
        } else {
            this.dispatchEvent({ type: "mouseup" });
        }
    };

    function mousemove( event ) {
        if ( ! this.enabled ) return;

        if ( _state === STATE.NONE ) {
            return;
        } else {
            _end = this.getMouse(event);
        }
        this.dispatchEvent( changeEvent );
    };

    function touchmove( event ) {
        $(this.domElement).append("touch");
        _touch = true;
        _end = new THREE.Vector2(event.touches[0].clientX, event.touches[1].clientY);
        this.dispatchEvent( changeEvent );
    };

    function mousewheel( event ) {
        if ( ! this.enabled ) return;

        event.preventDefault();
        event.stopPropagation();

        this.wheelzoom( event );
        this.setCamera();
        this.dispatchEvent( changeEvent );

    };

    //code from http://vetruvet.blogspot.com/2010/12/converting-single-touch-events-to-mouse.html
    var touchToMouse=function(b){if(!(b.touches.length>1)){var a=b.changedTouches[0],c="";switch(b.type){case "touchstart":c="mousedown";break;case "touchmove":c="mousemove";break;case "touchend":c="mouseup";break;default:return}var d=document.createEvent("MouseEvent");d.initMouseEvent(c,true,true,window,1,a.screenX,a.screenY,a.clientX,a.clientY,false,false,false,false,0,null);a.target.dispatchEvent(d);b.preventDefault()}};
    this.domElement.addEventListener( 'touchstart', touchToMouse );
    this.domElement.addEventListener( 'touchmove', touchToMouse );
    this.domElement.addEventListener( 'touchend', touchToMouse );

    this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );

    this.domElement.addEventListener( 'mousemove', mousemove.bind(this), false );
    this.domElement.addEventListener( 'mousedown', mousedown.bind(this), false );
    this.domElement.addEventListener( 'mouseup', mouseup.bind(this), false );
    this.domElement.addEventListener( 'mousewheel', mousewheel.bind(this), false);
    this.domElement.addEventListener( 'mouseout', mouseup.bind(this), false );

    window.addEventListener( 'keydown', keydown.bind(this), false );
    window.addEventListener( 'keyup', keyup.bind(this), false );

    this.resize($(cover).width(), $(cover).height() );
    this.flatmix = 0;
    this.setCamera();
};

THREE.LandscapeControls.prototype = {
    _limitview: function( ) {
        var flatmix = this.flatmix;
        var azlim = flatmix * 180;
        var altlim = flatmix * 90;

        this.altitude = this.altitude > 179.9999-altlim ? 179.9999-altlim : this.altitude;
        this.altitude = this.altitude < 0.0001+altlim ? 0.0001+altlim : this.altitude;

        if (azlim > this.azimuth || this.azimuth > (360 - azlim)) {
            var d1 = azlim - this.azimuth;
            var d2 = 360 - azlim - this.azimuth;
            this.azimuth = Math.abs(d1) > Math.abs(d2) ? 360-azlim : azlim;
        }

        var rad = this.radius, target = this.target.clone();
        var container = $(this.domElement.parentNode.parentNode)
        if (container.find("#zlockwhole").length > 0) {
            if (container.find("#zlockwhole")[0].checked) {
                rad  = this.flatsize / 2 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
            } else if (!container.find("#zlocknone")[0].checked) {
                rad  = this.flatsize / 4 / this.camera.cameraP.aspect;
                rad /= Math.tan(this.camera.fov / 2 * Math.PI / 180);
                rad -= this.flatoff;
                rad = flatmix * rad + (1 - flatmix) * this.radius;
                if (container.find("#zlockleft")[0].checked) {
                    target.x = flatmix * (-this.flatsize / 4) + (1 - flatmix) * target.x;
                } else if (container.find("#zlockright")[0].checked) {
                    target.x = flatmix * ( this.flatsize / 4) + (1 - flatmix) * target.x;
                }
            }
        }

        this._radius = rad;
        this._target = target;
    },

    resize: function(w, h) {
        this.domElement.style.width = w+"px";
        this.domElement.style.height = h+"px";
    },

    getMouse: function ( event ) {
        var off = $(event.target).offset();
        return new THREE.Vector2( event.clientX - off.left, event.clientY - off.top);
    },

    setCamera: function(az, alt, rad) {
        var changed = false;
        if (az !== undefined) {
            this.azimuth = ((az % 360)+360)%360;
            changed = true;
        }
        if (alt !== undefined) {
            this.altitude = alt;
            changed = true;
        }
        if (rad !== undefined) {
            this.radius = rad;
            changed = true;
        }

        this._limitview();

        var altrad = this.altitude*Math.PI / 180;
        var azirad = (this.azimuth+90)*Math.PI / 180;

        var eye = new THREE.Vector3(
            this._radius*Math.sin(altrad)*Math.cos(azirad),
            this._radius*Math.sin(altrad)*Math.sin(azirad),
            this._radius*Math.cos(altrad)
        );

        this.camera.position.add( this._target, eye );
        this.camera.lookAt( this._target );
        if (changed)
            this.dispatchEvent( {type:'change'} );
    },

    rotate: function ( mouseChange ) {
        this.azvel = -1 * this.rotateSpeed * mouseChange.x;
        var az = this.azimuth + this.azvel;
        if ( this.azlim > 0 ) {
            if ( this.azlim > az || az > (360-this.azlim)) {
                this.azimuth = azdiff < 0 ? this.azlim : 360-this.azlim;
            } else {
                this.azimuth = az - 360*Math.floor(az / 360);
            }
        } else {
            this.azimuth = az - 360*Math.floor(az / 360);
        }

        //this.altitude -= this.rotateSpeed*mouseChange.y;
        this.altvel = -1 * this.rotateSpeed * mouseChange.y;
        this.altitude += this.altvel;
        this.altitude = Math.max(Math.min(this.altitude, 180), 0.01);

        // Add panning depending on flatmix
        var panMouseChange = new Object;
        panMouseChange.x = mouseChange.x * Math.pow(this.flatmix, 2);
        panMouseChange.y = mouseChange.y * Math.pow(this.flatmix, 2);
        this.pan(panMouseChange);
    },

    pan: function( mouseChange ) {
        this.panxvel = this.panSpeed * mouseChange.x;
        this.panyvel = this.panSpeed * mouseChange.y;
        this.setpan( this.panSpeed * mouseChange.x, this.panSpeed * mouseChange.y );
    },

    setpan: function( x, y ) {
        var eye = this.camera.position.clone().subSelf(this.target);
        var right = eye.clone().crossSelf( this.camera.up );
        var up = right.clone().crossSelf(eye);
        var pan = (new THREE.Vector3()).add(
            right.setLength( x ),
            up.setLength( y ));
        this.camera.position.addSelf( pan );
        this.target.addSelf( pan );
    },

    zoom: function( mouseChange ) {
        var factor = 1.0 + mouseChange.y*this.zoomSpeed;
        this.radius *= factor;
        if (this.radius > this.maxRadius) {
            this.radius = this.maxRadius;
        }
        if (this.radius < this.minRadius(this.flatmix)) {
            this.radius = this.minRadius(this.flatmix);
        }
    },

    wheelzoom: function( wheelEvent ) {
        var factor = 1.0 + this.zoomSpeed * -1 * wheelEvent.wheelDelta/10.0;
        this.radius *= factor;
    },

    schedule: function( force ) {
        // console.log("Scheduling");
        if (this._in_anim || force) {
            this._in_anim = true;
            requestAnimationFrame( this.anim.bind(this) );
        }
    },

    unschedule: function() {
        this.resetvel();
        this._in_anim = false;
    },

    resetvel: function() {
        this.altvel = 0;
        this.azvel = 0;
        this.panxvel = 0;
        this.panyvel = 0;
    },

    anim: function() {
        if ( Math.abs(this.azvel) > 0 || Math.abs(this.altvel) > 0 ) {
            // console.log("Animating", this.azvel, this.altvel);
            this.azvel *= this.friction;
            this.azimuth += this.azvel;
            this.azimuth = ((this.azimuth % 360) + 360) % 360;

            this.altvel *= this.friction;
            this.altitude += this.altvel;
            this.altitude = Math.max(Math.min(this.altitude, 180), 0.01);
        }

        if ( Math.abs(this.panxvel) > 0 || Math.abs(this.panyvel) > 0 ) {
            // console.log("Animating", this.panxvel, this.panyvel);
            this.panxvel *= this.friction;
            this.panyvel *= this.friction;

            this.setpan(this.panxvel, this.panyvel);
        }

        this.dispatchEvent( {type:'change'} );

        if (Math.abs(this.altvel)<0.01 && Math.abs(this.azvel)<0.01 && Math.abs(this.panxvel)<0.01 && Math.abs(this.panyvel)<0.01) {
            this.resetvel();
        } else {
            this.schedule();
        }

    }

}
<|endoftext|>


<commit_before>/* Copyright (c) 2014-2015 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

import "angular-ui-router";
import "angular-touch";

import * as appEvents from "./app-events";

var app = angular.module("app", ["ngTouch", "ui.router"]);

angular.module("app").config(config);

config.$inject = ["$stateProvider", "$urlRouterProvider"];

function config($stateProvider, $urlRouterProvider) {

    $urlRouterProvider.otherwise("/inbox");

    $stateProvider.state("alerts", {
        url: "/alerts?q",
        template: "<alerts></alerts>",
        data: {
            mode: "alerts"
        }
    });

    $stateProvider.state("escalated", {
        url: "/escalated?q",
        template: "<alerts></alerts>",
        data: {
            mode: "escalated"
        }
    });

    $stateProvider.state("inbox", {
        url: "/inbox?q",
        template: "<alerts></alerts>",
        data: {
            mode: "inbox"
        }
    });

    $stateProvider.state("events", {
        url: "/events?q&timeStart&timeEnd",
        template: "<events></events>"
    });

    $stateProvider.state("event", {
        url: "/event/:id",
        template: "<event></event>"
    });

}

angular.module("app").run(run);

run.$inject = ["$rootScope"];

function run($rootScope) {
    $(window).resize(function () {
        $rootScope.$broadcast(appEvents.WINDOW_RESIZE);
    });
}

angular.element(document).ready(function () {
    var initInjector = angular.injector(["ng"]);
    var $http = initInjector.get("$http");
    $http.get("/api/config").then(response => {
        app.constant("rawConfig", response.data);
        angular.bootstrap(document, ["app"]);
    });
});<commit_msg>Fix link to config service<commit_after>/* Copyright (c) 2014-2015 Jason Ish
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

import "angular-ui-router";
import "angular-touch";

import * as appEvents from "./app-events";

var app = angular.module("app", ["ngTouch", "ui.router"]);

angular.module("app").config(config);

config.$inject = ["$stateProvider", "$urlRouterProvider"];

function config($stateProvider, $urlRouterProvider) {

    $urlRouterProvider.otherwise("/inbox");

    $stateProvider.state("alerts", {
        url: "/alerts?q",
        template: "<alerts></alerts>",
        data: {
            mode: "alerts"
        }
    });

    $stateProvider.state("escalated", {
        url: "/escalated?q",
        template: "<alerts></alerts>",
        data: {
            mode: "escalated"
        }
    });

    $stateProvider.state("inbox", {
        url: "/inbox?q",
        template: "<alerts></alerts>",
        data: {
            mode: "inbox"
        }
    });

    $stateProvider.state("events", {
        url: "/events?q&timeStart&timeEnd",
        template: "<events></events>"
    });

    $stateProvider.state("event", {
        url: "/event/:id",
        template: "<event></event>"
    });

}

angular.module("app").run(run);

run.$inject = ["$rootScope"];

function run($rootScope) {
    $(window).resize(function () {
        $rootScope.$broadcast(appEvents.WINDOW_RESIZE);
    });
}

angular.element(document).ready(function () {
    var initInjector = angular.injector(["ng"]);
    var $http = initInjector.get("$http");
    $http.get(document.location + "api/config").then(response => {
        app.constant("rawConfig", response.data);
        angular.bootstrap(document, ["app"]);
    });
});
<|endoftext|>