<commit_before>// This file is part of Indico.
// Copyright (C) 2002 - 2021 CERN
//
// Indico is free software; you can redistribute it and/or
// modify it under the terms of the MIT License; see the
// LICENSE file for more details.

/* global setupSearchBox:false, strnatcmp:false, cornerMessage:false, handleAjaxError:false,
          enableIfChecked:false */

import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';

import 'selectize';
import 'selectize/dist/css/selectize.css';
import 'selectize/dist/css/selectize.default.css';

import 'indico/modules/events/util/list_generator';
import 'indico/modules/events/util/static_filters';

import './badges';

import {$T} from 'indico/utils/i18n';

import {SingleEventMove, EventPublish} from './EventMove';

(function(global) {
  global.setupEventManagementActionMenu = function setupEventManagementActionMenu() {
    const moveContainer = document.querySelector('#event-action-move-container');
    if (moveContainer) {
      ReactDOM.render(
        React.createElement(SingleEventMove, {
          eventId: +moveContainer.dataset.eventId,
          currentCategoryId: +moveContainer.dataset.categoryId,
          hasPendingMoveRequest: moveContainer.dataset.pendingRequest !== undefined,
        }),
        moveContainer
      );
    }

    const publishContainer = document.querySelector('#event-action-publish-container');
    if (publishContainer) {
      ReactDOM.render(
        React.createElement(EventPublish, {
          eventId: +publishContainer.dataset.eventId,
          hasPendingPublishRequest: publishContainer.dataset.pendingRequest !== undefined,
        }),
        publishContainer
      );
    }

    $('#event-action-menu-clones-target').qbubble({
      content: {
        text: $('#event-action-menu-clones'),
      },
    });

    $('#event-action-menu-actions-target').qbubble({
      content: {
        text: $('#event-action-menu-actions'),
      },
      style: {
        classes: 'qtip-allow-overflow',
      },
    });

    const selectors = [
      '#event-action-menu-actions button:not(.js-dropdown)',
      '#event-action-menu-actions a:not(.disabled)',
    ];
    $(selectors.join(', ')).on('click', function() {
      $('#event-action-menu-actions-target').qbubble('hide');
    });
  };

  function refreshPersonFilters() {
    $('#person-filters ul > li').removeClass('enabled');
    const personRows = $('.js-event-person-list tr[data-person-roles]');
    const filters = $('.js-event-person-list [data-filter]:checked')
      .map(function() {
        const $this = $(this);
        $this.closest('li').addClass('enabled');
        return $this.data('filter');
      })
      .get();

    const visibleEntries = personRows.filter(function() {
      const $this = $(this);

      return _.any(filters, function(filterName) {
        return $this.data('person-roles')[filterName];
      });
    });

    personRows.addClass('hidden');
    visibleEntries.removeClass('hidden');
    $('.js-event-person-list').trigger('indico:syncEnableIfChecked');
  }

  function toggleResetBtn() {
    const isInitialState =
      $('#person-filters [data-filter]:checked').length ===
      $('#person-filters [data-filter]:not(#filter-no-account,#filter-no-registration)').length;
    $('.js-reset-role-filter').toggleClass('disabled', isInitialState);
  }

  function initTooltip() {
    $('.js-show-regforms').qtip({
      content: {
        text() {
          return $(this).data('title');
        },
      },
      hide: {
        delay: 100,
        fixed: true,
      },
    });
  }

  global.setupEventPersonsList = function setupEventPersonsList(options) {
    options = $.extend(
      {
        hasNoAccountFilter: false,
        hasNoRegistrationFilter: false,
      },
      options
    );

    const filterConfig = {
      itemHandle: 'tr',
      listItems: '#event-participants-list tbody tr:not(.hidden)',
      term: '#search-input',
      state: '#filtering-state',
      placeholder: '#filter-placeholder',
    };
    const applySearchFilters = setupSearchBox(filterConfig);

    enableIfChecked(
      '.js-event-person-list',
      '.select-row:visible',
      '.js-event-person-list .js-requires-selected-row'
    );
    if ($('.js-event-person-list').closest('.ui-dialog').length) {
      $('.js-event-person-list [data-toggle=dropdown]')
        .closest('.toolbar')
        .dropdown();
    }
    $('.js-event-person-list [data-filter]').on('click', refreshPersonFilters);

    $('.js-event-person-list td').on('mouseenter', function() {
      const $this = $(this);
      if (this.offsetWidth < this.scrollWidth && !$this.attr('title')) {
        $this.attr('title', $this.text());
      }
    });

    $('.js-event-person-list .tablesorter').tablesorter({
      cssAsc: 'header-sort-asc',
      cssDesc: 'header-sort-desc',
      headerTemplate: '',
      sortList: [[1, 0]],
    });

    const $roleLabels = $('.js-event-person-list .roles-column > span');
    $roleLabels.qbubble({
      show: {
        event: 'mouseover',
      },
      hide: {
        fixed: true,
        delay: 100,
        event: 'mouseleave',
      },
      position: {
        my: 'left center',
        at: 'right center',
      },
      content: {
        text() {
          const $this = $(this);
          const html = $('<div>');
          const role = $('<strong>', {text: $(this).data('role-name')});
          html.append(role);
          if ($this.is('.js-count-label')) {
            const list = $('<ul>', {class: 'qbubble-item-list'});
            const items = _.values($this.data('items')).sort(function(a, b) {
              return strnatcmp(a.title.toLowerCase(), b.title.toLowerCase());
            });

            $.each(items, function() {
              const item = $('<li>');
              if (this.url) {
                item.append($('<a>', {text: this.title, href: this.url}));
              } else {
                item.text(this.title);
              }
              list.append(item);
            });
            html.append(list);
          }

          return html;
        },
      },
    });

    // Sets background color of custom role labels based on their font color
    $roleLabels.filter('.custom').each(function() {
      const $this = $(this);
      $this.css('background-color', $.Color($this.css('color')).alpha(0.1));
    });

    if (options.hasNoAccountFilter) {
      $('.js-event-person-list [data-filter]:not(#filter-no-account)').on('change', function() {
        $('#filter-no-account').prop('checked', false);
        refreshPersonFilters();
        applySearchFilters();
      });
      $('#filter-no-account').on('change', function() {
        if (this.checked) {
          $('.js-event-person-list [data-filter]:checked:not(#filter-no-account)').prop(
            'checked',
            false
          );
        }
        refreshPersonFilters();
        applySearchFilters();
      });
    }

    if (options.hasNoRegistrationFilter) {
      $('.js-event-person-list [data-filter]:not(#filter-no-registration)').on(
        'change',
        function() {
          $('#filter-no-registration').prop('checked', false);
          refreshPersonFilters();
          applySearchFilters();
        }
      );
      $('#filter-no-registration').on('change', function() {
        if (this.checked) {
          $('.js-event-person-list [data-filter]:checked:not(#filter-no-registration)').prop(
            'checked',
            false
          );
        }
        refreshPersonFilters();
        applySearchFilters();
      });
    }

    initTooltip();

    const $personFilters = $('#person-filters');
    // Sets background color of role filter items based on their colored squared color
    $personFilters.find('li .colored-square').each(function() {
      const $this = $(this);
      $this.closest('li').css('background-color', $.Color($this.css('color')).alpha(0.1));
    });

    // Reset role filters
    $personFilters.find('.js-reset-role-filter').on('click', function() {
      $('.js-event-person-list [data-filter]').each(function() {
        const $this = $(this);
        $this.prop('checked', !$this.is('#filter-no-account, #filter-no-registration'));
        $this
          .parent()
          .toggleClass('enabled', !$this.is('#filter-no-account, #filter-no-registration'));
      });
      refreshPersonFilters();
      applySearchFilters();
      toggleResetBtn();
    });

    // Allows to click in the whole list item area to enable/disable role filters
    $personFilters.find('li label').on('click', function(evt) {
      evt.preventDefault();
    });
    $personFilters.find('li').on('click', function() {
      const $checkbox = $(this).find('[data-filter]');
      $checkbox.prop('checked', !$checkbox.prop('checked')).trigger('change');
      toggleResetBtn();
    });

    $('#event-participants-list').on('indico:htmlUpdated', initTooltip);
  };

  global.showUndoWarning = function showUndoWarning(message, feedbackMessage, actionCallback) {
    cornerMessage({
      message,
      progressMessage: $T.gettext('Undoing previous operation...'),
      feedbackMessage,
      actionLabel: $T.gettext('Undo'),
      actionCallback,
      duration: 10000,
      feedbackDuration: 4000,
      class: 'warning',
    });
  };

  global.handleSelectedRowHighlight = function handleSelectedRowHighlight(trigger) {
    const $obj = $('table.i-table input.select-row').on('change', function() {
      $(this)
        .closest('tr')
        .toggleClass('selected', this.checked);
    });

    if (trigger) {
      $obj.trigger('change');
    }
  };

  global.showThemeSettingsForm = function showThemeSettingsForm(options) {
    options = $.extend(
      true,
      {
        formUrl: null,
        themeFieldId: null,
        lastFieldId: null,
      },
      options
    );
    $(`#${options.themeFieldId}`).on('change', function() {
      $.ajax({
        url: options.formUrl,
        data: {
          theme: $(this).val(),
        },
        complete: IndicoUI.Dialogs.Util.progress(),
        error: handleAjaxError,
        success(data) {
          const lastField = $(`#${options.lastFieldId}`);
          lastField.nextAll(':not(.form-group-footer)').remove();
          if (data.html) {
            lastField.after(data.html);
          }
          if (data.js) {
            $('body').append(data.js);
          }
        },
      });
    });
  };
})(window);
<commit_msg>Fix entry sorting on author list filters<commit_after>// This file is part of Indico.
// Copyright (C) 2002 - 2021 CERN
//
// Indico is free software; you can redistribute it and/or
// modify it under the terms of the MIT License; see the
// LICENSE file for more details.

/* global setupSearchBox:false, strnatcmp:false, cornerMessage:false, handleAjaxError:false,
          enableIfChecked:false */

import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';

import 'selectize';
import 'selectize/dist/css/selectize.css';
import 'selectize/dist/css/selectize.default.css';

import 'indico/modules/events/util/list_generator';
import 'indico/modules/events/util/static_filters';

import './badges';

import {$T} from 'indico/utils/i18n';

import {SingleEventMove, EventPublish} from './EventMove';

(function(global) {
  global.setupEventManagementActionMenu = function setupEventManagementActionMenu() {
    const moveContainer = document.querySelector('#event-action-move-container');
    if (moveContainer) {
      ReactDOM.render(
        React.createElement(SingleEventMove, {
          eventId: +moveContainer.dataset.eventId,
          currentCategoryId: +moveContainer.dataset.categoryId,
          hasPendingMoveRequest: moveContainer.dataset.pendingRequest !== undefined,
        }),
        moveContainer
      );
    }

    const publishContainer = document.querySelector('#event-action-publish-container');
    if (publishContainer) {
      ReactDOM.render(
        React.createElement(EventPublish, {
          eventId: +publishContainer.dataset.eventId,
          hasPendingPublishRequest: publishContainer.dataset.pendingRequest !== undefined,
        }),
        publishContainer
      );
    }

    $('#event-action-menu-clones-target').qbubble({
      content: {
        text: $('#event-action-menu-clones'),
      },
    });

    $('#event-action-menu-actions-target').qbubble({
      content: {
        text: $('#event-action-menu-actions'),
      },
      style: {
        classes: 'qtip-allow-overflow',
      },
    });

    const selectors = [
      '#event-action-menu-actions button:not(.js-dropdown)',
      '#event-action-menu-actions a:not(.disabled)',
    ];
    $(selectors.join(', ')).on('click', function() {
      $('#event-action-menu-actions-target').qbubble('hide');
    });
  };

  function refreshPersonFilters() {
    $('#person-filters ul > li').removeClass('enabled');
    const personRows = $('.js-event-person-list tr[data-person-roles]');
    const filters = $('.js-event-person-list [data-filter]:checked')
      .map(function() {
        const $this = $(this);
        $this.closest('li').addClass('enabled');
        return $this.data('filter');
      })
      .get();

    const visibleEntries = personRows.filter((idx, entry) =>
      filters.find(filterName => $(entry).data('person-roles')[filterName])
    );

    personRows.addClass('hidden');
    visibleEntries.removeClass('hidden');
    $('.js-event-person-list').trigger('indico:syncEnableIfChecked');
  }

  function toggleResetBtn() {
    const isInitialState =
      $('#person-filters [data-filter]:checked').length ===
      $('#person-filters [data-filter]:not(#filter-no-account,#filter-no-registration)').length;
    $('.js-reset-role-filter').toggleClass('disabled', isInitialState);
  }

  function initTooltip() {
    $('.js-show-regforms').qtip({
      content: {
        text() {
          return $(this).data('title');
        },
      },
      hide: {
        delay: 100,
        fixed: true,
      },
    });
  }

  global.setupEventPersonsList = function setupEventPersonsList(options) {
    options = $.extend(
      {
        hasNoAccountFilter: false,
        hasNoRegistrationFilter: false,
      },
      options
    );

    const filterConfig = {
      itemHandle: 'tr',
      listItems: '#event-participants-list tbody tr:not(.hidden)',
      term: '#search-input',
      state: '#filtering-state',
      placeholder: '#filter-placeholder',
    };
    const applySearchFilters = setupSearchBox(filterConfig);

    enableIfChecked(
      '.js-event-person-list',
      '.select-row:visible',
      '.js-event-person-list .js-requires-selected-row'
    );
    if ($('.js-event-person-list').closest('.ui-dialog').length) {
      $('.js-event-person-list [data-toggle=dropdown]')
        .closest('.toolbar')
        .dropdown();
    }
    $('.js-event-person-list [data-filter]').on('click', refreshPersonFilters);

    $('.js-event-person-list td').on('mouseenter', function() {
      const $this = $(this);
      if (this.offsetWidth < this.scrollWidth && !$this.attr('title')) {
        $this.attr('title', $this.text());
      }
    });

    $('.js-event-person-list .tablesorter').tablesorter({
      cssAsc: 'header-sort-asc',
      cssDesc: 'header-sort-desc',
      headerTemplate: '',
      sortList: [[1, 0]],
    });

    const $roleLabels = $('.js-event-person-list .roles-column > span');
    $roleLabels.qbubble({
      show: {
        event: 'mouseover',
      },
      hide: {
        fixed: true,
        delay: 100,
        event: 'mouseleave',
      },
      position: {
        my: 'left center',
        at: 'right center',
      },
      content: {
        text() {
          const $this = $(this);
          const html = $('<div>');
          const role = $('<strong>', {text: $(this).data('role-name')});
          html.append(role);
          if ($this.is('.js-count-label')) {
            const list = $('<ul>', {class: 'qbubble-item-list'});
            const items = _.values($this.data('items')).sort(function(a, b) {
              return strnatcmp(a.title.toLowerCase(), b.title.toLowerCase());
            });

            $.each(items, function() {
              const item = $('<li>');
              if (this.url) {
                item.append($('<a>', {text: this.title, href: this.url}));
              } else {
                item.text(this.title);
              }
              list.append(item);
            });
            html.append(list);
          }

          return html;
        },
      },
    });

    // Sets background color of custom role labels based on their font color
    $roleLabels.filter('.custom').each(function() {
      const $this = $(this);
      $this.css('background-color', $.Color($this.css('color')).alpha(0.1));
    });

    if (options.hasNoAccountFilter) {
      $('.js-event-person-list [data-filter]:not(#filter-no-account)').on('change', function() {
        $('#filter-no-account').prop('checked', false);
        refreshPersonFilters();
        applySearchFilters();
      });
      $('#filter-no-account').on('change', function() {
        if (this.checked) {
          $('.js-event-person-list [data-filter]:checked:not(#filter-no-account)').prop(
            'checked',
            false
          );
        }
        refreshPersonFilters();
        applySearchFilters();
      });
    }

    if (options.hasNoRegistrationFilter) {
      $('.js-event-person-list [data-filter]:not(#filter-no-registration)').on(
        'change',
        function() {
          $('#filter-no-registration').prop('checked', false);
          refreshPersonFilters();
          applySearchFilters();
        }
      );
      $('#filter-no-registration').on('change', function() {
        if (this.checked) {
          $('.js-event-person-list [data-filter]:checked:not(#filter-no-registration)').prop(
            'checked',
            false
          );
        }
        refreshPersonFilters();
        applySearchFilters();
      });
    }

    initTooltip();

    const $personFilters = $('#person-filters');
    // Sets background color of role filter items based on their colored squared color
    $personFilters.find('li .colored-square').each(function() {
      const $this = $(this);
      $this.closest('li').css('background-color', $.Color($this.css('color')).alpha(0.1));
    });

    // Reset role filters
    $personFilters.find('.js-reset-role-filter').on('click', function() {
      $('.js-event-person-list [data-filter]').each(function() {
        const $this = $(this);
        $this.prop('checked', !$this.is('#filter-no-account, #filter-no-registration'));
        $this
          .parent()
          .toggleClass('enabled', !$this.is('#filter-no-account, #filter-no-registration'));
      });
      refreshPersonFilters();
      applySearchFilters();
      toggleResetBtn();
    });

    // Allows to click in the whole list item area to enable/disable role filters
    $personFilters.find('li label').on('click', function(evt) {
      evt.preventDefault();
    });
    $personFilters.find('li').on('click', function() {
      const $checkbox = $(this).find('[data-filter]');
      $checkbox.prop('checked', !$checkbox.prop('checked')).trigger('change');
      toggleResetBtn();
    });

    $('#event-participants-list').on('indico:htmlUpdated', initTooltip);
  };

  global.showUndoWarning = function showUndoWarning(message, feedbackMessage, actionCallback) {
    cornerMessage({
      message,
      progressMessage: $T.gettext('Undoing previous operation...'),
      feedbackMessage,
      actionLabel: $T.gettext('Undo'),
      actionCallback,
      duration: 10000,
      feedbackDuration: 4000,
      class: 'warning',
    });
  };

  global.handleSelectedRowHighlight = function handleSelectedRowHighlight(trigger) {
    const $obj = $('table.i-table input.select-row').on('change', function() {
      $(this)
        .closest('tr')
        .toggleClass('selected', this.checked);
    });

    if (trigger) {
      $obj.trigger('change');
    }
  };

  global.showThemeSettingsForm = function showThemeSettingsForm(options) {
    options = $.extend(
      true,
      {
        formUrl: null,
        themeFieldId: null,
        lastFieldId: null,
      },
      options
    );
    $(`#${options.themeFieldId}`).on('change', function() {
      $.ajax({
        url: options.formUrl,
        data: {
          theme: $(this).val(),
        },
        complete: IndicoUI.Dialogs.Util.progress(),
        error: handleAjaxError,
        success(data) {
          const lastField = $(`#${options.lastFieldId}`);
          lastField.nextAll(':not(.form-group-footer)').remove();
          if (data.html) {
            lastField.after(data.html);
          }
          if (data.js) {
            $('body').append(data.js);
          }
        },
      });
    });
  };
})(window);
<|endoftext|>


<commit_before>import template from './app.html';
import './app.scss';
import changelog from '../views/changelog-toaster-release.html';
import { isPhonePortrait, isPhonePortraitStream } from './mediaQueries';
import _ from 'underscore';
import i18next from 'i18next';

export const AppComponent = {
  template: template,
  controller: AppComponentCtrl
};

function AppComponentCtrl(
  $scope,
  dimInfoService,
  dimSettingsService,
  $i18next,
  toaster,
  $timeout,
  hotkeys,
  dimState
) {
  'ngInject';

  this.$onInit = function() {
    this.reviewsEnabled = $featureFlags.reviewsEnabled;
    this.settings = dimSettingsService;
    this.featureFlags = {
      colorA11y: $featureFlags.colorA11y
    };

    this.settings = dimSettingsService;
    $scope.$watch(() => this.settings.itemSize, (size) => {
      document.querySelector('html').style.setProperty("--item-size", `${size}px`);
    });
    $scope.$watch(() => this.settings.charCol, (cols) => {
      document.querySelector('html').style.setProperty("--character-columns", cols);
    });
    $scope.$watch(() => this.settings.vaultMaxCol, (cols) => {
      document.querySelector('html').style.setProperty("--vault-max-columns", cols);
    });

    $scope.$watch(() => this.settings.charColMobile, (cols) => {
      // this check is needed so on start up/load this doesn't override the value set above on "normal" mode.
      if (isPhonePortrait()) {
        document.querySelector('html').style.setProperty("--character-columns", cols);
      }
    });
    // a subscribe on isPhonePortraitStream is needed when the user on mobile changes from portrait to landscape
    // or a user on desktop shrinks the browser window below isphoneportrait treshold value
    isPhonePortraitStream().subscribe((isPhonePortrait) => {
      if (isPhonePortrait) {
        document.querySelector('html').style.setProperty("--character-columns", this.settings.charColMobile);
      } else {
        document.querySelector('html').style.setProperty("--character-columns", this.settings.charCol);
      }
    });

    hotkeys = hotkeys.bindTo($scope);

    hotkeys.add({
      combo: ['ctrl+alt+shift+d'],
      callback: function() {
        dimState.debug = true;
        console.log("***** DIM DEBUG MODE ENABLED *****");
      }
    });

    if ($featureFlags.colorA11y) {
      $scope.$watch(() => this.settings.colorA11y, (color) => {
        if (color && color !== '-') {
          document.querySelector('html').style.setProperty("--color-filter", `url(#${color.toLowerCase()})`);
        } else {
          document.querySelector('html').style.removeProperty("--color-filter");
        }
      });
    }

    // Show the changelog
    if ($featureFlags.changelogToaster) {
      $timeout(() => {
        dimInfoService.show(`changelogv${$DIM_VERSION.replace(/\./gi, '')}`, {
          title: $i18next.t('Help.Version', {
            version: $DIM_VERSION,
            context: $DIM_FLAVOR
          }),
          body: changelog
        });
      });
    }

    try {
      localStorage.setItem('test', 'true');
    } catch (e) {
      console.log('storage test', e);
      $timeout(() => {
        dimInfoService.show('no-storage', {
          title: $i18next.t('Help.NoStorage'),
          body: $i18next.t('Help.NoStorageMessage'),
          type: 'error',
          hideable: false
        }, 0);
      });
    }

    if (window.BroadcastChannel) {
      const updateChannel = new window.BroadcastChannel('precache-updates');

      const updateMessage = _.once(() => {
        $timeout(() => {
          dimInfoService.show('update-available', {
            title: $i18next.t('Help.UpdateAvailable'),
            body: $i18next.t('Help.UpdateAvailableMessage'),
            type: 'warn',
            hideable: false
          }, 0);
        });
      });

      const messageHandler = () => updateMessage();

      updateChannel.addEventListener('message', messageHandler);
      $scope.$on('$destroy', () => {
        updateChannel.removeEventListener('message', messageHandler);
      });
    }
  };

  $scope.$on('i18nextLanguageChange', () => {
    this.language = `lang-${i18next.language}`;
  });
}
<commit_msg>maybe if you import it?<commit_after>import template from './app.html';
import './app.scss';
import changelog from '../views/changelog-toaster-release.html';
import { isPhonePortrait, isPhonePortraitStream } from './mediaQueries';
import { subscribeOnScope } from './rx-utils';
import _ from 'underscore';
import i18next from 'i18next';

export const AppComponent = {
  template: template,
  controller: AppComponentCtrl
};

function AppComponentCtrl(
  $scope,
  dimInfoService,
  dimSettingsService,
  $i18next,
  toaster,
  $timeout,
  hotkeys,
  dimState
) {
  'ngInject';

  this.$onInit = function() {
    this.reviewsEnabled = $featureFlags.reviewsEnabled;
    this.settings = dimSettingsService;
    this.featureFlags = {
      colorA11y: $featureFlags.colorA11y
    };

    this.settings = dimSettingsService;
    $scope.$watch(() => this.settings.itemSize, (size) => {
      document.querySelector('html').style.setProperty("--item-size", `${size}px`);
    });
    $scope.$watch(() => this.settings.charCol, (cols) => {
      document.querySelector('html').style.setProperty("--character-columns", cols);
    });
    $scope.$watch(() => this.settings.vaultMaxCol, (cols) => {
      document.querySelector('html').style.setProperty("--vault-max-columns", cols);
    });

    $scope.$watch(() => this.settings.charColMobile, (cols) => {
      // this check is needed so on start up/load this doesn't override the value set above on "normal" mode.
      if (isPhonePortrait()) {
        document.querySelector('html').style.setProperty("--character-columns", cols);
      }
    });
    // a subscribe on isPhonePortraitStream is needed when the user on mobile changes from portrait to landscape
    // or a user on desktop shrinks the browser window below isphoneportrait treshold value
    isPhonePortraitStream().subscribeOnScope((isPhonePortrait) => {
      if (isPhonePortrait) {
        document.querySelector('html').style.setProperty("--character-columns", this.settings.charColMobile);
      } else {
        document.querySelector('html').style.setProperty("--character-columns", this.settings.charCol);
      }
    });

    hotkeys = hotkeys.bindTo($scope);

    hotkeys.add({
      combo: ['ctrl+alt+shift+d'],
      callback: function() {
        dimState.debug = true;
        console.log("***** DIM DEBUG MODE ENABLED *****");
      }
    });

    if ($featureFlags.colorA11y) {
      $scope.$watch(() => this.settings.colorA11y, (color) => {
        if (color && color !== '-') {
          document.querySelector('html').style.setProperty("--color-filter", `url(#${color.toLowerCase()})`);
        } else {
          document.querySelector('html').style.removeProperty("--color-filter");
        }
      });
    }

    // Show the changelog
    if ($featureFlags.changelogToaster) {
      $timeout(() => {
        dimInfoService.show(`changelogv${$DIM_VERSION.replace(/\./gi, '')}`, {
          title: $i18next.t('Help.Version', {
            version: $DIM_VERSION,
            context: $DIM_FLAVOR
          }),
          body: changelog
        });
      });
    }

    try {
      localStorage.setItem('test', 'true');
    } catch (e) {
      console.log('storage test', e);
      $timeout(() => {
        dimInfoService.show('no-storage', {
          title: $i18next.t('Help.NoStorage'),
          body: $i18next.t('Help.NoStorageMessage'),
          type: 'error',
          hideable: false
        }, 0);
      });
    }

    if (window.BroadcastChannel) {
      const updateChannel = new window.BroadcastChannel('precache-updates');

      const updateMessage = _.once(() => {
        $timeout(() => {
          dimInfoService.show('update-available', {
            title: $i18next.t('Help.UpdateAvailable'),
            body: $i18next.t('Help.UpdateAvailableMessage'),
            type: 'warn',
            hideable: false
          }, 0);
        });
      });

      const messageHandler = () => updateMessage();

      updateChannel.addEventListener('message', messageHandler);
      $scope.$on('$destroy', () => {
        updateChannel.removeEventListener('message', messageHandler);
      });
    }
  };

  $scope.$on('i18nextLanguageChange', () => {
    this.language = `lang-${i18next.language}`;
  });
}
<|endoftext|>


<commit_before>import * as constants from "./constants";
import sort from "immutable-sort";
import {updateTimeLeft, resizeCanvas, createCanvas} from "./gameUI";

let canvasWidth = 0;
let canvasHeight = 0;

let screenWidth;
let screenHeight;

let xScreenPosOnMap = 0;
let yScreenPosOnMap = 0;

let screenToMapRatioWidth;
let screenToMapRatioHeight;

let miniMapWidth;
let miniMapHeight;
let miniMapPosX;

let miniMapScreenPosWidth;
let miniMapScreenPosHeight;

let playerFocused = null;

export let cellFocused = null;

const resourceCacheAsCanvas = [
  [constants.regularColor, constants.regularRGBColor, constants.regularResourceMass],
  [constants.silverColor, constants.silverRGBColor, constants.resourceMass],
  [constants.goldColor, constants.goldRGBColor, constants.resourceMass],
].map(([c, r, m]) => prerenderResource(c, r, m));

function prerenderResource(color, rgba, radius) {
  const canvas = createCanvas();
  resizeCanvas(canvas, radius * 2, radius * 2);
  const context = canvas.getContext("2d");

  const grid = context.createRadialGradient(radius, radius, .5, radius, radius, constants.resourceMass);
  grid.addColorStop(0, color);
  grid.addColorStop(1, rgba);
  drawCircle(context, {x: radius, y: radius}, radius, grid);

  return canvas;
}

function drawCircle(context, position, radius, color, drawBorder = false) {
  context.beginPath();
  context.arc(position.x, position.y, radius, 0, Math.PI * 2, false);
  if(drawBorder){
    context.lineWidth = constants.highlightThickness;
    context.strokeStyle = constants.highlightColor;
    context.stroke();
  }
  context.fillStyle = color;
  context.fill();
}

function writeCellTeamName(playerName, context, position) {
  context.beginPath();
  context.fillStyle = constants.textColor;
  context.font = constants.textStyle;
  context.textAlign = "center";
  context.textBaseline = "middle";
  context.lineWidth = constants.textBorderThickness;
  context.strokeStyle = constants.textBorderColor;

  context.fillText(playerName, position.x, position.y);
  context.strokeText(playerName, position.x, position.y);
}

export function initMap(gameCanvas, map) {
  const screenCanvas = document.getElementById("screenCanvas");

  canvasWidth = map.width;
  canvasHeight = map.height;
  if(!resizeCanvas(gameCanvas, canvasWidth, canvasHeight)) {
    const context = gameCanvas.getContext("2d");
    context.clearRect(0, 0, canvasWidth, canvasHeight);
  }

  screenWidth = document.getElementById("gameDiv").offsetWidth - constants.scrollBarWidth;
  screenHeight = screenWidth * constants.ratioHeight;
  screenCanvas.width = screenWidth;
  screenCanvas.height = screenHeight;
  miniMapWidth = screenWidth / 4;
  miniMapPosX = screenWidth - miniMapWidth;

  screenToMapRatioWidth = gameCanvas.width / screenCanvas.width;
  screenToMapRatioHeight = gameCanvas.height / screenCanvas.height;
  miniMapScreenPosWidth = miniMapWidth / screenToMapRatioWidth;
  miniMapScreenPosHeight = miniMapHeight / screenToMapRatioHeight;
}

export function getPlayerColor(players, currentPlayer) {
  const playerPosition = sort(players, (a, b) => a.id - b.id)
    .findIndex(player => player.id === currentPlayer.id);
  return constants.playerColors[playerPosition];
}

export function drawPlayersOnMap(players, gameCanvas, drawNames) {
  const context = gameCanvas.getContext("2d");
  let cellArray = [];
  for(const player of players) {
    const color = getPlayerColor(players, player);
    for(const cell of player.cells) {
      cellArray.push({
        position: cell.position,
        radius: cell.radius,
        color: color,
        playerName: player.name,
        target: cell.target,
        playerId: player.id,
        id: cell.id
      });
    }
  }

  const cellsToDraw = sort(cellArray, (a, b) => a.radius - b.radius);
  for(const cell of cellsToDraw){
    if(cell.playerId === playerFocused && cell.id === cellFocused.id)
      drawCircle(context, cell.position, cell.radius, cell.color, true);
    else
      drawCircle(context, cell.position, cell.radius, cell.color);
    if (drawNames) writeCellTeamName(cell.playerName, context, cell.position);
    const targetLinesBtn = document.getElementById("targetLinesBtn");
    if (targetLinesBtn.className === "btn btn-primary") drawCellTargetLine(context, cell.position, cell.target, cell.color);
  }
}

export function drawCellTargetLine(context, position, target, color) {
  context.beginPath();
  context.moveTo(position.x, position.y);
  context.lineTo(target.x, target.y);
  context.lineWidth = constants.targetLineThickness;
  context.strokeStyle = color;
  context.stroke();
}

export function drawResourcesOnMap(resources, gameCanvas) {
  const context = gameCanvas.getContext("2d");
  const drawResources = (resources, resourceCanvas) => {
    for(const resource of resources) {
      context.drawImage(resourceCanvas, resource.x, resource.y);
    }
  };

  drawResources(resources.regular, resourceCacheAsCanvas[0]);
  drawResources(resources.silver, resourceCacheAsCanvas[1]);
  drawResources(resources.gold, resourceCacheAsCanvas[2]);
}

export function drawVirusesOnMap(viruses, gameCanvas) {
  const context = gameCanvas.getContext("2d");

  for(const virus of viruses) {
    const position = virus.position;
    const grad = context.createRadialGradient(position.x, position.y, 5, position.x, position.y, virus.radius);
    grad.addColorStop(0, constants.virusColor);
    grad.addColorStop(1, constants.virusEndColor);
    drawVirusShape(position, constants.numberOfSpikes, virus.radius, context, grad);
  }
}

function drawVirusShape(virus, spikes, outerRadius, context, color){
  let rot = Math.PI / 2 * 3;
  let x = virus.x;
  let y = virus.y;
  let step = Math.PI / spikes;
  let innerRadius = outerRadius - 5;

  context.beginPath();
  context.moveTo(virus.x, virus.y - outerRadius);
  for (let i = 0; i < spikes; i++) {
    x = virus.x + Math.cos(rot) * outerRadius;
    y = virus.y + Math.sin(rot) * outerRadius;
    context.lineTo(x, y);
    rot += step;

    x = virus.x + Math.cos(rot) * innerRadius;
    y = virus.y + Math.sin(rot) * innerRadius;
    context.lineTo(x, y);
    rot += step;
  }
  context.lineTo(virus.x, virus.y - outerRadius);
  context.closePath();
  context.fillStyle = color;
  context.fill();
}

export function drawMap(gameCanvas) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenContext = screenCanvas.getContext("2d");
  const screenWidth = screenCanvas.width;
  const screenHeight = screenCanvas.height;

  screenContext.clearRect(0, 0, screenWidth, screenHeight);
  screenContext.drawImage(gameCanvas, xScreenPosOnMap, yScreenPosOnMap, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight);
}

export function initMiniMap(gameCanvas, miniMapCanvas, miniMapTmpCanvas, players) {
  const miniMapContext = miniMapCanvas.getContext("2d");

  miniMapHeight = miniMapWidth * gameCanvas.height / gameCanvas.width;

  //set dimensions
  resizeCanvas(miniMapCanvas, miniMapWidth, miniMapHeight);

  if(!resizeCanvas(miniMapTmpCanvas, canvasWidth, canvasHeight)) {
    const tmpContext = miniMapTmpCanvas.getContext("2d");
    tmpContext.clearRect(0, 0, canvasWidth, canvasHeight);
  }

  //MiniMap background
  miniMapContext.clearRect(0, 0, canvasWidth, canvasHeight);
  miniMapContext.rect(0, 0, canvasWidth, canvasHeight);
  miniMapContext.fillStyle = "rgba(58, 58, 58, 0.85)";
  miniMapContext.fill();

  drawPlayersOnMap(players, miniMapTmpCanvas, false);
  miniMapContext.drawImage(miniMapTmpCanvas, 0, 0, miniMapWidth, miniMapHeight);
}

export function drawMiniMap(gameCanvas, miniMapCanvas) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenContext = screenCanvas.getContext("2d");

  drawMiniMapScreenPos(gameCanvas, miniMapCanvas);
  screenContext.drawImage(miniMapCanvas, miniMapPosX, 0);
  screenCanvas.style.background = "#000";
}

function findMiniMapScreenPositionPlayer(players){
  if(playerFocused != null){
    let player = players.find(p => p.id === playerFocused);
    checkIfCellFocusedStillExists(player.cells);
    setFocusScreen(cellFocused.position);
  }
}

export function findNextCell(cells, playerId){
  let cell = cells[0];
  if(playerFocused === playerId){
    for(let i = 0; i < cells.length; i++){
      if(cellFocused.id === cells[i].id){
        cell = cells[i + 1];

        if(!cell)
          cell = cells[0];
        break;
      }
    }
  }
  else{
    playerFocused = playerId;
  }
  cellFocused = cell;
}

function checkIfCellFocusedStillExists(cells){
  let cell = cells.find(p => p.id === cellFocused.id);
  if(!cell){
    cellFocused = cells[0];
  }
  else{
    cellFocused = cell;
  }
}

function drawMiniMapScreenPos(canvas, miniMapCanvas) {
  const miniMapContext = miniMapCanvas.getContext("2d");

  miniMapContext.strokeStyle = "#fff";
  const xMiniMapPos = miniMapWidth / canvas.width * xScreenPosOnMap;
  const yMiniMapPos = miniMapHeight / canvas.height * yScreenPosOnMap;
  miniMapContext.strokeRect(xMiniMapPos, yMiniMapPos, miniMapScreenPosWidth, miniMapScreenPosHeight);
}

export function setFocusScreen(position) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenWidth = screenCanvas.width;
  const screenHeight = screenCanvas.height;

  let newPosition = {
    x: position.x - (screenWidth / 2),
    y: position.y - (screenHeight / 2)
  };
  newPosition = keepInsideMap(newPosition, canvasWidth, screenWidth, canvasHeight, screenHeight);
  xScreenPosOnMap = newPosition.x;
  yScreenPosOnMap = newPosition.y;
}

function changeScreenPos(mousePos) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenWidth = screenCanvas.width;
  const screenHeight = screenCanvas.height;

  let miniMapPos = {
    x : (mousePos.x - miniMapPosX) - (miniMapScreenPosWidth / 2),
    y : mousePos.y - (miniMapScreenPosHeight / 2)
  };
  miniMapPos = keepInsideMap(miniMapPos, miniMapWidth, miniMapScreenPosWidth, miniMapHeight, miniMapScreenPosHeight);

  xScreenPosOnMap = miniMapPos.x * screenToMapRatioWidth * (screenWidth / miniMapWidth);
  yScreenPosOnMap = miniMapPos.y * screenToMapRatioHeight * (screenHeight / miniMapHeight);
}

function keepInsideMap(pos, bigWidth, smallWidth, bigHeight, smallHeight) {
  if (pos.x < 0) {
    pos.x = 0;
  } else if (pos.x > bigWidth - smallWidth) {
    pos.x = bigWidth - smallWidth;
  }

  if (pos.y < 0) {
    pos.y = 0;
  } else if (pos.y > bigHeight - smallHeight) {
    pos.y = bigHeight - smallHeight;
  }
  return pos;
}

export function initCanvas() {
  const screenCanvas = document.getElementById("screenCanvas");
  let mouseIsDown = false;

  function getMousePos(evt) {
    const rect = screenCanvas.getBoundingClientRect();
    return {
      x : (evt.clientX - rect.left) / (rect.right - rect.left) * screenCanvas.width,
      y : (evt.clientY - rect.top) / (rect.bottom - rect.top) * screenCanvas.height
    };
  }

  function mouseClick(e) {
    const mousePos = getMousePos(e);
    if (mousePos.x > miniMapPosX && mousePos.y < miniMapHeight) {
      playerFocused = null;
      changeScreenPos(mousePos);
    }
  }

  screenCanvas.onmousedown = function(e) {
    mouseIsDown = true;
    mouseClick(e);
  };

  screenCanvas.onmouseup = function(e) {
    if(mouseIsDown) mouseClick(e);
    mouseIsDown = false;
  };

  screenCanvas.onmousemove = function(e) {
    if(!mouseIsDown) return false;
    mouseClick(e);
    return false;
  };
}

export function drawGame(gameState, gameCanvas, miniMapCanvas, miniMapTmpCanvas) {
  initMap(gameCanvas, gameState.map);
  findMiniMapScreenPositionPlayer(gameState.players);
  initMiniMap(gameCanvas, miniMapCanvas, miniMapTmpCanvas, gameState.players);
  drawResourcesOnMap(gameState.resources, gameCanvas);
  drawVirusesOnMap(gameState.viruses, gameCanvas);
  drawPlayersOnMap(gameState.players, gameCanvas, true);
  drawMap(gameCanvas);
  drawMiniMap(gameCanvas, miniMapCanvas);

  updateTimeLeft(gameState.timeLeft);
}

function interpolate(prev, next, ratio) {
  return (1 - ratio) * prev + ratio * next;
}

export function interpolateState(prev, next, ratio) {
  if(ratio <= 0) return prev;
  if(ratio >= 1) return next;

  const current = JSON.parse(JSON.stringify(prev));

  current.players = current.players.map(player => {
    player.cells = player.cells.map(cell => {
      let nextCell = next.players.find(p => p.id === player.id).cells.find(c => c.id === cell.id);
      // Only interpolate position when the cell is not dead
      if(nextCell) {
        cell.position.x = interpolate(cell.position.x, nextCell.position.x, ratio);
        cell.position.y = interpolate(cell.position.y, nextCell.position.y, ratio);
        cell.radius = interpolate(cell.radius, nextCell.radius, ratio);
      } else {
        cell.radius = interpolate(cell.radius, 0, ratio);
      }

      return cell;
    });

    return player;
  });

  return current;
}

export function getCurrentGameId() {
  function getParameterByName(name) {
    const url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
    const results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return "";
    return decodeURIComponent(results[2].replace(/\+/g, " "));
  }

  return getParameterByName("gameId");
}
<commit_msg>JS cache viruses<commit_after>import * as constants from "./constants";
import sort from "immutable-sort";
import {updateTimeLeft, resizeCanvas, createCanvas} from "./gameUI";

let canvasWidth = 0;
let canvasHeight = 0;

let screenWidth;
let screenHeight;

let xScreenPosOnMap = 0;
let yScreenPosOnMap = 0;

let screenToMapRatioWidth;
let screenToMapRatioHeight;

let miniMapWidth;
let miniMapHeight;
let miniMapPosX;

let miniMapScreenPosWidth;
let miniMapScreenPosHeight;

let playerFocused = null;

export let cellFocused = null;

const resourceCache = [
  [constants.regularColor, constants.regularRGBColor, constants.regularResourceMass],
  [constants.silverColor, constants.silverRGBColor, constants.resourceMass],
  [constants.goldColor, constants.goldRGBColor, constants.resourceMass],
].map(([c, r, m]) => prerenderResource(c, r, m));
const virusCache = {};

function prerenderResource(color, rgba, radius) {
  const canvas = createCanvas();
  resizeCanvas(canvas, radius * 2, radius * 2);
  const context = canvas.getContext("2d");

  const grid = context.createRadialGradient(radius, radius, .5, radius, radius, constants.resourceMass);
  grid.addColorStop(0, color);
  grid.addColorStop(1, rgba);
  drawCircle(context, {x: radius, y: radius}, radius, grid);

  return canvas;
}

function drawCircle(context, position, radius, color, drawBorder = false) {
  context.beginPath();
  context.arc(position.x, position.y, radius, 0, Math.PI * 2, false);
  if(drawBorder){
    context.lineWidth = constants.highlightThickness;
    context.strokeStyle = constants.highlightColor;
    context.stroke();
  }
  context.fillStyle = color;
  context.fill();
}

function writeCellTeamName(playerName, context, position) {
  context.beginPath();
  context.fillStyle = constants.textColor;
  context.font = constants.textStyle;
  context.textAlign = "center";
  context.textBaseline = "middle";
  context.lineWidth = constants.textBorderThickness;
  context.strokeStyle = constants.textBorderColor;

  context.fillText(playerName, position.x, position.y);
  context.strokeText(playerName, position.x, position.y);
}

export function initMap(gameCanvas, map) {
  const screenCanvas = document.getElementById("screenCanvas");

  canvasWidth = map.width;
  canvasHeight = map.height;
  if(!resizeCanvas(gameCanvas, canvasWidth, canvasHeight)) {
    const context = gameCanvas.getContext("2d");
    context.clearRect(0, 0, canvasWidth, canvasHeight);
  }

  screenWidth = document.getElementById("gameDiv").offsetWidth - constants.scrollBarWidth;
  screenHeight = screenWidth * constants.ratioHeight;
  screenCanvas.width = screenWidth;
  screenCanvas.height = screenHeight;
  miniMapWidth = screenWidth / 4;
  miniMapPosX = screenWidth - miniMapWidth;

  screenToMapRatioWidth = gameCanvas.width / screenCanvas.width;
  screenToMapRatioHeight = gameCanvas.height / screenCanvas.height;
  miniMapScreenPosWidth = miniMapWidth / screenToMapRatioWidth;
  miniMapScreenPosHeight = miniMapHeight / screenToMapRatioHeight;
}

export function getPlayerColor(players, currentPlayer) {
  const playerPosition = sort(players, (a, b) => a.id - b.id)
    .findIndex(player => player.id === currentPlayer.id);
  return constants.playerColors[playerPosition];
}

export function drawPlayersOnMap(players, gameCanvas, drawNames) {
  const context = gameCanvas.getContext("2d");
  let cellArray = [];
  for(const player of players) {
    const color = getPlayerColor(players, player);
    for(const cell of player.cells) {
      cellArray.push({
        position: cell.position,
        radius: cell.radius,
        color: color,
        playerName: player.name,
        target: cell.target,
        playerId: player.id,
        id: cell.id
      });
    }
  }

  const cellsToDraw = sort(cellArray, (a, b) => a.radius - b.radius);
  for(const cell of cellsToDraw){
    if(cell.playerId === playerFocused && cell.id === cellFocused.id)
      drawCircle(context, cell.position, cell.radius, cell.color, true);
    else
      drawCircle(context, cell.position, cell.radius, cell.color);
    if (drawNames) writeCellTeamName(cell.playerName, context, cell.position);
    const targetLinesBtn = document.getElementById("targetLinesBtn");
    if (targetLinesBtn.className === "btn btn-primary") drawCellTargetLine(context, cell.position, cell.target, cell.color);
  }
}

export function drawCellTargetLine(context, position, target, color) {
  context.beginPath();
  context.moveTo(position.x, position.y);
  context.lineTo(target.x, target.y);
  context.lineWidth = constants.targetLineThickness;
  context.strokeStyle = color;
  context.stroke();
}

export function drawResourcesOnMap(resources, gameCanvas) {
  const context = gameCanvas.getContext("2d");
  const drawResources = (resources, resourceCanvas) => {
    for(const resource of resources) {
      context.drawImage(resourceCanvas, resource.x, resource.y);
    }
  };

  drawResources(resources.regular, resourceCache[0]);
  drawResources(resources.silver, resourceCache[1]);
  drawResources(resources.gold, resourceCache[2]);
}

export function drawVirusesOnMap(viruses, gameCanvas) {
  const context = gameCanvas.getContext("2d");

  for(const virus of viruses) {
    let canvas = virusCache[virus.radius];
    if (!canvas) {
      canvas = drawVirusShape(virus.radius);
      virusCache[virus.radius] = canvas;
    }
    context.drawImage(canvas, virus.position.x, virus.position.y);
  }
}

function drawVirusShape(radius) {
  const canvas = createCanvas();
  resizeCanvas(canvas, radius * 2 + 5, radius * 2 + 5);
  const context = canvas.getContext("2d");
  const color = context.createRadialGradient(radius, radius, 5, radius, radius, radius);
  color.addColorStop(0, constants.virusColor);
  color.addColorStop(1, constants.virusEndColor);
  let rot = Math.PI / 2 * 3;
  let x = radius;
  let y = radius;
  let step = Math.PI / constants.numberOfSpikes;
  let innerRadius = radius - 5;

  context.beginPath();
  context.moveTo(radius, 0);
  for (let i = 0; i < constants.numberOfSpikes; i++) {
    x = radius + Math.cos(rot) * radius;
    y = radius + Math.sin(rot) * radius;
    context.lineTo(x, y);
    rot += step;

    x = radius + Math.cos(rot) * innerRadius;
    y = radius + Math.sin(rot) * innerRadius;
    context.lineTo(x, y);
    rot += step;
  }
  context.lineTo(radius, 0);
  context.closePath();
  context.fillStyle = color;
  context.fill();
  return canvas;
}

export function drawMap(gameCanvas) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenContext = screenCanvas.getContext("2d");
  const screenWidth = screenCanvas.width;
  const screenHeight = screenCanvas.height;

  screenContext.clearRect(0, 0, screenWidth, screenHeight);
  screenContext.drawImage(gameCanvas, xScreenPosOnMap, yScreenPosOnMap, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight);
}

export function initMiniMap(gameCanvas, miniMapCanvas, miniMapTmpCanvas, players) {
  const miniMapContext = miniMapCanvas.getContext("2d");

  miniMapHeight = miniMapWidth * gameCanvas.height / gameCanvas.width;

  //set dimensions
  resizeCanvas(miniMapCanvas, miniMapWidth, miniMapHeight);

  if(!resizeCanvas(miniMapTmpCanvas, canvasWidth, canvasHeight)) {
    const tmpContext = miniMapTmpCanvas.getContext("2d");
    tmpContext.clearRect(0, 0, canvasWidth, canvasHeight);
  }

  //MiniMap background
  miniMapContext.clearRect(0, 0, canvasWidth, canvasHeight);
  miniMapContext.rect(0, 0, canvasWidth, canvasHeight);
  miniMapContext.fillStyle = "rgba(58, 58, 58, 0.85)";
  miniMapContext.fill();

  drawPlayersOnMap(players, miniMapTmpCanvas, false);
  miniMapContext.drawImage(miniMapTmpCanvas, 0, 0, miniMapWidth, miniMapHeight);
}

export function drawMiniMap(gameCanvas, miniMapCanvas) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenContext = screenCanvas.getContext("2d");

  drawMiniMapScreenPos(gameCanvas, miniMapCanvas);
  screenContext.drawImage(miniMapCanvas, miniMapPosX, 0);
  screenCanvas.style.background = "#000";
}

function findMiniMapScreenPositionPlayer(players){
  if(playerFocused != null){
    let player = players.find(p => p.id === playerFocused);
    checkIfCellFocusedStillExists(player.cells);
    setFocusScreen(cellFocused.position);
  }
}

export function findNextCell(cells, playerId){
  let cell = cells[0];
  if(playerFocused === playerId){
    for(let i = 0; i < cells.length; i++){
      if(cellFocused.id === cells[i].id){
        cell = cells[i + 1];

        if(!cell)
          cell = cells[0];
        break;
      }
    }
  }
  else{
    playerFocused = playerId;
  }
  cellFocused = cell;
}

function checkIfCellFocusedStillExists(cells){
  let cell = cells.find(p => p.id === cellFocused.id);
  if(!cell){
    cellFocused = cells[0];
  }
  else{
    cellFocused = cell;
  }
}

function drawMiniMapScreenPos(canvas, miniMapCanvas) {
  const miniMapContext = miniMapCanvas.getContext("2d");

  miniMapContext.strokeStyle = "#fff";
  const xMiniMapPos = miniMapWidth / canvas.width * xScreenPosOnMap;
  const yMiniMapPos = miniMapHeight / canvas.height * yScreenPosOnMap;
  miniMapContext.strokeRect(xMiniMapPos, yMiniMapPos, miniMapScreenPosWidth, miniMapScreenPosHeight);
}

export function setFocusScreen(position) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenWidth = screenCanvas.width;
  const screenHeight = screenCanvas.height;

  let newPosition = {
    x: position.x - (screenWidth / 2),
    y: position.y - (screenHeight / 2)
  };
  newPosition = keepInsideMap(newPosition, canvasWidth, screenWidth, canvasHeight, screenHeight);
  xScreenPosOnMap = newPosition.x;
  yScreenPosOnMap = newPosition.y;
}

function changeScreenPos(mousePos) {
  const screenCanvas = document.getElementById("screenCanvas");
  const screenWidth = screenCanvas.width;
  const screenHeight = screenCanvas.height;

  let miniMapPos = {
    x : (mousePos.x - miniMapPosX) - (miniMapScreenPosWidth / 2),
    y : mousePos.y - (miniMapScreenPosHeight / 2)
  };
  miniMapPos = keepInsideMap(miniMapPos, miniMapWidth, miniMapScreenPosWidth, miniMapHeight, miniMapScreenPosHeight);

  xScreenPosOnMap = miniMapPos.x * screenToMapRatioWidth * (screenWidth / miniMapWidth);
  yScreenPosOnMap = miniMapPos.y * screenToMapRatioHeight * (screenHeight / miniMapHeight);
}

function keepInsideMap(pos, bigWidth, smallWidth, bigHeight, smallHeight) {
  if (pos.x < 0) {
    pos.x = 0;
  } else if (pos.x > bigWidth - smallWidth) {
    pos.x = bigWidth - smallWidth;
  }

  if (pos.y < 0) {
    pos.y = 0;
  } else if (pos.y > bigHeight - smallHeight) {
    pos.y = bigHeight - smallHeight;
  }
  return pos;
}

export function initCanvas() {
  const screenCanvas = document.getElementById("screenCanvas");
  let mouseIsDown = false;

  function getMousePos(evt) {
    const rect = screenCanvas.getBoundingClientRect();
    return {
      x : (evt.clientX - rect.left) / (rect.right - rect.left) * screenCanvas.width,
      y : (evt.clientY - rect.top) / (rect.bottom - rect.top) * screenCanvas.height
    };
  }

  function mouseClick(e) {
    const mousePos = getMousePos(e);
    if (mousePos.x > miniMapPosX && mousePos.y < miniMapHeight) {
      playerFocused = null;
      changeScreenPos(mousePos);
    }
  }

  screenCanvas.onmousedown = function(e) {
    mouseIsDown = true;
    mouseClick(e);
  };

  screenCanvas.onmouseup = function(e) {
    if(mouseIsDown) mouseClick(e);
    mouseIsDown = false;
  };

  screenCanvas.onmousemove = function(e) {
    if(!mouseIsDown) return false;
    mouseClick(e);
    return false;
  };
}

export function drawGame(gameState, gameCanvas, miniMapCanvas, miniMapTmpCanvas) {
  initMap(gameCanvas, gameState.map);
  findMiniMapScreenPositionPlayer(gameState.players);
  initMiniMap(gameCanvas, miniMapCanvas, miniMapTmpCanvas, gameState.players);
  drawResourcesOnMap(gameState.resources, gameCanvas);
  drawVirusesOnMap(gameState.viruses, gameCanvas);
  drawPlayersOnMap(gameState.players, gameCanvas, true);
  drawMap(gameCanvas);
  drawMiniMap(gameCanvas, miniMapCanvas);

  updateTimeLeft(gameState.timeLeft);
}

function interpolate(prev, next, ratio) {
  return (1 - ratio) * prev + ratio * next;
}

export function interpolateState(prev, next, ratio) {
  if(ratio <= 0) return prev;
  if(ratio >= 1) return next;

  const current = JSON.parse(JSON.stringify(prev));

  current.players = current.players.map(player => {
    player.cells = player.cells.map(cell => {
      let nextCell = next.players.find(p => p.id === player.id).cells.find(c => c.id === cell.id);
      // Only interpolate position when the cell is not dead
      if(nextCell) {
        cell.position.x = interpolate(cell.position.x, nextCell.position.x, ratio);
        cell.position.y = interpolate(cell.position.y, nextCell.position.y, ratio);
        cell.radius = interpolate(cell.radius, nextCell.radius, ratio);
      } else {
        cell.radius = interpolate(cell.radius, 0, ratio);
      }

      return cell;
    });

    return player;
  });

  return current;
}

export function getCurrentGameId() {
  function getParameterByName(name) {
    const url = window.location.href;
    name = name.replace(/[\[\]]/g, "\\$&");
    const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)");
    const results = regex.exec(url);
    if (!results) return null;
    if (!results[2]) return "";
    return decodeURIComponent(results[2].replace(/\+/g, " "));
  }

  return getParameterByName("gameId");
}
<|endoftext|>


<commit_before>(function() {
/* jshint ignore:start */

/* jshint ignore:end */

define("require_vuejs", function(){
    return plugin;
});
/*vim: set ts=4 ex=4 tabshift=4 expandtab :*/

/*globals: define, require */
/*
 * css-parser.js
 *
 * Distributed under terms of the MIT license.
 */
/* jshint ignore:start */

/* jshint ignore:end */

var css_parser = (function(){
    "use strict";
    var idCount = 0;

    var appendCSSStyle = function(css, id) {
        if(css && typeof document !== "undefined") {
            var style = document.getElementById("style_" + id);
            if (style) {
                return style;
            }
            style = document.createElement("style");
            var head = document.head || document.getElementsByTagName("head")[0];

            style.type = "text/css";
            style.id = "style_" + id;
            if (style.styleSheet){
                style.styleSheet.cssText = css;
            } else {
                style.appendChild(document.createTextNode(css));
            }
            head.appendChild(style);
            return style;
        }
    };

    var functionString = function(parsed) {
        if (typeof document === "undefined")  // you are running optimization ( r.js )
            var document = createDocumentMock();

        var css = parsed.css;
        var attributeId = "data-style" + idCount;
        if ( css === false ) {
            return "";
        } else {
            if (parsed.scoped) {
                css = css
                    .replace(/(^|,)( *)\.([^ \s\n\r\t]+)[\s{]/gm, "$1$2[" + attributeId + "].$3");
            }
            css = css // turn into one line
                .replace(/([^\\])'/g, "$1\\'")
                .replace(/''/g, "'\\'")
                .replace(/[\n\r]+/g, " ")
                .replace(/ {2,20}/g, " ");
        }

        var result = "(" + appendCSSStyle.toString() + ")('" + css + "', " + idCount + ");\n";
        return result;
    };

    var createDocumentMock = function() {
        return {
            createElement: function() {},
            head: {},
            getElementsByTagName: function() {},
            createTextNode: function() {}
        };
    };

    var parseElement = function(doc) {
        idCount = idCount + 1;
        var queryResult = doc.getElementsByTagName("style");
        if (!queryResult || !queryResult.length) {
            return {
                id: 0,
                css: "",
                scoped: false,
                functionString: ""
            };
        }
        var style = queryResult[0];
        var scoped = style.hasAttribute("scoped");

        var result = {};
        result.css = style.innerHTML;
        result.id = idCount;
        result.scoped = scoped;
        result.functionString =  functionString(result);

        return result;
    };

    return {
        parseElement: parseElement
    };
})();

/*
 * template-parser.js
 *
 * Distributed under terms of the MIT license.
 */
/* jshint ignore:start */

/* jshint ignore:end */

var template_parser = (function(){
    var isOnBrowser = !!document;

    var parseOnBrowser = function(text, css_result) {
        var root;
        if (typeof text === "string") {
            root = document.createElement("div");
            root.insertAdjacentHTML("afterbegin", text);
        } else {
            root = text;
        }

        var queryResult = root.getElementsByTagName("template");
        if (!queryResult || !queryResult.length) {
            return "";
        }
        var template = queryResult[0];

        if (css_result.scoped) {
            template.innerHTML = template
                .innerHTML.replace(/([ \t]+class=['"])/g, " data-style" + css_result.id + "$1");
        }

        return clearTemplateText(template.innerHTML);
    };

    var clearTemplateText = function(text) {
        if (!text) {  return ""; }
        return text
            .replace(/([^\\])'/g, "$1\\'")
            .replace(/^(.*)$/mg, "'$1' + ") // encapsulate template code between ' and put a +
            .replace(/ {2,20}/g, " ") + "''";
    };

    var extractTemplate = function(text) {
        var start = text.indexOf("<template>");
        var end   = text.lastIndexOf("</template>");
        return clearTemplateText(text.substring(start + 10, end));
    };

    if (isOnBrowser) {
        extractTemplate = parseOnBrowser;
    }

    return {
        extractTemplate: extractTemplate
    };

})();

/*
 * vue.js
 *
 * Distributed under terms of the MIT license.
 */
/* global Promise */
/* jshint ignore:start */

/* jshint ignore:end */

var plugin = (function(){
    "use strict";

    var modulesLoaded = {};

    var functionTemplate = ["(function(template){", "})("];

    var parse = function(text) {
        var doc = document.implementation.createHTMLDocument("");
        doc.write(text);
        var scriptElement = doc.getElementsByTagName("script")[0];
        var source = scriptElement.innerHTML;
        var css_result = css_parser.parseElement(doc);
        var template = template_parser.extractTemplate(doc, css_result);
        var functionString = css_result.functionString;
        if (!source || source.length < 10) {
            source = scriptElement.src;
            source = "define(['" + source + "'], function(comp) {\n comp.template = template;\n return comp;\n});\n";
        }

        return functionTemplate[0] +
         source +
          functionString +
          functionTemplate[1] +
          template + ");";
    };

    var loadLocal = function(url, name) {
        var fs = require.nodeRequire("fs");
        var text = fs.readFileSync(url, "utf-8");
        if(text[0] === "\uFEFF") { // remove BOM ( Byte Mark Order ) from utf8 files
            text = text.substring(1);
        }
        var parsed = parse(text).replace(/(define\()\s*(\[.*)/, "$1\"vue!" + name + "\", $2");
        return parsed;
    };

    return {
        normalize: function(name, normalize) {
            return normalize(name);
        },
        write: function(pluginName, moduleName, write) {
            write.asModule(pluginName + "!" + moduleName, modulesLoaded[moduleName]);
        },
        load: function (name, req, onload, config) {
            var url, extension;

            if (config.paths && config.paths[name]) {
                name = config.paths[name];
            }

            // if file name has an extension, don't add .vue
            if(/.*(\.vue)|(\.html?)/.test(name)) {
                extension = "";
            } else {
                extension = ".vue";
            }

            url = req.toUrl(name + extension);

            // this is used to browser to create a way to debug the file
            var sourceHeader = config.isBuild?"" : "//# sourceURL=" + location.origin + url + "\n";
            var loadRemote;

            if(config.isBuild) {
                loadRemote = function(url, callback) {
                    callback = callback || function() {};
                    return new Promise(function(resolve, reject) {
                        try {
                            var fs = require.nodeRequire("fs");
                            var text = fs.readFileSync(url, "utf-8").toString();
                            if(text[0] === "\uFEFF") { // remove BOM ( Byte Mark Order ) from utf8 files
                                text = text.substring(1);
                            }
                            var parsed = parse(text).replace(/(define\()\s*(\[.*)/, "$1\"" + name + "\", $2");
                            callback(parsed);
                            resolve(parsed);
                        } catch(error) {
                            reject(error);
                        }
                    });
                };
            } else {
                loadRemote = function(path, callback) {
                    callback = callback || function() {};
                    return new Promise(function(resolve, reject) {
                        var xhttp = new XMLHttpRequest();
                        xhttp.timeout = (
                            (config.waitSeconds || config.timeout) || 3
                        ) * 1000;
                        xhttp.onreadystatechange = function() {
                            if (xhttp.readyState === 4
                          && xhttp.status < 400) {
                                var result = parse(xhttp.responseText);
                                callback(result);
                                resolve(result);
                            }
                        };
                        xhttp.ontimeout = function() {
                            var error = new Error("Timeout loading: " + path);
                            callback({}, error);
                            reject(error);
                            throw error;
                        };
                        xhttp.open("GET", path, true);
                        xhttp.send();
                    });
                };
            }

            req([], function() {
                if(config.isBuild) {
                    var data = loadLocal(url, name);
                    modulesLoaded[name] = data;
                    onload.fromText(data);
                } else {
                    loadRemote(url)
                        .then(function(text, error){
                            if (error) {
                                onload.error(error);
                                throw new Error("Error loading: " + url);
                            }
                            modulesLoaded[name] = sourceHeader + text;
                            onload.fromText(modulesLoaded[name]);
                        })
                        .catch(function(error) {
                            var message = error ? error.message || "Error: " : "Error !";
                            message += "\n Url: " + url;
                            throw new Error(message);
                        });
                }
            });
        }
    };
})();

/**
 * vue.js
 * Copyright (C) 2017
 *
 * Distributed under terms of the MIT license.
 */
/* jshint ignore:start */

/* jshint ignore:end */

define("vue", function(){
    return plugin;
});
/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab : */

})();
<commit_msg>fix firefox<commit_after>(function() {
/* jshint ignore:start */

/* jshint ignore:end */

define("require_vuejs", function(){
    return plugin;
});
/*vim: set ts=4 ex=4 tabshift=4 expandtab :*/

/*globals: define, require */
/*
 * css-parser.js
 *
 * Distributed under terms of the MIT license.
 */
/* jshint ignore:start */

/* jshint ignore:end */

var css_parser = (function(){
    "use strict";
    var idCount = 0;

    var appendCSSStyle = function(css, id) {
        if(css && typeof document !== "undefined") {
            var style = document.getElementById("style_" + id);
            if (style) {
                return style;
            }
            style = document.createElement("style");
            var head = document.head || document.getElementsByTagName("head")[0];

            style.type = "text/css";
            style.id = "style_" + id;
            if (style.styleSheet){
                style.styleSheet.cssText = css;
            } else {
                style.appendChild(document.createTextNode(css));
            }
            head.appendChild(style);
            return style;
        }
    };

    var functionString = function(parsed) {
        if (typeof document === "undefined")  // you are running optimization ( r.js )
            var document = createDocumentMock();

        var css = parsed.css;
        var attributeId = "data-style" + idCount;
        if ( css === false ) {
            return "";
        } else {
            if (parsed.scoped) {
                css = css
                    .replace(/(^|,)( *)\.([^ \s\n\r\t]+)[\s{]/gm, "$1$2[" + attributeId + "].$3");
            }
            css = css // turn into one line
                .replace(/([^\\])'/g, "$1\\'")
                .replace(/''/g, "'\\'")
                .replace(/[\n\r]+/g, " ")
                .replace(/ {2,20}/g, " ");
        }

        var result = "(" + appendCSSStyle.toString() + ")('" + css + "', " + idCount + ");\n";
        return result;
    };

    var createDocumentMock = function() {
        return {
            createElement: function() {},
            head: {},
            getElementsByTagName: function() {},
            createTextNode: function() {}
        };
    };

    var parseElement = function(doc) {
        idCount = idCount + 1;
        var queryResult = doc.getElementsByTagName("style");
        if (!queryResult || !queryResult.length) {
            return {
                id: 0,
                css: "",
                scoped: false,
                functionString: ""
            };
        }
        var style = queryResult[0];
        var scoped = style.hasAttribute("scoped");

        var result = {};
        result.css = style.innerHTML;
        result.id = idCount;
        result.scoped = scoped;
        result.functionString =  functionString(result);

        return result;
    };

    return {
        parseElement: parseElement
    };
})();

/*
 * template-parser.js
 *
 * Distributed under terms of the MIT license.
 */
/* jshint ignore:start */

/* jshint ignore:end */

var template_parser = (function(){
    var isOnBrowser = !!document;

    var parseOnBrowser = function(text, css_result) {
        var root;
        if (typeof text === "string") {
            root = document.createElement("div");
            root.insertAdjacentHTML("afterbegin", text);
        } else {
            root = text;
        }

        var queryResult = root.getElementsByTagName("template");
        if (!queryResult || !queryResult.length) {
            return "";
        }
        var template = queryResult[0];

        if (css_result.scoped) {
            template.innerHTML = template
                .innerHTML.replace(/([ \t]+class=['"])/g, " data-style" + css_result.id + "$1");
        }

        return clearTemplateText(template.innerHTML);
    };

    var clearTemplateText = function(text) {
        if (!text) {  return ""; }
        return text
            .replace(/([^\\])'/g, "$1\\'")
            .replace(/^(.*)$/mg, "'$1' + ") // encapsulate template code between ' and put a +
            .replace(/ {2,20}/g, " ") + "''";
    };

    var extractTemplate = function(text) {
        var start = text.indexOf("<template>");
        var end   = text.lastIndexOf("</template>");
        return clearTemplateText(text.substring(start + 10, end));
    };

    if (isOnBrowser) {
        extractTemplate = parseOnBrowser;
    }

    return {
        extractTemplate: extractTemplate
    };

})();

/*
 * vue.js
 *
 * Distributed under terms of the MIT license.
 */
/* global Promise */
/* jshint ignore:start */

/* jshint ignore:end */

var plugin = (function(){
    "use strict";

    var modulesLoaded = {};

    var functionTemplate = ["(function(template){", "})("];

    var parse = function(text) {
        var doc = document.implementation.createHTMLDocument("");
        doc.body.innerHTML = text;
        var scriptElement = doc.getElementsByTagName("script")[0];
        var source = scriptElement.innerHTML;
        var css_result = css_parser.parseElement(doc);
        var template = template_parser.extractTemplate(doc, css_result);
        var functionString = css_result.functionString;
        if (!source || source.length < 10) {
            source = scriptElement.src;
            source = "define(['" + source + "'], function(comp) {\n comp.template = template;\n return comp;\n});\n";
        }

        return functionTemplate[0] +
         source +
          functionString +
          functionTemplate[1] +
          template + ");";
    };

    var loadLocal = function(url, name) {
        var fs = require.nodeRequire("fs");
        var text = fs.readFileSync(url, "utf-8");
        if(text[0] === "\uFEFF") { // remove BOM ( Byte Mark Order ) from utf8 files
            text = text.substring(1);
        }
        var parsed = parse(text).replace(/(define\()\s*(\[.*)/, "$1\"vue!" + name + "\", $2");
        return parsed;
    };

    return {
        normalize: function(name, normalize) {
            return normalize(name);
        },
        write: function(pluginName, moduleName, write) {
            write.asModule(pluginName + "!" + moduleName, modulesLoaded[moduleName]);
        },
        load: function (name, req, onload, config) {
            var url, extension;

            if (config.paths && config.paths[name]) {
                name = config.paths[name];
            }

            // if file name has an extension, don't add .vue
            if(/.*(\.vue)|(\.html?)/.test(name)) {
                extension = "";
            } else {
                extension = ".vue";
            }

            url = req.toUrl(name + extension);

            // this is used to browser to create a way to debug the file
            var sourceHeader = config.isBuild?"" : "//# sourceURL=" + location.origin + url + "\n";
            var loadRemote;

            if(config.isBuild) {
                loadRemote = function(url, callback) {
                    callback = callback || function() {};
                    return new Promise(function(resolve, reject) {
                        try {
                            var fs = require.nodeRequire("fs");
                            var text = fs.readFileSync(url, "utf-8").toString();
                            if(text[0] === "\uFEFF") { // remove BOM ( Byte Mark Order ) from utf8 files
                                text = text.substring(1);
                            }
                            var parsed = parse(text).replace(/(define\()\s*(\[.*)/, "$1\"" + name + "\", $2");
                            callback(parsed);
                            resolve(parsed);
                        } catch(error) {
                            reject(error);
                        }
                    });
                };
            } else {
                loadRemote = function(path, callback) {
                    callback = callback || function() {};
                    return new Promise(function(resolve, reject) {
                        var xhttp = new XMLHttpRequest();
                        xhttp.timeout = (
                            (config.waitSeconds || config.timeout) || 3
                        ) * 1000;
                        xhttp.onreadystatechange = function() {
                            if (xhttp.readyState === 4
                          && xhttp.status < 400) {
                                var result = parse(xhttp.responseText);
                                callback(result);
                                resolve(result);
                            }
                        };
                        xhttp.ontimeout = function() {
                            var error = new Error("Timeout loading: " + path);
                            callback({}, error);
                            reject(error);
                            throw error;
                        };
                        xhttp.open("GET", path, true);
                        xhttp.send();
                    });
                };
            }

            req([], function() {
                if(config.isBuild) {
                    var data = loadLocal(url, name);
                    modulesLoaded[name] = data;
                    onload.fromText(data);
                } else {
                    loadRemote(url)
                        .then(function(text, error){
                            if (error) {
                                onload.error(error);
                                throw new Error("Error loading: " + url);
                            }
                            modulesLoaded[name] = sourceHeader + text;
                            onload.fromText(modulesLoaded[name]);
                        })
                        .catch(function(error) {
                            var message = error ? error.message || "Error: " : "Error !";
                            message += "\n Url: " + url;
                            throw new Error(message);
                        });
                }
            });
        }
    };
})();

/**
 * vue.js
 * Copyright (C) 2017
 *
 * Distributed under terms of the MIT license.
 */
/* jshint ignore:start */

/* jshint ignore:end */

define("vue", function(){
    return plugin;
});
/* vim: set tabstop=4 softtabstop=4 shiftwidth=4 expandtab : */

})();
<|endoftext|>


<commit_before>
/*!
 * Ext JS SS Library 0.0.1
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * http://www.extjs.com/license
 */

/**
 * Module dependencies.
 */

var parse = require('url').parse,
    Buffer = require('buffer').Buffer,
    http = require('http');

/**
 * JSON-RPC version.
 */

var VERSION = exports.VERSION = '2.0';

/**
 * JSON parse error.
 */

var PARSE_ERROR = exports.PARSE_ERROR = -32700

/**
 * Invalid request due to invalid or missing properties.
 */

var INVALID_REQUEST = exports.INVALID_REQUEST = -32600

/**
 * Service method does not exist.
 */

var METHOD_NOT_FOUND = exports.METHOD_NOT_FOUND = -32601

/**
 * Invalid parameters.
 */

var INVALID_PARAMS = exports.INVALID_PARAMS = -32602

/**
 * Internal JSON-RPC error.
 */

var INTERNAL_ERROR = exports.INTERNAL_ERROR = -32603

/**
 * Default error messages.
 */

var errorMessages = exports.errorMessages = {}
errorMessages[PARSE_ERROR] = 'Parse Error.'
errorMessages[INVALID_REQUEST] = 'Invalid Request.'
errorMessages[METHOD_NOT_FOUND] = 'Method Not Found.'
errorMessages[INVALID_PARAMS] = 'Invalid Params.'
errorMessages[INTERNAL_ERROR] = 'Internal Error.'

exports.setup = function(env, procedures){
    this.procedures = procedures || {};
}

exports.handle = function(err, req, res, next){
    var contentType = req.headers['content-type'] || '';
    if (!err && req.method === 'POST' && contentType === 'application/json') {
        var data = '';
        req.setEncoding('utf8');
        req.addListener('data', function(chunk) { data += chunk; });
        req.addListener('end', function() {
            req.rawBody = data;
            try {
                req.body = JSON.parse(data);
                if (validRequest(req)) {

                } else {
                    respond(res, { error: { code: INVALID_REQUEST }});
                }
            } catch (err) {
                respond(res, { error: { code: PARSE_ERROR }});
            }
        });
    } else {
        next();
    }
};

function validRequest(req){
    return req.body.jsonrpc === VERSION && typeof req.body.id === 'number';
}

function respond(res, obj){
    obj.id = obj.id || null;
    obj.jsonrpc = VERSION;
    if (obj.error && !obj.error.message) {
        obj.error.message = errorMessages[obj.error.code];
    }
    var body = JSON.stringify(obj);
    res.writeHead(200, {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body)
    })
    res.end(body);
};<commit_msg>jslint-ifying jsonrpc<commit_after>
/*!
 * Ext JS SS Library 0.0.1
 * Copyright(c) 2006-2010 Ext JS, Inc.
 * licensing@extjs.com
 * http://www.extjs.com/license
 */

/**
 * Module dependencies.
 */

var parse = require('url').parse,
    Buffer = require('buffer').Buffer,
    http = require('http');

/**
 * JSON-RPC version.
 */

var VERSION = exports.VERSION = '2.0';

/**
 * JSON parse error.
 */

var PARSE_ERROR = exports.PARSE_ERROR = -32700;

/**
 * Invalid request due to invalid or missing properties.
 */

var INVALID_REQUEST = exports.INVALID_REQUEST = -32600;

/**
 * Service method does not exist.
 */

var METHOD_NOT_FOUND = exports.METHOD_NOT_FOUND = -32601;

/**
 * Invalid parameters.
 */

var INVALID_PARAMS = exports.INVALID_PARAMS = -32602;

/**
 * Internal JSON-RPC error.
 */

var INTERNAL_ERROR = exports.INTERNAL_ERROR = -32603;

/**
 * Default error messages.
 */

var errorMessages = exports.errorMessages = {};
errorMessages[PARSE_ERROR] = 'Parse Error.';
errorMessages[INVALID_REQUEST] = 'Invalid Request.';
errorMessages[METHOD_NOT_FOUND] = 'Method Not Found.';
errorMessages[INVALID_PARAMS] = 'Invalid Params.';
errorMessages[INTERNAL_ERROR] = 'Internal Error.';

exports.setup = function(env, procedures){
    this.procedures = procedures || {};
};

exports.handle = function(err, req, res, next){
    var contentType = req.headers['content-type'] || '';
    if (!err && req.method === 'POST' && contentType === 'application/json') {
        var data = '';
        req.setEncoding('utf8');
        req.addListener('data', function(chunk) { data += chunk; });
        req.addListener('end', function() {
            req.rawBody = data;
            try {
                req.body = JSON.parse(data);
                if (validRequest(req)) {

                } else {
                    respond(res, { error: { code: INVALID_REQUEST }});
                }
            } catch (err) {
                respond(res, { error: { code: PARSE_ERROR }});
            }
        });
    } else {
        next();
    }
};

/**
 * Check if the given request is a valid
 * JSON remote procedure call.
 *
 *   - "jsonrpc" must match the supported version ('2.0')
 *   - "id" must be numeric
 *
 * @param  {ServerRequest} req
 * @return {Boolean}
 * @api private
 */

function validRequest(req){
    return req.body.jsonrpc === VERSION && typeof req.body.id === 'number';
}

/**
 * Respond with the given JSON object.
 *
 * @param  {ServerResponse} res
 * @param  {Object} obj
 * @api private
 */

function respond(res, obj){
    obj.id = obj.id || null;
    obj.jsonrpc = VERSION;
    if (obj.error && !obj.error.message) {
        obj.error.message = errorMessages[obj.error.code];
    }
    var body = JSON.stringify(obj);
    res.writeHead(200, {
        'Content-Type': 'application/json',
        'Content-Length': Buffer.byteLength(body)
    });
    res.end(body);
}<|endoftext|>


<commit_before>App.Toolbar.Actions.AddRoute = function() {

  this.handlers = [];
  this.map = App.Map.instance();
  this.lastMarker = -1;

  var lineSymbol = {
    path: google.maps.SymbolPath.FORWARD_OPEN_ARROW
  };

  var polyOptions = {
    strokeColor: '#000000',
    strokeOpacity: 1.0,
    strokeWeight: 3,
    map: this.map,
    editable: false,
    icons: [{
      icon: lineSymbol,
      offset: '100%'
    }]
  };

  this.poly = new google.maps.Polyline(polyOptions);
};

App.Toolbar.Actions.AddRoute.prototype.execute = function() {
  this.bindEvents();
  this.map.setOptions({ draggableCursor: 'crosshair' });
};

App.Toolbar.Actions.AddRoute.prototype.bindEvents = function() {

  var path = this.poly.getPath();

  $.each(App.Map.markers, function(i, marker) {
    (function(i, marker) {

      var handler = google.maps.event.addListener(marker, 'click', function(e) {
        if (marker.poly === this.lastMarker) {
          path.removeAt(marker.poly);
          this.lastMarker--;
          marker.poly = -1;
        } else {
          path.push(e.latLng);
          this.lastMarker++;
          marker.poly = this.lastMarker;
        }
      }.bind(this));

      this.handlers.push(handler);

    }.bind(this)(i, marker));
  }.bind(this));
};

App.Toolbar.Actions.AddRoute.prototype.addLine = function(e) {
  var path = this.poly.getPath();
  path.push(e.latLng);
};

App.Toolbar.Actions.AddRoute.prototype.reset = function() {
  this.map.setOptions({ draggableCursor: null });
  $.each(this.handlers, function(i, handler) {
    handler.remove();
  });
};<commit_msg>Small route fix<commit_after>App.Toolbar.Actions.AddRoute = function() {

  this.handlers = [];
  this.map = App.Map.instance();
  this.lastMarker = -1;

  var lineSymbol = {
    path: google.maps.SymbolPath.FORWARD_OPEN_ARROW
  };

  var polyOptions = {
    strokeColor: '#000000',
    strokeOpacity: 1.0,
    strokeWeight: 3,
    map: this.map,
    editable: false,
    icons: [{
      icon: lineSymbol,
      offset: '100%'
    }]
  };

  this.poly = new google.maps.Polyline(polyOptions);
};

App.Toolbar.Actions.AddRoute.prototype.execute = function() {
  this.bindEvents();
  this.map.setOptions({ draggableCursor: 'crosshair' });
};

App.Toolbar.Actions.AddRoute.prototype.bindEvents = function() {

  var path = this.poly.getPath();

  $.each(App.Map.markers, function(i, marker) {
    (function(i, marker) {

      var handler = google.maps.event.addListener(marker, 'click', function(e) {
        if (marker.poly === this.lastMarker) {
          path.removeAt(marker.poly);
          this.lastMarker--;
          marker.poly = false;
        } else if (!marker.poly && marker.poly !== 0) {
          path.push(e.latLng);
          this.lastMarker++;
          marker.poly = this.lastMarker;
        }
      }.bind(this));

      this.handlers.push(handler);

    }.bind(this)(i, marker));
  }.bind(this));
};

App.Toolbar.Actions.AddRoute.prototype.addLine = function(e) {
  var path = this.poly.getPath();
  path.push(e.latLng);
};

App.Toolbar.Actions.AddRoute.prototype.reset = function() {
  this.map.setOptions({ draggableCursor: null });
  $.each(this.handlers, function(i, handler) {
    handler.remove();
  });
};<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Link } from 'react-router';
import moment from '../../../assets/moment.min.js';
//Import Actions
import { submitFile, getRecentSubmission} from './AssignmentActions'
// Import Style
import styles from './Assignment.css';

import Feedback from './components/Feedback/Feedback';
// Import Material UI Components
import {
  Card,
  CardActions,
  CardHeader,
  CardMedia,
  CardTitle,
  CardText
} from 'material-ui/Card';
import RaisedButton from 'material-ui/RaisedButton';
import {
  Table,
  TableBody,
  TableHeader,
  TableHeaderColumn,
  TableRow,
  TableRowColumn,
} from 'material-ui/Table';
import TextField from 'material-ui/TextField';

export class Assignment extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isMounted: false,
      feedbackVisible: false,
      score: 0,
      fileChosen: false,
      submitted: false,
    };
    this.intervalID = setInterval(() => this.checkFeedback(), 5000);
  }

  componentDidMount() {
    this.setState({ isMounted: true });
  }

  componentWillReceiveProps(NextProps) {
    var courseNum = NextProps.params.course;
    var assignmentNum = NextProps.assignmentData[NextProps.params.assignment]._id;
    var userEmail = NextProps.user.email;
    this.props.getRecentSubmission(courseNum, assignmentNum, userEmail);
  }

  render() {
    var assignmentData = this.props.assignmentData[this.props.params.assignment];
    if (assignmentData == null) { return null; }
    // console.log(assignmentData);
    var due_date = moment(assignmentData.due_date).format('LLL');
    var end_date = moment(assignmentData.end_date).format('LLL');
    var asst_desc = assignmentData.description;
    console.log(this.props.score.toString());
    var problemsArray = assignmentData.problems;
    //Determine maxScore
    var maxScore = 0;
    for(var i = 0; i < problemsArray.length; i++){
      maxScore = maxScore + problemsArray[i].score;
    }
    var displayName = this.props.assignmentData[this.props.params.assignment].name;
    return (
      <Card>
        <CardTitle title={displayName} subtitle={asst_desc} />
        <CardMedia>

            <TableBody displayRowCheckbox={false}>
              <TableRow selectable={false}>
                <TableRowColumn>Due date: </TableRowColumn>
                <TableRowColumn>{due_date}</TableRowColumn>
              </TableRow>
              <TableRow selectable={false}>
                <TableRowColumn>Last day to hand in: </TableRowColumn>
                <TableRowColumn>{end_date}</TableRowColumn>
              </TableRow>
              <TableRow selectable={false}>
                <TableRowColumn>Most recent score: </TableRowColumn>
                <TableRowColumn>{(typeof this.props.score === 'number') ? (this.props.score.toString() + "/" + maxScore.toString()) : this.props.score}</TableRowColumn>
              </TableRow>
            </TableBody>

        </CardMedia>
        <CardText>
          {
            this.state.feedbackVisible
              ? <Feedback rawFeedback={this.props.feedback} />
              : null
          }
          {
            (this.state.fileChosen && !this.state.submitted)
            ?

            : null
          }
          {
            this.state.submitted
              ?
Your submission has been submitted for review.

              : null
          }
          {
            (this.state.fileChosen || this.state.submitted)
            ? null
            :
Please choose a file to submit.

          }
        </CardText>
        <CardActions>
          <RaisedButton
            primary={true}
            label='Choose File'
            containerElement='label'>
            <input id="submission" type="file" style={{ display: 'none' }} onChange={() => this.fileEntered()}/>
          </RaisedButton>
          <RaisedButton disabled={!this.state.fileChosen} label="submit" primary={true} onClick={() => this.sendFile()} />
          <RaisedButton onClick={() => this.hideShowFeedback()} primary={true}
            label={
              this.state.feedbackVisible ? 'Hide Feedback' : 'Show Feedback'
            }
          />
        </CardActions>
      </Card>
    );
  }

  fileEntered() {
    var file = document.getElementById("submission").files[0];
    if(typeof file != undefined) {
      this.setState({
        fileChosen: true,
        submitted: false,
      });
    }
  }

  hideShowFeedback() {
    this.setState({ feedbackVisible: !this.state.feedbackVisible });
  }

  sendFile() {
    var file = document.getElementById("submission").files[0];
    if (typeof file == undefined) {
      this.setState({fileChosen: false});
      return;
    }
    var courseNum = this.props.params.course;
    var assignmentNum = this.props.assignmentData[this.props.params.assignment]._id;
    this.props.submitFile(courseNum, assignmentNum, file);
    this.setState({
      fileChosen: false,
      submitted: true,
    });
    document.getElementById("submission").value="";
  }

  checkFeedback() {
    if(!this.state.isMounted) {
      return;
    }
    var courseNum = this.props.params.course;
    var assignmentNum = this.props.assignmentData[this.props.params.assignment]._id;
    var userEmail = this.props.user.email;
    this.props.getRecentSubmission(courseNum, assignmentNum, userEmail);
  }
}
// Retrieve data from store as props
function mapStateToProps(state) {
  return {
    user: state.app.user,
    score: state.assignment.score,
    maxScore: state.assignment.maxScore,
    feedback: state.assignment.feedback,
    assignmentData: state.assignments.assignmentsMap,
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    submitFile: submitFile,
    getRecentSubmission: getRecentSubmission,
  }, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Assignment);

<commit_msg>missed another file<commit_after>import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Link } from 'react-router';
import moment from '../../../assets/moment.min.js';
//Import Actions
import { submitFile, getRecentSubmission} from './AssignmentActions'
// Import Style
import styles from './Assignment.css';

import Feedback from './components/Feedback/Feedback';
// Import Material UI Components
import {
  Card,
  CardActions,
  CardHeader,
  CardMedia,
  CardTitle,
  CardText
} from 'material-ui/Card';
import RaisedButton from 'material-ui/RaisedButton';
import {
  Table,
  TableBody,
  TableHeader,
  TableHeaderColumn,
  TableRow,
  TableRowColumn,
} from 'material-ui/Table';
import TextField from 'material-ui/TextField';

export class Assignment extends Component {
  constructor(props) {
    super(props);
    this.state = {
      isMounted: false,
      feedbackVisible: false,
      score: 0,
      fileChosen: false,
      submitted: false,
    };
    this.intervalID = setInterval(() => this.checkFeedback(), 5000);
  }

  componentDidMount() {
    this.setState({ isMounted: true });
  }

  componentWillReceiveProps(NextProps) {
    var courseNum = NextProps.params.course;
    var assignmentNum = NextProps.assignmentData[NextProps.params.assignment].id;
    var userEmail = NextProps.user.email;
    this.props.getRecentSubmission(courseNum, assignmentNum, userEmail);
  }

  render() {
    var assignmentData = this.props.assignmentData[this.props.params.assignment];
    if (assignmentData == null) { return null; }
    // console.log(assignmentData);
    var due_date = moment(assignmentData.due_date).format('LLL');
    var end_date = moment(assignmentData.end_date).format('LLL');
    var asst_desc = assignmentData.description;
    console.log(this.props.score.toString());
    var problemsArray = assignmentData.problems;
    //Determine maxScore
    var maxScore = 0;
    for(var i = 0; i < problemsArray.length; i++){
      maxScore = maxScore + problemsArray[i].score;
    }
    var displayName = this.props.assignmentData[this.props.params.assignment].name;
    return (
      <Card>
        <CardTitle title={displayName} subtitle={asst_desc} />
        <CardMedia>

            <TableBody displayRowCheckbox={false}>
              <TableRow selectable={false}>
                <TableRowColumn>Due date: </TableRowColumn>
                <TableRowColumn>{due_date}</TableRowColumn>
              </TableRow>
              <TableRow selectable={false}>
                <TableRowColumn>Last day to hand in: </TableRowColumn>
                <TableRowColumn>{end_date}</TableRowColumn>
              </TableRow>
              <TableRow selectable={false}>
                <TableRowColumn>Most recent score: </TableRowColumn>
                <TableRowColumn>{(typeof this.props.score === 'number') ? (this.props.score.toString() + "/" + maxScore.toString()) : this.props.score}</TableRowColumn>
              </TableRow>
            </TableBody>

        </CardMedia>
        <CardText>
          {
            this.state.feedbackVisible
              ? <Feedback rawFeedback={this.props.feedback} />
              : null
          }
          {
            (this.state.fileChosen && !this.state.submitted)
            ?

            : null
          }
          {
            this.state.submitted
              ?
Your submission has been submitted for review.

              : null
          }
          {
            (this.state.fileChosen || this.state.submitted)
            ? null
            :
Please choose a file to submit.

          }
        </CardText>
        <CardActions>
          <RaisedButton
            primary={true}
            label='Choose File'
            containerElement='label'>
            <input id="submission" type="file" style={{ display: 'none' }} onChange={() => this.fileEntered()}/>
          </RaisedButton>
          <RaisedButton disabled={!this.state.fileChosen} label="submit" primary={true} onClick={() => this.sendFile()} />
          <RaisedButton onClick={() => this.hideShowFeedback()} primary={true}
            label={
              this.state.feedbackVisible ? 'Hide Feedback' : 'Show Feedback'
            }
          />
        </CardActions>
      </Card>
    );
  }

  fileEntered() {
    var file = document.getElementById("submission").files[0];
    if(typeof file != undefined) {
      this.setState({
        fileChosen: true,
        submitted: false,
      });
    }
  }

  hideShowFeedback() {
    this.setState({ feedbackVisible: !this.state.feedbackVisible });
  }

  sendFile() {
    var file = document.getElementById("submission").files[0];
    if (typeof file == undefined) {
      this.setState({fileChosen: false});
      return;
    }
    var courseNum = this.props.params.course;
    var assignmentNum = this.props.assignmentData[this.props.params.assignment].id;
    this.props.submitFile(courseNum, assignmentNum, file);
    this.setState({
      fileChosen: false,
      submitted: true,
    });
    document.getElementById("submission").value="";
  }

  checkFeedback() {
    if(!this.state.isMounted) {
      return;
    }
    var courseNum = this.props.params.course;
    var assignmentNum = this.props.assignmentData[this.props.params.assignment].id;
    var userEmail = this.props.user.email;
    this.props.getRecentSubmission(courseNum, assignmentNum, userEmail);
  }
}
// Retrieve data from store as props
function mapStateToProps(state) {
  return {
    user: state.app.user,
    score: state.assignment.score,
    maxScore: state.assignment.maxScore,
    feedback: state.assignment.feedback,
    assignmentData: state.assignments.assignmentsMap,
  };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators({
    submitFile: submitFile,
    getRecentSubmission: getRecentSubmission,
  }, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(Assignment);

<|endoftext|>