<commit_before>/* eslint-disable */
// @ngInject
export default function baseResourceListController(
  baseControllerListClass,
  ENV,
  resourcesService,
  servicesService,
  currentStateService,
  usersService,
  projectsService,
  $uibModal,
  $rootScope,
  $state,
  $sanitize,
  $q,
  $interval,
) {
  let ControllerListClass = baseControllerListClass.extend({
    init: function() {
      this.service = resourcesService;
      this.categories = {};
      this.categories[ENV.VirtualMachines] = 'vms';
      this.categories[ENV.PrivateClouds] = 'private_clouds';
      this.categories[ENV.Storages] = 'storages';
      this.categories[ENV.Volumes] = 'volumes';
      this.enableRefresh = true;

      let fn = this._super.bind(this);
      this.loadInitialContext().then(() => {
        this.tableOptions = this.getTableOptions();
        fn();
      });
      this.hasCustomFilters = false;
      this.resourceTimers = {};
    },
    $onDestroy: function() {
      angular.forEach(this.resourceTimers, timer => {
        $interval.cancel(timer);
      });
    },
    getTableOptions: function() {
      let vm = this.controllerScope;
      const options = {
        searchFieldName: 'name',
        noDataText: gettext('You have no resources yet.'),
        noMatchesText: gettext('No resources found matching filter.'),
        enableOrdering: true,
        columns: [
          {
            id: 'name',
            title: gettext('Name'),
            className: 'all',
            orderField: 'name',
            render: function(row) {
              return vm.renderResourceName(row);
            },
          },
          {
            id: 'provider',
            title: gettext('Provider'),
            className: 'desktop',
            orderField: 'service_name',
            render: function(row) {
              return $sanitize(row.service_name);
            },
          },
          {
            id: 'state',
            title: gettext('State'),
            className: 'min-tablet-l',
            orderField: 'state',
            render: function(row) {
              return vm.renderResourceState(row);
            },
          },
        ],
        tableActions: this.getTableActions(),
        rowActions: function(row) {
          let index = this.findIndexById(row);
          return (
            `<span class="btn-group">` +
            `<action-button-resource button-controller="controller" button-model="controller.list[${index}]"></action-button-resource>` +
            `<resource-summary-button url="controller.list[${index}].url"></resource-summary-button>` +
            `</span>`
          );
        },
      };
      if (this.currentUser.is_support && !this.currentUser.is_staff) {
        delete options.rowActions;
      }
      return options;
    },
    getTableActions: function() {
      return [];
    },
    renderResourceName: function(row) {
      // Hack for datatables.net
      return (
        this.renderComponent('resource-name', row) +
        `<span class="ng-hide">${row.name}</span>`
      );
    },
    renderResourceState: function(row) {
      // Hack for datatables.net
      return (
        this.renderComponent('resource-state', row) +
        `<span class="ng-hide">${row.state}</span>`
      );
    },
    renderComponent: function(component, row) {
      const index = this.findIndexById(row);
      return `<${component} resource="controller.list[${index}]"></${component}>`;
    },
    getCategoryKey: function() {
      return this.categories[this.category];
    },
    rowFields: [
      'uuid',
      'url',
      'name',
      'state',
      'runtime_state',
      'created',
      'error_message',
      'resource_type',
      'latitude',
      'longitude',
      'service_name',
      'service_uuid',
      'customer',
      'service_settings_state',
      'service_settings_error_message',
      'service_settings_uuid',
      'security_groups',
      'description',
      'is_link_valid',
      'customer_name',
      'project_name',
      'marketplace_resource_uuid',
    ],
    addRowFields: function(fields) {
      for (let i = 0; i < fields.length; i++) {
        let field = fields[i];
        if (this.rowFields.indexOf(field) === -1) {
          this.rowFields.push(field);
        }
      }
    },
    getMarkers: function() {
      let items = this.controllerScope.list.filter(function hasCoordinates(
        item,
      ) {
        return item.latitude !== null && item.longitude !== null;
      });

      let points = {};
      items.forEach(function groupMarkersByCoordinates(item) {
        let key = [item.latitude, item.longitude];
        if (!points[key]) {
          points[key] = [];
        }
        points[key].push(item);
      });

      let markers = [];
      angular.forEach(points, function createMarker(items) {
        let item = items[0];
        let message = items
          .map(function(item) {
            return item.name;
          })
          .join('<br/>');
        markers.push({
          lat: item.latitude,
          lng: item.longitude,
          message: message,
        });
      });
      return markers;
    },
    projectHasNonSharedService: function(project) {
      for (let i = 0; i < project.services.length; i++) {
        let service = project.services[i];
        if (!service.shared) {
          return true;
        }
      }
      return false;
    },
    loadInitialContext: function() {
      return $q.all([
        currentStateService
          .getCustomer()
          .then(customer => (this.currentCustomer = customer)),
        currentStateService
          .getProject()
          .then(project => (this.currentProject = project)),
        usersService.getCurrentUser().then(user => (this.currentUser = user)),
      ]);
    },
    getList: function(filter) {
      let query = angular.extend({}, filter, {
        field: this.rowFields,
      });
      if (angular.isDefined(this.category) && !this.getFilter().resource_type) {
        query.resource_category = this.getCategoryKey();
        this.updateFilters(filter);
      }
      return this._super(query);
    },
    updateFilters: function(filter) {
      let query = angular.extend(
        {
          resource_category: this.getCategoryKey(),
          customer: currentStateService.getCustomerUuid(),
        },
        filter,
      );
      angular.forEach(this.service.defaultFilter, function(val, key) {
        if (key !== 'resource_type') {
          query[key] = val;
        }
      });
      return resourcesService.countByType(query).then(
        function(counts) {
          this.searchFilters = this.getFilterByCounts(counts);
        }.bind(this),
      );
    },
    getFilterByCounts: function(counts) {
      let filters = [];
      angular.forEach(counts, function(count, resourceType) {
        if (count > 0) {
          filters.push({
            name: 'resource_type',
            title: resourceType.replace('.', ' ') + ' (' + count + ')',
            value: resourceType,
          });
        }
      });
      return filters;
    },
    afterGetList: function() {
      angular.forEach(this.list, function(resource) {
        resourcesService.cleanOptionsCache(resource.url);
      });
      this._super();
      this.pollResources();
    },
    afterInstanceRemove: function(resource) {
      this._super(resource);
      servicesService.clearAllCacheForCurrentEndpoint();
      projectsService.clearAllCacheForCurrentEndpoint();
    },
    reInitResource: function(resource) {
      let vm = this;
      return resourcesService
        .$get(resource.resource_type, resource.uuid)
        .then(function(response) {
          vm.setResource(resource, response);
          // when row is updated in the list it is not refreshed in the datatable. Do it manually.
          $rootScope.$broadcast('updateRow', { data: response });
          vm.afterGetList();
        });
    },
    setResource: function(resource, response) {
      let index = this.list.indexOf(resource);
      this.list[index] = response;
    },
    resourceIsUpdating: function(resource) {
      // resource is updating if it is not in stable state.
      return resource.state !== 'OK' && resource.state !== 'Erred';
    },
    pollResource: function(resource) {
      let vm = this;
      function removeItem(collection, item) {
        // modify an object, not a reference.
        let index = collection.indexOf(item);
        if (index !== -1) {
          collection.splice(index, 1);
        }
      }
      function internalPollResource(resource) {
        let uuid = resource.uuid;

        if (vm.resourceTimers[resource.uuid]) {
          return;
        }

        vm.resourceTimers[resource.uuid] = $interval(() => {
          resourcesService
            .$get(resource.resource_type, uuid)
            .then(response => {
              // do not call updateRow as it reloads a table and actions are reloaded on ENV.singleResourcePollingTimeout
              vm.setResource(resource, response);
              if (!vm.resourceIsUpdating(response)) {
                $interval.cancel(vm.resourceTimers[uuid]);
                delete vm.resourceTimers[uuid];
                // update row only once to avoid table redrawing on each call.
                $rootScope.$broadcast('updateRow', { data: response });
                resourcesService.cleanOptionsCache(resource.url);
              }
            })
            .catch(response => {
              $interval.cancel(vm.resourceTimers[uuid]);
              delete vm.resourceTimers[uuid];
              if (response.status === 404) {
                removeItem(vm.list, resource);
                $rootScope.$broadcast('removeRow', { data: uuid });
                $rootScope.$broadcast('refreshResource');
              }
            });
        }, ENV.singleResourcePollingTimeout);
      }
      return internalPollResource(resource);
    },
    pollResources: function() {
      if (!ENV.resourcePollingEnabled) {
        return;
      }
      if (this.resourcePollingDisabled) {
        return;
      }
      let vm = this;

      let newResources = this.list.filter(vm.resourceIsUpdating);
      angular.forEach(newResources, resource => {
        vm.pollResource(resource);
      });
    },
  });

  return ControllerListClass;
}
<commit_msg>Drop leftover.<commit_after>/* eslint-disable */
// @ngInject
export default function baseResourceListController(
  baseControllerListClass,
  ENV,
  resourcesService,
  servicesService,
  currentStateService,
  usersService,
  projectsService,
  $uibModal,
  $rootScope,
  $state,
  $sanitize,
  $q,
  $interval,
) {
  let ControllerListClass = baseControllerListClass.extend({
    init: function() {
      this.service = resourcesService;
      this.categories = {};
      this.categories[ENV.VirtualMachines] = 'vms';
      this.categories[ENV.PrivateClouds] = 'private_clouds';
      this.categories[ENV.Storages] = 'storages';
      this.categories[ENV.Volumes] = 'volumes';
      this.enableRefresh = true;

      let fn = this._super.bind(this);
      this.loadInitialContext().then(() => {
        this.tableOptions = this.getTableOptions();
        fn();
      });
      this.hasCustomFilters = false;
      this.resourceTimers = {};
    },
    $onDestroy: function() {
      angular.forEach(this.resourceTimers, timer => {
        $interval.cancel(timer);
      });
    },
    getTableOptions: function() {
      let vm = this.controllerScope;
      const options = {
        searchFieldName: 'name',
        noDataText: gettext('You have no resources yet.'),
        noMatchesText: gettext('No resources found matching filter.'),
        enableOrdering: true,
        columns: [
          {
            id: 'name',
            title: gettext('Name'),
            className: 'all',
            orderField: 'name',
            render: function(row) {
              return vm.renderResourceName(row);
            },
          },
          {
            id: 'provider',
            title: gettext('Provider'),
            className: 'desktop',
            orderField: 'service_name',
            render: function(row) {
              return $sanitize(row.service_name);
            },
          },
          {
            id: 'state',
            title: gettext('State'),
            className: 'min-tablet-l',
            orderField: 'state',
            render: function(row) {
              return vm.renderResourceState(row);
            },
          },
        ],
        tableActions: this.getTableActions(),
        rowActions: function(row) {
          let index = this.findIndexById(row);
          return (
            `<span class="btn-group">` +
            `<action-button-resource button-controller="controller" button-model="controller.list[${index}]"></action-button-resource>` +
            `<resource-summary-button url="controller.list[${index}].url"></resource-summary-button>` +
            `</span>`
          );
        },
      };
      if (this.currentUser.is_support && !this.currentUser.is_staff) {
        delete options.rowActions;
      }
      return options;
    },
    getTableActions: function() {
      return [];
    },
    renderResourceName: function(row) {
      // Hack for datatables.net
      return (
        this.renderComponent('resource-name', row) +
        `<span class="ng-hide">${row.name}</span>`
      );
    },
    renderResourceState: function(row) {
      // Hack for datatables.net
      return (
        this.renderComponent('resource-state', row) +
        `<span class="ng-hide">${row.state}</span>`
      );
    },
    renderComponent: function(component, row) {
      const index = this.findIndexById(row);
      return `<${component} resource="controller.list[${index}]"></${component}>`;
    },
    getCategoryKey: function() {
      return this.categories[this.category];
    },
    rowFields: [
      'uuid',
      'url',
      'name',
      'state',
      'runtime_state',
      'created',
      'error_message',
      'resource_type',
      'latitude',
      'longitude',
      'service_name',
      'service_uuid',
      'customer',
      'service_settings_state',
      'service_settings_error_message',
      'service_settings_uuid',
      'security_groups',
      'description',
      'is_link_valid',
      'customer_name',
      'project_name',
      'marketplace_resource_uuid',
    ],
    addRowFields: function(fields) {
      for (let i = 0; i < fields.length; i++) {
        let field = fields[i];
        if (this.rowFields.indexOf(field) === -1) {
          this.rowFields.push(field);
        }
      }
    },
    loadInitialContext: function() {
      return $q.all([
        currentStateService
          .getCustomer()
          .then(customer => (this.currentCustomer = customer)),
        currentStateService
          .getProject()
          .then(project => (this.currentProject = project)),
        usersService.getCurrentUser().then(user => (this.currentUser = user)),
      ]);
    },
    getList: function(filter) {
      let query = angular.extend({}, filter, {
        field: this.rowFields,
      });
      if (angular.isDefined(this.category) && !this.getFilter().resource_type) {
        query.resource_category = this.getCategoryKey();
        this.updateFilters(filter);
      }
      return this._super(query);
    },
    updateFilters: function(filter) {
      let query = angular.extend(
        {
          resource_category: this.getCategoryKey(),
          customer: currentStateService.getCustomerUuid(),
        },
        filter,
      );
      angular.forEach(this.service.defaultFilter, function(val, key) {
        if (key !== 'resource_type') {
          query[key] = val;
        }
      });
      return resourcesService.countByType(query).then(
        function(counts) {
          this.searchFilters = this.getFilterByCounts(counts);
        }.bind(this),
      );
    },
    getFilterByCounts: function(counts) {
      let filters = [];
      angular.forEach(counts, function(count, resourceType) {
        if (count > 0) {
          filters.push({
            name: 'resource_type',
            title: resourceType.replace('.', ' ') + ' (' + count + ')',
            value: resourceType,
          });
        }
      });
      return filters;
    },
    afterGetList: function() {
      angular.forEach(this.list, function(resource) {
        resourcesService.cleanOptionsCache(resource.url);
      });
      this._super();
      this.pollResources();
    },
    afterInstanceRemove: function(resource) {
      this._super(resource);
      servicesService.clearAllCacheForCurrentEndpoint();
      projectsService.clearAllCacheForCurrentEndpoint();
    },
    reInitResource: function(resource) {
      let vm = this;
      return resourcesService
        .$get(resource.resource_type, resource.uuid)
        .then(function(response) {
          vm.setResource(resource, response);
          // when row is updated in the list it is not refreshed in the datatable. Do it manually.
          $rootScope.$broadcast('updateRow', { data: response });
          vm.afterGetList();
        });
    },
    setResource: function(resource, response) {
      let index = this.list.indexOf(resource);
      this.list[index] = response;
    },
    resourceIsUpdating: function(resource) {
      // resource is updating if it is not in stable state.
      return resource.state !== 'OK' && resource.state !== 'Erred';
    },
    pollResource: function(resource) {
      let vm = this;
      function removeItem(collection, item) {
        // modify an object, not a reference.
        let index = collection.indexOf(item);
        if (index !== -1) {
          collection.splice(index, 1);
        }
      }
      function internalPollResource(resource) {
        let uuid = resource.uuid;

        if (vm.resourceTimers[resource.uuid]) {
          return;
        }

        vm.resourceTimers[resource.uuid] = $interval(() => {
          resourcesService
            .$get(resource.resource_type, uuid)
            .then(response => {
              // do not call updateRow as it reloads a table and actions are reloaded on ENV.singleResourcePollingTimeout
              vm.setResource(resource, response);
              if (!vm.resourceIsUpdating(response)) {
                $interval.cancel(vm.resourceTimers[uuid]);
                delete vm.resourceTimers[uuid];
                // update row only once to avoid table redrawing on each call.
                $rootScope.$broadcast('updateRow', { data: response });
                resourcesService.cleanOptionsCache(resource.url);
              }
            })
            .catch(response => {
              $interval.cancel(vm.resourceTimers[uuid]);
              delete vm.resourceTimers[uuid];
              if (response.status === 404) {
                removeItem(vm.list, resource);
                $rootScope.$broadcast('removeRow', { data: uuid });
                $rootScope.$broadcast('refreshResource');
              }
            });
        }, ENV.singleResourcePollingTimeout);
      }
      return internalPollResource(resource);
    },
    pollResources: function() {
      if (!ENV.resourcePollingEnabled) {
        return;
      }
      if (this.resourcePollingDisabled) {
        return;
      }
      let vm = this;

      let newResources = this.list.filter(vm.resourceIsUpdating);
      angular.forEach(newResources, resource => {
        vm.pollResource(resource);
      });
    },
  });

  return ControllerListClass;
}
<|endoftext|>


<commit_before>import { CANCEL } from 'redux-throttle'
import {
  PLAYER_BG_ALPHA,
  PLAYER_ERROR,
  PLAYER_QUEUE_LOAD,
  PLAYER_LEAVE_REQUEST,
  PLAYER_MEDIA_ELEMENT_CHANGE,
  PLAYER_MEDIA_REQUEST,
  PLAYER_MEDIA_REQUEST_SUCCESS,
  PLAYER_MEDIA_REQUEST_ERROR,
  PLAYER_NEXT,
  PLAYER_PAUSE,
  PLAYER_PLAY,
  PLAYER_QUEUE_END,
  PLAYER_STATUS_REQUEST,
  PLAYER_VOLUME,
} from 'shared/actionTypes'

// have server emit player status to room
export function emitStatus (status) {
  return (dispatch, getState) => {
    const player = getState().player
    const visualizer = getState().playerVisualizer

    dispatch({
      type: PLAYER_STATUS_REQUEST,
      payload: {
        alpha: player.alpha,
        errorMessage: player.errorMessage,
        // string primitive is a hack to pass selector equality check on client side
        historyJSON: JSON.stringify(player.history),
        isAtQueueEnd: player.isAtQueueEnd,
        isErrored: player.isErrored,
        isPlaying: player.isPlaying,
        position: player.position,
        queueId: player.queueId,
        volume: player.volume,
        visualizer,
        ...status, // may have current position, etc.
      },
      meta: {
        throttle: {
          wait: 1000,
          leading: true,
        }
      }
    })
  }
}

// generic player error
export function playerError (message) {
  return {
    type: PLAYER_ERROR,
    payload: { message },
  }
}

// player left room
export function emitLeave () {
  return {
    type: PLAYER_LEAVE_REQUEST,
  }
}

// cancel any pending status emits
export function cancelStatus () {
  return {
    type: CANCEL,
    payload: {
      type: PLAYER_STATUS_REQUEST
    }
  }
}

// sets new queueId and adds previous to history
export function loadQueueItem (queueId) {
  return {
    type: PLAYER_QUEUE_LOAD,
    payload: {
      queueId,
    }
  }
}

export function mediaElementChange (payload) {
  return {
    type: PLAYER_MEDIA_ELEMENT_CHANGE,
    payload,
  }
}

export function mediaRequest (meta) {
  return {
    type: PLAYER_MEDIA_REQUEST,
    payload: meta,
  }
}

export function mediaRequestSuccess () {
  return {
    type: PLAYER_MEDIA_REQUEST_SUCCESS,
  }
}

export function mediaRequestError (message) {
  return (dispatch, getState) => {
    dispatch({
      type: PLAYER_MEDIA_REQUEST_ERROR,
      payload: { message }
    })

    // generic error action (stops playback)
    dispatch(playerError(message))
  }
}

export function queueEnd () {
  return {
    type: PLAYER_QUEUE_END,
  }
}

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [PLAYER_BG_ALPHA]: (state, { payload }) => ({
    ...state,
    alpha: payload,
  }),
  [PLAYER_ERROR]: (state, { payload }) => ({
    ...state,
    isPlaying: false,
    isErrored: true,
    errorMessage: payload.message,
  }),
  [PLAYER_MEDIA_ELEMENT_CHANGE]: (state, { payload }) => ({
    ...state,
    isAlphaSupported: payload.isAlphaSupported,
  }),
  [PLAYER_MEDIA_REQUEST]: (state, { payload }) => ({
    ...state,
    isFetching: true,
  }),
  [PLAYER_MEDIA_REQUEST_SUCCESS]: (state, { payload }) => ({
    ...state,
    isFetching: false,
  }),
  [PLAYER_MEDIA_REQUEST_ERROR]: (state, { payload }) => ({
    ...state,
    isFetching: false,
  }),
  [PLAYER_NEXT]: (state, { payload }) => ({
    ...state,
    isErrored: false,
    isPlaying: true,
    isPlayingNext: true,
    lastCommandAt: Date.now(),
  }),
  [PLAYER_PAUSE]: (state, { payload }) => ({
    ...state,
    isPlaying: false,
    lastCommandAt: Date.now(),
  }),
  [PLAYER_PLAY]: (state, { payload }) => ({
    ...state,
    isErrored: false,
    isPlaying: true,
    lastCommandAt: Date.now(),
  }),
  [PLAYER_QUEUE_END]: (state, { payload }) => ({
    ...state,
    isAtQueueEnd: true,
    isPlayingNext: false,
  }),
  [PLAYER_QUEUE_LOAD]: (state, { payload }) => {
    const history = state.history.slice()

    // save previous
    if (state.queueId !== -1) {
      history.push(state.queueId)
    }

    return {
      ...state,
      history,
      isAtQueueEnd: false,
      isPlayingNext: false,
      queueId: payload.queueId,
    }
  },
  [PLAYER_STATUS_REQUEST]: (state, { payload }) => ({
    ...state,
    position: payload.position,
  }),
  [PLAYER_VOLUME]: (state, { payload }) => ({
    ...state,
    volume: payload,
    lastCommandAt: Date.now(),
  }),
}

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
  alpha: 0.5,
  errorMessage: '',
  history: [], // queueIds
  isAtQueueEnd: false,
  isAlphaSupported: false,
  isErrored: false,
  isFetching: false,
  isPlaying: false,
  isPlayingNext: false,
  lastCommandAt: null,
  position: 0,
  queueId: -1,
  volume: 1,
}

export default function playerReducer (state = initialState, action) {
  const handler = ACTION_HANDLERS[action.type]

  return handler ? handler(state, action) : state
}
<commit_msg>Fix missing prop in status emissions<commit_after>import { CANCEL } from 'redux-throttle'
import {
  PLAYER_BG_ALPHA,
  PLAYER_ERROR,
  PLAYER_QUEUE_LOAD,
  PLAYER_LEAVE_REQUEST,
  PLAYER_MEDIA_ELEMENT_CHANGE,
  PLAYER_MEDIA_REQUEST,
  PLAYER_MEDIA_REQUEST_SUCCESS,
  PLAYER_MEDIA_REQUEST_ERROR,
  PLAYER_NEXT,
  PLAYER_PAUSE,
  PLAYER_PLAY,
  PLAYER_QUEUE_END,
  PLAYER_STATUS_REQUEST,
  PLAYER_VOLUME,
} from 'shared/actionTypes'

// have server emit player status to room
export function emitStatus (status) {
  return (dispatch, getState) => {
    const player = getState().player
    const visualizer = getState().playerVisualizer

    dispatch({
      type: PLAYER_STATUS_REQUEST,
      payload: {
        alpha: player.alpha,
        isAlphaSupported: player.isAlphaSupported,
        errorMessage: player.errorMessage,
        // string primitive is a hack to pass selector equality check on client side
        historyJSON: JSON.stringify(player.history),
        isAtQueueEnd: player.isAtQueueEnd,
        isErrored: player.isErrored,
        isPlaying: player.isPlaying,
        position: player.position,
        queueId: player.queueId,
        volume: player.volume,
        visualizer,
        ...status, // may have current position, etc.
      },
      meta: {
        throttle: {
          wait: 1000,
          leading: true,
        }
      }
    })
  }
}

// generic player error
export function playerError (message) {
  return {
    type: PLAYER_ERROR,
    payload: { message },
  }
}

// player left room
export function emitLeave () {
  return {
    type: PLAYER_LEAVE_REQUEST,
  }
}

// cancel any pending status emits
export function cancelStatus () {
  return {
    type: CANCEL,
    payload: {
      type: PLAYER_STATUS_REQUEST
    }
  }
}

// sets new queueId and adds previous to history
export function loadQueueItem (queueId) {
  return {
    type: PLAYER_QUEUE_LOAD,
    payload: {
      queueId,
    }
  }
}

export function mediaElementChange (payload) {
  return {
    type: PLAYER_MEDIA_ELEMENT_CHANGE,
    payload,
  }
}

export function mediaRequest (meta) {
  return {
    type: PLAYER_MEDIA_REQUEST,
    payload: meta,
  }
}

export function mediaRequestSuccess () {
  return {
    type: PLAYER_MEDIA_REQUEST_SUCCESS,
  }
}

export function mediaRequestError (message) {
  return (dispatch, getState) => {
    dispatch({
      type: PLAYER_MEDIA_REQUEST_ERROR,
      payload: { message }
    })

    // generic error action (stops playback)
    dispatch(playerError(message))
  }
}

export function queueEnd () {
  return {
    type: PLAYER_QUEUE_END,
  }
}

// ------------------------------------
// Action Handlers
// ------------------------------------
const ACTION_HANDLERS = {
  [PLAYER_BG_ALPHA]: (state, { payload }) => ({
    ...state,
    alpha: payload,
  }),
  [PLAYER_ERROR]: (state, { payload }) => ({
    ...state,
    isPlaying: false,
    isErrored: true,
    errorMessage: payload.message,
  }),
  [PLAYER_MEDIA_ELEMENT_CHANGE]: (state, { payload }) => ({
    ...state,
    isAlphaSupported: payload.isAlphaSupported,
  }),
  [PLAYER_MEDIA_REQUEST]: (state, { payload }) => ({
    ...state,
    isFetching: true,
  }),
  [PLAYER_MEDIA_REQUEST_SUCCESS]: (state, { payload }) => ({
    ...state,
    isFetching: false,
  }),
  [PLAYER_MEDIA_REQUEST_ERROR]: (state, { payload }) => ({
    ...state,
    isFetching: false,
  }),
  [PLAYER_NEXT]: (state, { payload }) => ({
    ...state,
    isErrored: false,
    isPlaying: true,
    isPlayingNext: true,
    lastCommandAt: Date.now(),
  }),
  [PLAYER_PAUSE]: (state, { payload }) => ({
    ...state,
    isPlaying: false,
    lastCommandAt: Date.now(),
  }),
  [PLAYER_PLAY]: (state, { payload }) => ({
    ...state,
    isErrored: false,
    isPlaying: true,
    lastCommandAt: Date.now(),
  }),
  [PLAYER_QUEUE_END]: (state, { payload }) => ({
    ...state,
    isAtQueueEnd: true,
    isPlayingNext: false,
  }),
  [PLAYER_QUEUE_LOAD]: (state, { payload }) => {
    const history = state.history.slice()

    // save previous
    if (state.queueId !== -1) {
      history.push(state.queueId)
    }

    return {
      ...state,
      history,
      isAtQueueEnd: false,
      isPlayingNext: false,
      queueId: payload.queueId,
    }
  },
  [PLAYER_STATUS_REQUEST]: (state, { payload }) => ({
    ...state,
    position: payload.position,
  }),
  [PLAYER_VOLUME]: (state, { payload }) => ({
    ...state,
    volume: payload,
    lastCommandAt: Date.now(),
  }),
}

// ------------------------------------
// Reducer
// ------------------------------------
const initialState = {
  alpha: 0.5,
  errorMessage: '',
  history: [], // queueIds
  isAtQueueEnd: false,
  isAlphaSupported: false,
  isErrored: false,
  isFetching: false,
  isPlaying: false,
  isPlayingNext: false,
  lastCommandAt: null,
  position: 0,
  queueId: -1,
  volume: 1,
}

export default function playerReducer (state = initialState, action) {
  const handler = ACTION_HANDLERS[action.type]

  return handler ? handler(state, action) : state
}
<|endoftext|>


<commit_before>/**
 * MultiDRM loader
 */
( function( mw, $ ) {
	"use strict";
	var mseSupported = window['MediaSource'] || window['WebKitMediaSource'];
	//Load 3rd party plugins if DRM sources are available
	mw.addKalturaConfCheck( function( embedPlayer, callback ){
		if( embedPlayer.isPluginEnabled( 'multiDrm' ) ) {
			if (mseSupported) {
				mw.log("Media Source Extensions supported on this browser");
				registerDashPlayer();
				var sources = embedPlayer.getSources();
				var drmSources = sources.filter( function ( source ) {
					return ( ( source.mimeType === "application/dash+xml" ) ||
					( source.mimeType === "video/ism" || source.mimeType === "video/playreadySmooth" && mw.isChrome() &&  !mw.isMobileDevice()) );
				} );
				var isDrmSourceAvailable = drmSources.length > 0;
				if ( isDrmSourceAvailable ) {
					mw.log("Media sources found, loading DASH player");
					var clDashPlayerUrl = embedPlayer.getKalturaConfig( "multiDrm", "clDashPlayerUrl" ) || mw.getMwEmbedPath() + "node_modules/mwEmbed-Dash-Everywhere/video.js";
					var dashJsUrl = embedPlayer.getKalturaConfig( "multiDrm", "dashJsUrl" ) || mw.getMwEmbedPath() + "node_modules/mwEmbed-Dash-Everywhere/cldasheverywhere.min.js";
					if (clDashPlayerUrl && dashJsUrl) {
						$.ajax({
							url: clDashPlayerUrl,
							dataType: "script",
							cache: true,
							success: $.ajax({
								url: dashJsUrl,
								dataType: "script",
								cache: true,
								success: function () {
									mw.log("DASH player loaded, setting configuration");
									//Get user configuration
									var drmUserConfig = embedPlayer.getKalturaConfig("multiDrm");
									//Get default config
									var drmConfig = getDefaultDrmConfig();
									//Deep extend custom config
									$.extend(true, drmConfig, drmUserConfig);
									embedPlayer.setKalturaConfig("multiDrm", drmConfig);
									//Set reference for DASH playback engine
									mw.dash = {
										player: videojs
									};
									callback();
								},
								error: function(){
									mw.log("Error::Playback engine couldn't be found");
									callback();
								}
							}),
							error: function(){
								mw.log("Error::Playback engine couldn't be found");
								callback();
							}
						});
					} else {
						mw.log("Playback engine couldn't be found, not loading DASH player");
						callback();
					}
				} else {
					mw.log("No media sources found, not loading DASH player");
					callback();
				}
			} else {
				mw.log("Media Source Extensions not supported on this browser");
				callback();
			}
		} else {
			callback();
		}
	});

	function registerDashPlayer(){
		// Add multidrm player:
		$( mw ).bind( 'EmbedPlayerUpdateMediaPlayers' , function ( event , mediaPlayers ) {
			mw.log("Register DASH player and extensions");
			var multiDRMProtocols = ['application/dash+xml'];
			//On chrome add smooth stream mimetype support
			if ( mw.isChrome() &&  !mw.isMobileDevice()) {
				multiDRMProtocols.push( "video/ism" );
				multiDRMProtocols.push( "video/playreadySmooth" );
			}
			var multiDRMPlayer = new mw.MediaPlayer( 'multidrm', multiDRMProtocols, 'MultiDRM' );
			mediaPlayers.addPlayer( multiDRMPlayer );
			// add
			$.each( multiDRMProtocols, function ( inx, mimeType ) {
				if ( mediaPlayers.defaultPlayers[mimeType] ) {
					mediaPlayers.defaultPlayers[mimeType].unshift( 'MultiDRM' );
					return true;
				}
				mediaPlayers.defaultPlayers[mimeType] = ['MultiDRM'];
			} );
		} );
	}

	if (!Array.prototype.filter) {
		Array.prototype.filter = function(fun/*, thisArg*/) {
			'use strict';

			if (this === void 0 || this === null) {
				throw new TypeError();
			}

			var t = Object(this);
			var len = t.length >>> 0;
			if (typeof fun !== 'function') {
				throw new TypeError();
			}

			var res = [];
			var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
			for (var i = 0; i < len; i++) {
				if (i in t) {
					var val = t[i];

					// NOTE: Technically this should Object.defineProperty at
					//       the next index, as push can be affected by
					//       properties on Object.prototype and Array.prototype.
					//       But that method's new, and collisions should be
					//       rare, so use the more-compatible alternative.
					if (fun.call(thisArg, val, i, t)) {
						res.push(val);
					}
				}
			}

			return res;
		};
	}

	function getDefaultDrmConfig(){
		var defaultConfig = {
			"drm": "auto",
			"customData": {
				"userId": null ,
				"sessionId": "castlab-session" ,
				"merchant": "kaltura"
			},
			"sendCustomData": true,
			"generatePSSH": false,
			"assetId": null , //coguid //entryid
			"variantId": null , //flavorid
			"authenticationToken": null ,
			"widevineLicenseServerURL": null,
			"accessLicenseServerURL": null,
			"autoplay": false,
			"widht":"100%",
			"height":"100%",
			"flashFile": mw.getConfig("EmbedPlayer.dashAsUrl"),
			"controls": false ,
			"techs": ["dashjs", "dashas"] ,
			"debug": false
		};
		return defaultConfig;
	}
} )( window.mediaWiki, window.jQuery );<commit_msg>Fix deferred loading of DASH libraries<commit_after>/**
 * MultiDRM loader
 */
( function( mw, $ ) {
	"use strict";
	var mseSupported = window['MediaSource'] || window['WebKitMediaSource'];
	//Load 3rd party plugins if DRM sources are available
	mw.addKalturaConfCheck( function( embedPlayer, callback ){
		if( embedPlayer.isPluginEnabled( 'multiDrm' ) ) {
			if (mseSupported) {
				mw.log("Media Source Extensions supported on this browser");
				registerDashPlayer();
				var sources = embedPlayer.getSources();
				var drmSources = sources.filter( function ( source ) {
					return ( ( source.mimeType === "application/dash+xml" ) ||
					( source.mimeType === "video/ism" || source.mimeType === "video/playreadySmooth" && mw.isChrome() &&  !mw.isMobileDevice()) );
				} );
				var isDrmSourceAvailable = drmSources.length > 0;
				if ( isDrmSourceAvailable ) {
					mw.log("Media sources found, loading DASH player");
					var clDashPlayerUrl = embedPlayer.getKalturaConfig( "multiDrm", "clDashPlayerUrl" ) || mw.getMwEmbedPath() + "node_modules/mwEmbed-Dash-Everywhere/video.js";
					var dashJsUrl = embedPlayer.getKalturaConfig( "multiDrm", "dashJsUrl" ) || mw.getMwEmbedPath() + "node_modules/mwEmbed-Dash-Everywhere/cldasheverywhere.min.js";
					if (clDashPlayerUrl && dashJsUrl) {
						$.getScript( clDashPlayerUrl)
							.then(function(){return $.getScript( dashJsUrl)})
							.done(function(){
								mw.log("DASH player loaded, setting configuration");
								//Get user configuration
								var drmUserConfig = embedPlayer.getKalturaConfig("multiDrm");
								//Get default config
								var drmConfig = getDefaultDrmConfig();
								//Deep extend custom config
								$.extend(true, drmConfig, drmUserConfig);
								embedPlayer.setKalturaConfig("multiDrm", drmConfig);
								//Set reference for DASH playback engine
								mw.dash = {
									player: videojs
								};
								callback();
							})
							.fail(function( ) {
								mw.log("Error::Playback engine couldn't be found");
								callback();
							});
					} else {
						mw.log("Playback engine couldn't be found, not loading DASH player");
						callback();
					}
				} else {
					mw.log("No media sources found, not loading DASH player");
					callback();
				}
			} else {
				mw.log("Media Source Extensions not supported on this browser");
				callback();
			}
		} else {
			callback();
		}
	});

	function registerDashPlayer(){
		// Add multidrm player:
		$( mw ).bind( 'EmbedPlayerUpdateMediaPlayers' , function ( event , mediaPlayers ) {
			mw.log("Register DASH player and extensions");
			var multiDRMProtocols = ['application/dash+xml'];
			//On chrome add smooth stream mimetype support
			if ( mw.isChrome() &&  !mw.isMobileDevice()) {
				multiDRMProtocols.push( "video/ism" );
				multiDRMProtocols.push( "video/playreadySmooth" );
			}
			var multiDRMPlayer = new mw.MediaPlayer( 'multidrm', multiDRMProtocols, 'MultiDRM' );
			mediaPlayers.addPlayer( multiDRMPlayer );
			// add
			$.each( multiDRMProtocols, function ( inx, mimeType ) {
				if ( mediaPlayers.defaultPlayers[mimeType] ) {
					mediaPlayers.defaultPlayers[mimeType].unshift( 'MultiDRM' );
					return true;
				}
				mediaPlayers.defaultPlayers[mimeType] = ['MultiDRM'];
			} );
		} );
	}

	if (!Array.prototype.filter) {
		Array.prototype.filter = function(fun/*, thisArg*/) {
			'use strict';

			if (this === void 0 || this === null) {
				throw new TypeError();
			}

			var t = Object(this);
			var len = t.length >>> 0;
			if (typeof fun !== 'function') {
				throw new TypeError();
			}

			var res = [];
			var thisArg = arguments.length >= 2 ? arguments[1] : void 0;
			for (var i = 0; i < len; i++) {
				if (i in t) {
					var val = t[i];

					// NOTE: Technically this should Object.defineProperty at
					//       the next index, as push can be affected by
					//       properties on Object.prototype and Array.prototype.
					//       But that method's new, and collisions should be
					//       rare, so use the more-compatible alternative.
					if (fun.call(thisArg, val, i, t)) {
						res.push(val);
					}
				}
			}

			return res;
		};
	}

	function getDefaultDrmConfig(){
		var defaultConfig = {
			"drm": "auto",
			"customData": {
				"userId": null ,
				"sessionId": "castlab-session" ,
				"merchant": "kaltura"
			},
			"sendCustomData": true,
			"generatePSSH": false,
			"assetId": null , //coguid //entryid
			"variantId": null , //flavorid
			"authenticationToken": null ,
			"widevineLicenseServerURL": null,
			"accessLicenseServerURL": null,
			"autoplay": false,
			"widht":"100%",
			"height":"100%",
			"flashFile": mw.getConfig("EmbedPlayer.dashAsUrl"),
			"controls": false ,
			"techs": ["dashjs", "dashas"] ,
			"debug": false
		};
		return defaultConfig;
	}
} )( window.mediaWiki, window.jQuery );<|endoftext|>


<commit_before>import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import CONSTANTS from '../src/constants.json';
import { ajax } from '../src/ajax.js';

const analyticsType = 'endpoint';
const url = 'URL_TO_SERVER_ENDPOINT';

const {
  EVENTS: {
    AUCTION_END,
    BID_WON,
    BID_RESPONSE,
    BID_REQUESTED,
    BID_TIMEOUT,
  }
} = CONSTANTS;

let saveEvents = {}
let allEvents = {}
let auctionEnd = {}
let initOptions = {}
let endpoint = 'https://default'
let requestsAttributes = ['adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'params', 'userId', 'labelAny', 'bids'];

function getAdapterNameForAlias(aliasName) {
  return adapterManager.aliasRegistry[aliasName] || aliasName;
}

function filterAttributes(arg, removead) {
  let response = {};
  if (typeof arg == 'object') {
    if (typeof arg['bidderCode'] == 'string') {
      response['originalBidder'] = getAdapterNameForAlias(arg['bidderCode']);
    } else if (typeof arg['bidder'] == 'string') {
      response['originalBidder'] = getAdapterNameForAlias(arg['bidder']);
    }
    if (!removead && typeof arg['ad'] != 'undefined') {
      response['ad'] = arg['ad'];
    }
    if (typeof arg['gdprConsent'] != 'undefined') {
      response['gdprConsent'] = {};
      if (typeof arg['gdprConsent']['consentString'] != 'undefined') { response['gdprConsent']['consentString'] = arg['gdprConsent']['consentString']; }
    }
    requestsAttributes.forEach((attr) => {
      if (typeof arg[attr] != 'undefined') { response[attr] = arg[attr]; }
    });
    if (typeof response['creativeId'] == 'number') { response['creativeId'] = response['creativeId'].toString(); }
  }
  return response;
}

function cleanAuctionEnd(args) {
  let response = {};
  let filteredObj;
  let objects = ['bidderRequests', 'bidsReceived', 'noBids'];
  objects.forEach((attr) => {
    if (Array.isArray(args[attr])) {
      response[attr] = [];
      args[attr].forEach((obj) => {
        filteredObj = filterAttributes(obj, true);
        if (typeof obj['bids'] == 'object') {
          filteredObj['bids'] = [];
          obj['bids'].forEach((bid) => {
            filteredObj['bids'].push(filterAttributes(bid, true));
          });
        }
        response[attr].push(filteredObj);
      });
    }
  });
  return response;
}

function cleanCreatives(args) {
  return filterAttributes(args, false);
}

function enhanceMediaType(arg) {
  saveEvents['bidRequested'].forEach((bidRequested) => {
    if (bidRequested['auctionId'] == arg['auctionId'] && Array.isArray(bidRequested['bids'])) {
      bidRequested['bids'].forEach((bid) => {
        if (bid['transactionId'] == arg['transactionId'] && bid['bidId'] == arg['requestId']) { arg['mediaTypes'] = bid['mediaTypes']; }
      });
    }
  });
  return arg;
}

function addBidResponse(args) {
  let eventType = BID_RESPONSE;
  let argsCleaned = cleanCreatives(JSON.parse(JSON.stringify(args))); ;
  if (allEvents[eventType] == undefined) { allEvents[eventType] = [] }
  allEvents[eventType].push(argsCleaned);
}

function addBidRequested(args) {
  let eventType = BID_REQUESTED;
  let argsCleaned = filterAttributes(args, true);
  if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] }
  saveEvents[eventType].push(argsCleaned);
}

function addTimeout(args) {
  let eventType = BID_TIMEOUT;
  if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] }
  saveEvents[eventType].push(args);
  let argsCleaned = [];
  let argsDereferenced = JSON.parse(JSON.stringify(args));
  argsDereferenced.forEach((attr) => {
    argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false));
  });
  if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] }
  auctionEnd[eventType].push(argsCleaned);
}

function addAuctionEnd(args) {
  let eventType = AUCTION_END;
  if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] }
  saveEvents[eventType].push(args);
  let argsCleaned = cleanAuctionEnd(JSON.parse(JSON.stringify(args)));
  if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] }
  auctionEnd[eventType].push(argsCleaned);
}

function handleBidWon(args) {
  args = enhanceMediaType(filterAttributes(JSON.parse(JSON.stringify(args)), true));
  let increment = args['cpm'];
  if (typeof saveEvents['auctionEnd'] == 'object') {
    saveEvents['auctionEnd'].forEach((auction) => {
      if (auction['auctionId'] == args['auctionId'] && typeof auction['bidsReceived'] == 'object') {
        auction['bidsReceived'].forEach((bid) => {
          if (bid['transactionId'] == args['transactionId'] && bid['adId'] != args['adId']) {
            if (args['cpm'] < bid['cpm']) {
              increment = 0;
            } else if (increment > args['cpm'] - bid['cpm']) {
              increment = args['cpm'] - bid['cpm'];
            }
          }
        });
      }
    });
  }
  args['cpmIncrement'] = increment;
  if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; }
  ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true});
}

function handleAuctionEnd() {
  ajax(endpoint + '.bidwatch.io/analytics/auctions', function (data) {
    let list = JSON.parse(data);
    if (Array.isArray(list) && typeof allEvents['bidResponse'] != 'undefined') {
      allEvents['bidResponse'].forEach((bidResponse) => {
        if (list.includes(bidResponse['originalBidder'] + '_' + bidResponse['creativeId'])) { ajax(endpoint + '.bidwatch.io/analytics/creatives', null, JSON.stringify(bidResponse), {method: 'POST', withCredentials: true}); }
      });
    }
    allEvents = {};
  }, JSON.stringify(auctionEnd), {method: 'POST', withCredentials: true});
  auctionEnd = {};
}

let bidwatchAnalytics = Object.assign(adapter({url, analyticsType}), {
  track({
    eventType,
    args
  }) {
    switch (eventType) {
      case AUCTION_END:
        addAuctionEnd(args);
        handleAuctionEnd();
        break;
      case BID_WON:
        handleBidWon(args);
        break;
      case BID_RESPONSE:
        addBidResponse(args);
        break;
      case BID_REQUESTED:
        addBidRequested(args);
        break;
      case BID_TIMEOUT:
        addTimeout(args);
        break;
    }
  }});

// save the base class function
bidwatchAnalytics.originEnableAnalytics = bidwatchAnalytics.enableAnalytics;

// override enableAnalytics so we can get access to the config passed in from the page
bidwatchAnalytics.enableAnalytics = function (config) {
  bidwatchAnalytics.originEnableAnalytics(config); // call the base class function
  initOptions = config.options;
  if (initOptions.domain) { endpoint = 'https://' + initOptions.domain; }
};

adapterManager.registerAnalyticsAdapter({
  adapter: bidwatchAnalytics,
  code: 'bidwatch'
});

export default bidwatchAnalytics;
<commit_msg>bidWatch Analytics Adapter : minor fixes (#8937)<commit_after>import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js';
import adapterManager from '../src/adapterManager.js';
import CONSTANTS from '../src/constants.json';
import { ajax } from '../src/ajax.js';

const analyticsType = 'endpoint';
const url = 'URL_TO_SERVER_ENDPOINT';

const {
  EVENTS: {
    AUCTION_END,
    BID_WON,
    BID_RESPONSE,
    BID_REQUESTED,
    BID_TIMEOUT,
  }
} = CONSTANTS;

let saveEvents = {}
let allEvents = {}
let auctionEnd = {}
let initOptions = {}
let endpoint = 'https://default'
let requestsAttributes = ['adUnitCode', 'auctionId', 'bidder', 'bidderCode', 'bidId', 'cpm', 'creativeId', 'currency', 'width', 'height', 'mediaType', 'netRevenue', 'originalCpm', 'originalCurrency', 'requestId', 'size', 'source', 'status', 'timeToRespond', 'transactionId', 'ttl', 'sizes', 'mediaTypes', 'src', 'params', 'userId', 'labelAny', 'bids'];

function getAdapterNameForAlias(aliasName) {
  return adapterManager.aliasRegistry[aliasName] || aliasName;
}

function filterAttributes(arg, removead) {
  let response = {};
  if (typeof arg == 'object') {
    if (typeof arg['bidderCode'] == 'string') {
      response['originalBidder'] = getAdapterNameForAlias(arg['bidderCode']);
    } else if (typeof arg['bidder'] == 'string') {
      response['originalBidder'] = getAdapterNameForAlias(arg['bidder']);
    }
    if (!removead && typeof arg['ad'] != 'undefined') {
      response['ad'] = arg['ad'];
    }
    if (typeof arg['gdprConsent'] != 'undefined') {
      response['gdprConsent'] = {};
      if (typeof arg['gdprConsent']['consentString'] != 'undefined') { response['gdprConsent']['consentString'] = arg['gdprConsent']['consentString']; }
    }
    requestsAttributes.forEach((attr) => {
      if (typeof arg[attr] != 'undefined') { response[attr] = arg[attr]; }
    });
    if (typeof response['creativeId'] == 'number') { response['creativeId'] = response['creativeId'].toString(); }
  }
  return response;
}

function cleanAuctionEnd(args) {
  let response = {};
  let filteredObj;
  let objects = ['bidderRequests', 'bidsReceived', 'noBids', 'adUnits'];
  objects.forEach((attr) => {
    if (Array.isArray(args[attr])) {
      response[attr] = [];
      args[attr].forEach((obj) => {
        filteredObj = filterAttributes(obj, true);
        if (typeof obj['bids'] == 'object') {
          filteredObj['bids'] = [];
          obj['bids'].forEach((bid) => {
            filteredObj['bids'].push(filterAttributes(bid, true));
          });
        }
        response[attr].push(filteredObj);
      });
    }
  });
  return response;
}

function cleanCreatives(args) {
  return filterAttributes(args, false);
}

function enhanceMediaType(arg) {
  saveEvents['bidRequested'].forEach((bidRequested) => {
    if (bidRequested['auctionId'] == arg['auctionId'] && Array.isArray(bidRequested['bids'])) {
      bidRequested['bids'].forEach((bid) => {
        if (bid['transactionId'] == arg['transactionId'] && bid['bidId'] == arg['requestId']) { arg['mediaTypes'] = bid['mediaTypes']; }
      });
    }
  });
  return arg;
}

function addBidResponse(args) {
  let eventType = BID_RESPONSE;
  let argsCleaned = cleanCreatives(JSON.parse(JSON.stringify(args))); ;
  if (allEvents[eventType] == undefined) { allEvents[eventType] = [] }
  allEvents[eventType].push(argsCleaned);
}

function addBidRequested(args) {
  let eventType = BID_REQUESTED;
  let argsCleaned = filterAttributes(args, true);
  if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] }
  saveEvents[eventType].push(argsCleaned);
}

function addTimeout(args) {
  let eventType = BID_TIMEOUT;
  if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] }
  saveEvents[eventType].push(args);
  let argsCleaned = [];
  let argsDereferenced = JSON.parse(JSON.stringify(args));
  argsDereferenced.forEach((attr) => {
    argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false));
  });
  if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] }
  auctionEnd[eventType].push(argsCleaned);
}

function addAuctionEnd(args) {
  let eventType = AUCTION_END;
  if (saveEvents[eventType] == undefined) { saveEvents[eventType] = [] }
  saveEvents[eventType].push(args);
  let argsCleaned = cleanAuctionEnd(JSON.parse(JSON.stringify(args)));
  if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] }
  auctionEnd[eventType].push(argsCleaned);
}

function handleBidWon(args) {
  args = enhanceMediaType(filterAttributes(JSON.parse(JSON.stringify(args)), true));
  let increment = args['cpm'];
  if (typeof saveEvents['auctionEnd'] == 'object') {
    saveEvents['auctionEnd'].forEach((auction) => {
      if (auction['auctionId'] == args['auctionId'] && typeof auction['bidsReceived'] == 'object') {
        auction['bidsReceived'].forEach((bid) => {
          if (bid['transactionId'] == args['transactionId'] && bid['adId'] != args['adId']) {
            if (args['cpm'] < bid['cpm']) {
              increment = 0;
            } else if (increment > args['cpm'] - bid['cpm']) {
              increment = args['cpm'] - bid['cpm'];
            }
          }
        });
      }
    });
  }
  args['cpmIncrement'] = increment;
  if (typeof saveEvents.bidRequested == 'object' && saveEvents.bidRequested.length > 0 && saveEvents.bidRequested[0].gdprConsent) { args.gdpr = saveEvents.bidRequested[0].gdprConsent; }
  ajax(endpoint + '.bidwatch.io/analytics/bid_won', null, JSON.stringify(args), {method: 'POST', withCredentials: true});
}

function handleAuctionEnd() {
  ajax(endpoint + '.bidwatch.io/analytics/auctions', function (data) {
    let list = JSON.parse(data);
    if (Array.isArray(list) && typeof allEvents['bidResponse'] != 'undefined') {
      let alreadyCalled = [];
      allEvents['bidResponse'].forEach((bidResponse) => {
        let tmpId = bidResponse['originalBidder'] + '_' + bidResponse['creativeId'];
        if (list.includes(tmpId) && !alreadyCalled.includes(tmpId)) {
          alreadyCalled.push(tmpId);
          ajax(endpoint + '.bidwatch.io/analytics/creatives', null, JSON.stringify(bidResponse), {method: 'POST', withCredentials: true});
        }
      });
    }
    allEvents = {};
  }, JSON.stringify(auctionEnd), {method: 'POST', withCredentials: true});
  auctionEnd = {};
}

let bidwatchAnalytics = Object.assign(adapter({url, analyticsType}), {
  track({
    eventType,
    args
  }) {
    switch (eventType) {
      case AUCTION_END:
        addAuctionEnd(args);
        handleAuctionEnd();
        break;
      case BID_WON:
        handleBidWon(args);
        break;
      case BID_RESPONSE:
        addBidResponse(args);
        break;
      case BID_REQUESTED:
        addBidRequested(args);
        break;
      case BID_TIMEOUT:
        addTimeout(args);
        break;
    }
  }});

// save the base class function
bidwatchAnalytics.originEnableAnalytics = bidwatchAnalytics.enableAnalytics;

// override enableAnalytics so we can get access to the config passed in from the page
bidwatchAnalytics.enableAnalytics = function (config) {
  bidwatchAnalytics.originEnableAnalytics(config); // call the base class function
  initOptions = config.options;
  if (initOptions.domain) {
    endpoint = 'https://' + initOptions.domain;
  }
};

adapterManager.registerAnalyticsAdapter({
  adapter: bidwatchAnalytics,
  code: 'bidwatch'
});

export default bidwatchAnalytics;
<|endoftext|>


<commit_before>var { h, when, map, Proxy, Struct, Value, computed } = require('mutant')
var nest = require('depnest')
var ref = require('ssb-ref')
var AnchorHook = require('../../../../lib/anchor-hook')

exports.needs = nest({
  'keys.sync.id': 'first',
  'feed.obs.thread': 'first',
  'message.sync.unbox': 'first',
  'message.html': {
    render: 'first',
    compose: 'first'
  },
  'sbot.async.get': 'first',
  'intl.sync.i18n': 'first'
})

exports.gives = nest('page.html.render')

exports.create = function (api) {
  const i18n = api.intl.sync.i18n
  return nest('page.html.render', function (id) {
    if (!ref.isMsg(id)) return
    var loader = h('div', {className: 'Loading -large'})

    var result = Proxy(loader)
    var anchor = Value()

    var meta = Struct({
      type: 'post',
      root: Proxy(id),
      branch: Proxy(id),
      channel: Value(undefined),
      recps: Value(undefined)
    })

    var compose = api.message.html.compose({
      meta,
      isPrivate: when(meta.recps, true),
      shrink: false,
      hooks: [
        AnchorHook('reply', anchor, (el) => el.focus())
      ],
      placeholder: when(meta.recps, i18n('Write a private reply'), i18n('Write a public reply'))
    })

    api.sbot.async.get(id, (err, value) => {
      if (err) {
        return result.set(h('PageHeading', [
          h('h1', i18n('Cannot load thead'))
        ]))
      }

      if (typeof value.content === 'string') {
        value = api.message.sync.unbox(value)
      }

      if (!value) {
        return result.set(h('PageHeading', [
          h('h1', i18n('Cannot display message.'))
        ]))
      }

      // what happens in private stays in private!
      meta.recps.set(value.content.recps)

      var isReply = !!value.content.root
      var thread = api.feed.obs.thread(id, {branch: isReply})

      meta.channel.set(value.content.channel)
      meta.root.set(value.content.root || thread.rootId)

      // if root thread, reply to last post
      meta.branch.set(isReply ? thread.branchId : thread.lastId)

      var container = h('Thread', [
        h('div.messages', [
          when(thread.branchId, h('a.full', {href: thread.rootId, anchor: id}, [i18n('View full thread')])),
          map(thread.messages, (msg) => {
            return computed([msg, thread.previousKey(msg)], (msg, previousId) => {
              return h('div', {
                hooks: [AnchorHook(msg.key, anchor, showContext)]
              }, [
                api.message.html.render(msg, {
                  pageId: id,
                  previousId,
                  includeReferences: true
                })
              ])
            })
          }, {
            maxTime: 5,
            idle: true
          })
        ]),
        compose
      ])
      result.set(when(thread.sync, container, loader))
    })

    var view = h('div', {className: 'SplitView'}, [
      h('div.main', [
        result
      ])
    ])

    view.setAnchor = function (value) {
      anchor.set(value)
    }

    return view
  })
}

function showContext (element) {
  var scrollParent = getScrollParent(element)
  if (scrollParent) {
    // ensure context is visible
    scrollParent.scrollTop = Math.max(0, scrollParent.scrollTop - 100)
  }
}

function getScrollParent (element) {
  while (element.parentNode) {
    if (element.parentNode.scrollTop) {
      return element.parentNode
    } else {
      element = element.parentNode
    }
  }
}
<commit_msg>message view: improve anchor jumping when at top of screen<commit_after>var { h, when, map, Proxy, Struct, Value, computed } = require('mutant')
var nest = require('depnest')
var ref = require('ssb-ref')
var AnchorHook = require('../../../../lib/anchor-hook')

exports.needs = nest({
  'keys.sync.id': 'first',
  'feed.obs.thread': 'first',
  'message.sync.unbox': 'first',
  'message.html': {
    render: 'first',
    compose: 'first'
  },
  'sbot.async.get': 'first',
  'intl.sync.i18n': 'first'
})

exports.gives = nest('page.html.render')

exports.create = function (api) {
  const i18n = api.intl.sync.i18n
  return nest('page.html.render', function (id) {
    if (!ref.isMsg(id)) return
    var loader = h('div', {className: 'Loading -large'})

    var result = Proxy(loader)
    var anchor = Value()

    var meta = Struct({
      type: 'post',
      root: Proxy(id),
      branch: Proxy(id),
      channel: Value(undefined),
      recps: Value(undefined)
    })

    var compose = api.message.html.compose({
      meta,
      isPrivate: when(meta.recps, true),
      shrink: false,
      hooks: [
        AnchorHook('reply', anchor, (el) => el.focus())
      ],
      placeholder: when(meta.recps, i18n('Write a private reply'), i18n('Write a public reply'))
    })

    api.sbot.async.get(id, (err, value) => {
      if (err) {
        return result.set(h('PageHeading', [
          h('h1', i18n('Cannot load thead'))
        ]))
      }

      if (typeof value.content === 'string') {
        value = api.message.sync.unbox(value)
      }

      if (!value) {
        return result.set(h('PageHeading', [
          h('h1', i18n('Cannot display message.'))
        ]))
      }

      // what happens in private stays in private!
      meta.recps.set(value.content.recps)

      var isReply = !!value.content.root
      var thread = api.feed.obs.thread(id, {branch: isReply})

      meta.channel.set(value.content.channel)
      meta.root.set(value.content.root || thread.rootId)

      // if root thread, reply to last post
      meta.branch.set(isReply ? thread.branchId : thread.lastId)

      var container = h('Thread', [
        h('div.messages', [
          when(thread.branchId, h('a.full', {href: thread.rootId, anchor: id}, [i18n('View full thread')])),
          map(thread.messages, (msg) => {
            return computed([msg, thread.previousKey(msg)], (msg, previousId) => {
              return h('div', {
                hooks: [AnchorHook(msg.key, anchor, showContext)]
              }, [
                api.message.html.render(msg, {
                  pageId: id,
                  previousId,
                  includeReferences: true
                })
              ])
            })
          }, {
            maxTime: 5,
            idle: true
          })
        ]),
        compose
      ])
      result.set(when(thread.sync, container, loader))
    })

    var view = h('div', {className: 'SplitView'}, [
      h('div.main', [
        result
      ])
    ])

    view.setAnchor = function (value) {
      anchor.set(value)
    }

    return view
  })
}

function showContext (element) {
  var scrollParent = getScrollParent(element)
  if (scrollParent) {
    // ensure context is visible
    scrollParent.scrollTop = Math.max(0, scrollParent.scrollTop - 100)
  }
}

function getScrollParent (element) {
  while (element.parentNode) {
    if (element.parentNode.scrollTop > 10) {
      return element.parentNode
    } else {
      element = element.parentNode
    }
  }
}
<|endoftext|>


<commit_before>angular.module('hsgc')
  .directive('footballDriveChart', ['$window', '$log', function($window, $log) {
    return {
      restrict: 'EA',
      templateUrl: 'templates/footballDriveChart.html',
      link: function(scope, element) {
        scope.selectedPeriod = 0;
        scope.lastPlay = {};
        scope.previousPlay = {};
        scope.hasPreviousPlay = false;
        var paper = typeof(window.paper) === 'undefined' ? null : window.paper;
        var firstload = true;
        var footballFieldCanvas = null;
        var font = "sans-serif";

        scope.$watch('selectedDetailTab', function(newValue, oldValue) {
          if (newValue === 5 && newValue !== oldValue) {
            scope.resize();
          }
        });

        scope.$on('datacastLoaded', function() {
          if (angular.isUndefined(scope.playByPlay))
            return;

          if (firstload) {
            firstload = false;

            footballFieldCanvas = $(element.find('canvas')[0]);
            paper.setup(footballFieldCanvas[0]);

            window.angular.element($window).on('resize', scope.resize);
            scope.$on('$destroy', function() {
              window.angular.element($window).off('resize', scope.resize);
            }); // remove the resize listener from the canvas to prevent memory leak
          }

          scope.lastPlay = scope.playByPlay[scope.playByPlay.length - 1];
          scope.previousPlay = scope.playByPlay[scope.playByPlay.length - 2];
          scope.hasPreviousPlay = scope.playByPlay.length > 2;

          scope.resize();
        });

        scope.resize = function() {
          var desiredWidth = element.parent().width() - 20;
          var desiredHeight = 0;
          if (desiredWidth < 1) {
            desiredWidth = footballFieldCanvas.width();
          }
          //football fields including endzones are 4:9 = height:width ratio
          desiredHeight = 4 * desiredWidth / 9;

          footballFieldCanvas.prop({
            width: desiredWidth,
            height: desiredHeight
          });

          paper.view.viewSize = new paper.Size(desiredWidth, desiredHeight);
          scope.drawDrive();
        };

        scope.drawDrive = function() {
          var fieldSize = paper.view.viewSize;
          var field = new paper.Path.Rectangle(new paper.Rectangle(new paper.Point(0,0), fieldSize));
          field.fillColor = "#008A2E";

          var endzoneWidth = fieldSize.width / 12;
          var playingFieldWidth = fieldSize.width - 2 * endzoneWidth;
          var yardLines = [];
          for (var i = 0; i <= 100; i++) {
            yardLines.push(endzoneWidth + i * playingFieldWidth / 100);
          };
          var awayEndzone = new paper.Path.Rectangle(new paper.Rectangle(0, 0, endzoneWidth, fieldSize.height));
          awayEndzone.fillColor = "#ff0000";

          var endZoneTextHeight = endzoneWidth / 3;
          var awayTeamNameSize = scope.getTextSize(scope.awayName, endZoneTextHeight);
          var awayTeamName = new paper.PointText(new paper.Point( (endzoneWidth / 2) - (awayTeamNameSize.width / 2), (fieldSize.height / 2) + (awayTeamNameSize.height / 2)));
          awayTeamName.content = scope.awayName;
          awayTeamName.strokeColor = "#ffffff";
          awayTeamName.fillColor = "#ffffff";
          awayTeamName.fontSize = awayTeamNameSize.height + "px";
          awayTeamName.fontFamily = font;
          awayTeamName.rotate(270);

          var homeEndzone = new paper.Path.Rectangle(new paper.Rectangle(yardLines[yardLines.length - 1], 0, endzoneWidth, fieldSize.height));
          homeEndzone.fillColor = "#0000ff";

          var homeTeamNameSize = scope.getTextSize(scope.homeName, endZoneTextHeight);
          var homeTeamName = new paper.PointText(new paper.Point(yardLines[yardLines.length - 1] + (endzoneWidth / 2) - (homeTeamNameSize.width / 2), (fieldSize.height / 2) + (homeTeamNameSize.height / 2)));
          homeTeamName.content = scope.homeName;
          homeTeamName.strokeColor = "#ffffff";
          homeTeamName.fillColor = "#ffffff";
          homeTeamName.fontSize = awayTeamNameSize.height + "px";
          homeTeamName.fontFamily = font;
          homeTeamName.rotate(90);

          var textHeight = (yardLines[5] - yardLines[0]) * 0.8;
          var textSizes = {};
          var yardNumbers = [1, 2, 3, 4, 5, 0];
          for (var i = 0; i < yardNumbers.length; i++) {
            textSizes[yardNumbers[i]] = scope.getTextSize(yardNumbers[i], textHeight);
          }

          var yardLineTextVerticalPadding = textHeight * 0.1;
          var yardLineTextHorizontalPadding = 2;
          var yardLineSpacing = yardLines[1] - yardLines[0];
          var hashMarkInset = fieldSize.height / 3;
          var topMiddleHashMarkY = hashMarkInset;
          var bottomMiddleHashMarkY = fieldSize.height - hashMarkInset;

          for (var i = 0; i < yardLines.length; i++) {
            if (i % 5 === 0) {
              var line = new paper.Path.Line(new paper.Point(yardLines[i], 0), new paper.Point(yardLines[i], fieldSize.height));
              line.strokeColor = "#ffffff";
              if (i % 10 === 0) {
                var yardLineWidth = 3;
                line.strokeWidth = yardLineWidth;
                if (i !== 0 && i !== yardLines.length - 1) {
                  var yardLine = i;
                  if (yardLine > 50) {
                    yardLine = 100 - yardLine;
                  }
                  yardLine = yardLine / 10;
                  var text10DigitSize = textSizes[yardLine];
                  var topYardText10Digit = new paper.PointText(new paper.Point(yardLines[i] + yardLineTextHorizontalPadding + yardLineWidth, text10DigitSize.height + yardLineTextVerticalPadding));
                  topYardText10Digit.content = yardLine;
                  topYardText10Digit.strokeColor = "#ffffff";
                  topYardText10Digit.fillColor = "#ffffff";
                  topYardText10Digit.fontSize = text10DigitSize.height + "px";
                  topYardText10Digit.fontFamily = font;
                  topYardText10Digit.rotate(180);

                  var yardText0DigitSize = textSizes[0];
                  var topYardText0Digit = new paper.PointText(new paper.Point(yardLines[i] - text10DigitSize.width - yardLineTextHorizontalPadding, yardText0DigitSize.height + yardLineTextVerticalPadding));
                  topYardText0Digit.content = 0;
                  topYardText0Digit.strokeColor = "#ffffff";
                  topYardText0Digit.fillColor = "#ffffff";
                  topYardText0Digit.fontSize = yardText0DigitSize.height + "px";
                  topYardText0Digit.fontFamily = font;
                  topYardText0Digit.rotate(180);

                  var text10DigitSize = textSizes[yardLine];
                  var bottomYardText10Digit = new paper.PointText(new paper.Point(yardLines[i] - text10DigitSize.width - yardLineTextHorizontalPadding, fieldSize.height - text10DigitSize.height / 2 - yardLineTextVerticalPadding));
                  bottomYardText10Digit.content = yardLine;
                  bottomYardText10Digit.strokeColor = "#ffffff";
                  bottomYardText10Digit.fillColor = "#ffffff";
                  bottomYardText10Digit.fontSize = text10DigitSize.height + "px";
                  bottomYardText10Digit.fontFamily = font;

                  var yardText0DigitSize = textSizes[0];
                  var bottomYardText0Digit = new paper.PointText(new paper.Point(yardLines[i] + yardLineTextHorizontalPadding + yardLineWidth, fieldSize.height - yardText0DigitSize.height / 2 - yardLineTextVerticalPadding));
                  bottomYardText0Digit.content = 0;
                  bottomYardText0Digit.strokeColor = "#ffffff";
                  bottomYardText0Digit.fillColor = "#ffffff";
                  bottomYardText0Digit.fontSize = yardText0DigitSize.height + "px";
                  bottomYardText0Digit.fontFamily = font;
                }
              }
            } else {
              if (yardLineSpacing > 5) {
                //only draw hashmarks if there is enough space
                scope.drawHashMark(yardLines[i], 0, true);
                scope.drawHashMark(yardLines[i], fieldSize.height, false);
                scope.drawHashMark(yardLines[i], topMiddleHashMarkY, true);
                scope.drawHashMark(yardLines[i], bottomMiddleHashMarkY, false);
              }
            }
          }

          var scrimmageLineX = yardLines[scope.lastPlay.Spot];
          var scrimmageLine = new paper.Path.Line(new paper.Point(scrimmageLineX, 0), new paper.Point(scrimmageLineX, fieldSize.height));
          scrimmageLine.strokeColor = "#ffff00";
          scrimmageLine.strokeWidth = 5;

          var arrowOffset;
          var arrowRotation;
          var triangleRadius = textHeight / 2;
          var arrowColor;
          if (scope.lastPlay.TeamSeasonId == scope.homeTeamSeasonId) {
            arrowOffset = triangleRadius;
            arrowRotation = 270;
            arrowColor = "#0000ff";
          } else {
            arrowOffset = -triangleRadius;
            arrowRotation = 90;
            arrowColor = "#ff0000";
          }
          var arrow = new paper.Path.RegularPolygon(new paper.Point(scrimmageLineX + arrowOffset, fieldSize.height / 2), 3, triangleRadius);
          arrow.fillColor = arrowColor;
          arrow.rotate(arrowRotation);

          var firstDownYardLine;
          if (scope.lastPlay.TeamSeasonId == scope.homeTeamSeasonId) {
            firstDownYardLine = scope.lastPlay.Spot - scope.lastPlay.Distance;
          } else {
            firstDownYardLine = scope.lastPlay.Spot + scope.lastPlay.Distance;
          }

          var firstDownLineX = yardLines[firstDownYardLine];
          var firstDownLine = new paper.Path.Line(new paper.Point(firstDownLineX, 0), new paper.Point(firstDownLineX, fieldSize.height));
          firstDownLine.strokeColor = "#00ffff";
          firstDownLine.strokeWidth = 5;

          paper.view.draw();
        };

        scope.drawHashMark = function(x, y, drawTopToBottom) {
          var hashMarkHeight = 10;
          var yCoord = drawTopToBottom ? y : y - hashMarkHeight;
          var mark = new paper.Path.Line(new paper.Point(x, yCoord), new paper.Point(x, yCoord + hashMarkHeight));
          mark.strokeColor = "#ffffff";
        };

        scope.getTextSize = function(text, height) {
          var canvas = footballFieldCanvas[0];
          var context = canvas.getContext("2d");
          context.font = height + "px " + font;
          var metrics = context.measureText(text);
          var width =  metrics.width;

          return { width: width, height: height};
        };

        scope.getSpot = function(play) {
          var team = (play.TeamSeasonId == scope.homeTeamSeasonId && play.Spot <= 50) || (play.TeamSeasonId == scope.awayTeamSeasonId && play.Spot > 50) ? scope.homeAcronym : scope.awayAcronym;
          var spot = play.Spot <= 50 ? play.Spot : 100 - play.Spot;

          return team + ' ' + spot;
        };

        scope.getScrimmageLeft = function(play) {
          if (play.TeamSeasonId == scope.homeTeamSeasonId) {
            return 100 - play.Spot;
          } else if (play.TeamSeasonId == scope.awayTeamSeasonId) {
            return play.Spot;
          }
        }

        scope.getGoalLeft = function(play) {
          var distance_offset = play.Distance;
          if (play.TeamSeasonId == scope.homeTeamSeasonId) {
            distance_offset = distance_offset * -1;
          }

          return scope.getScrimmageLeft(play) + distance_offset;
        }

        scope.downEmbelish = function(number) {
          if (number == 1) {
            return "1st";
          } else if (number == 2) {
            return "2nd";
          } else if (number == 3) {
            return "3rd";
          } else if (number == 4) {
            return "4th";
          }
        }
      }
    };
  }]);<commit_msg>Fix bug when there is no play-by-play<commit_after>angular.module('hsgc')
  .directive('footballDriveChart', ['$window', '$log', function($window, $log) {
    return {
      restrict: 'EA',
      templateUrl: 'templates/footballDriveChart.html',
      link: function(scope, element) {
        scope.selectedPeriod = 0;
        scope.lastPlay = {};
        scope.previousPlay = {};
        scope.hasPreviousPlay = false;
        var paper = typeof(window.paper) === 'undefined' ? null : window.paper;
        var firstload = true;
        var footballFieldCanvas = null;
        var font = "sans-serif";

        scope.$watch('selectedDetailTab', function(newValue, oldValue) {
          if (newValue === 5 && newValue !== oldValue) {
            scope.resize();
          }
        });

        scope.$on('datacastLoaded', function() {
          if (angular.isUndefined(scope.playByPlay) || scope.playByPlay.length === 0)
            return;

          if (firstload) {
            firstload = false;

            footballFieldCanvas = $(element.find('canvas')[0]);
            paper.setup(footballFieldCanvas[0]);

            window.angular.element($window).on('resize', scope.resize);
            scope.$on('$destroy', function() {
              window.angular.element($window).off('resize', scope.resize);
            }); // remove the resize listener from the canvas to prevent memory leak
          }

          scope.lastPlay = scope.playByPlay[scope.playByPlay.length - 1];
          scope.previousPlay = scope.playByPlay[scope.playByPlay.length - 2];
          scope.hasPreviousPlay = scope.playByPlay.length > 2;

          scope.resize();
        });

        scope.resize = function() {
          var desiredWidth = element.parent().width() - 20;
          var desiredHeight = 0;
          if (desiredWidth < 1) {
            desiredWidth = footballFieldCanvas.width();
          }
          //football fields including endzones are 4:9 = height:width ratio
          desiredHeight = 4 * desiredWidth / 9;

          footballFieldCanvas.prop({
            width: desiredWidth,
            height: desiredHeight
          });

          paper.view.viewSize = new paper.Size(desiredWidth, desiredHeight);
          scope.drawDrive();
        };

        scope.drawDrive = function() {
          var fieldSize = paper.view.viewSize;
          var field = new paper.Path.Rectangle(new paper.Rectangle(new paper.Point(0,0), fieldSize));
          field.fillColor = "#008A2E";

          var endzoneWidth = fieldSize.width / 12;
          var playingFieldWidth = fieldSize.width - 2 * endzoneWidth;
          var yardLines = [];
          for (var i = 0; i <= 100; i++) {
            yardLines.push(endzoneWidth + i * playingFieldWidth / 100);
          };
          var awayEndzone = new paper.Path.Rectangle(new paper.Rectangle(0, 0, endzoneWidth, fieldSize.height));
          awayEndzone.fillColor = "#ff0000";

          var endZoneTextHeight = endzoneWidth / 3;
          var awayTeamNameSize = scope.getTextSize(scope.awayName, endZoneTextHeight);
          var awayTeamName = new paper.PointText(new paper.Point( (endzoneWidth / 2) - (awayTeamNameSize.width / 2), (fieldSize.height / 2) + (awayTeamNameSize.height / 2)));
          awayTeamName.content = scope.awayName;
          awayTeamName.strokeColor = "#ffffff";
          awayTeamName.fillColor = "#ffffff";
          awayTeamName.fontSize = awayTeamNameSize.height + "px";
          awayTeamName.fontFamily = font;
          awayTeamName.rotate(270);

          var homeEndzone = new paper.Path.Rectangle(new paper.Rectangle(yardLines[yardLines.length - 1], 0, endzoneWidth, fieldSize.height));
          homeEndzone.fillColor = "#0000ff";

          var homeTeamNameSize = scope.getTextSize(scope.homeName, endZoneTextHeight);
          var homeTeamName = new paper.PointText(new paper.Point(yardLines[yardLines.length - 1] + (endzoneWidth / 2) - (homeTeamNameSize.width / 2), (fieldSize.height / 2) + (homeTeamNameSize.height / 2)));
          homeTeamName.content = scope.homeName;
          homeTeamName.strokeColor = "#ffffff";
          homeTeamName.fillColor = "#ffffff";
          homeTeamName.fontSize = awayTeamNameSize.height + "px";
          homeTeamName.fontFamily = font;
          homeTeamName.rotate(90);

          var textHeight = (yardLines[5] - yardLines[0]) * 0.8;
          var textSizes = {};
          var yardNumbers = [1, 2, 3, 4, 5, 0];
          for (var i = 0; i < yardNumbers.length; i++) {
            textSizes[yardNumbers[i]] = scope.getTextSize(yardNumbers[i], textHeight);
          }

          var yardLineTextVerticalPadding = textHeight * 0.1;
          var yardLineTextHorizontalPadding = 2;
          var yardLineSpacing = yardLines[1] - yardLines[0];
          var hashMarkInset = fieldSize.height / 3;
          var topMiddleHashMarkY = hashMarkInset;
          var bottomMiddleHashMarkY = fieldSize.height - hashMarkInset;

          for (var i = 0; i < yardLines.length; i++) {
            if (i % 5 === 0) {
              var line = new paper.Path.Line(new paper.Point(yardLines[i], 0), new paper.Point(yardLines[i], fieldSize.height));
              line.strokeColor = "#ffffff";
              if (i % 10 === 0) {
                var yardLineWidth = 3;
                line.strokeWidth = yardLineWidth;
                if (i !== 0 && i !== yardLines.length - 1) {
                  var yardLine = i;
                  if (yardLine > 50) {
                    yardLine = 100 - yardLine;
                  }
                  yardLine = yardLine / 10;
                  var text10DigitSize = textSizes[yardLine];
                  var topYardText10Digit = new paper.PointText(new paper.Point(yardLines[i] + yardLineTextHorizontalPadding + yardLineWidth, text10DigitSize.height + yardLineTextVerticalPadding));
                  topYardText10Digit.content = yardLine;
                  topYardText10Digit.strokeColor = "#ffffff";
                  topYardText10Digit.fillColor = "#ffffff";
                  topYardText10Digit.fontSize = text10DigitSize.height + "px";
                  topYardText10Digit.fontFamily = font;
                  topYardText10Digit.rotate(180);

                  var yardText0DigitSize = textSizes[0];
                  var topYardText0Digit = new paper.PointText(new paper.Point(yardLines[i] - text10DigitSize.width - yardLineTextHorizontalPadding, yardText0DigitSize.height + yardLineTextVerticalPadding));
                  topYardText0Digit.content = 0;
                  topYardText0Digit.strokeColor = "#ffffff";
                  topYardText0Digit.fillColor = "#ffffff";
                  topYardText0Digit.fontSize = yardText0DigitSize.height + "px";
                  topYardText0Digit.fontFamily = font;
                  topYardText0Digit.rotate(180);

                  var text10DigitSize = textSizes[yardLine];
                  var bottomYardText10Digit = new paper.PointText(new paper.Point(yardLines[i] - text10DigitSize.width - yardLineTextHorizontalPadding, fieldSize.height - text10DigitSize.height / 2 - yardLineTextVerticalPadding));
                  bottomYardText10Digit.content = yardLine;
                  bottomYardText10Digit.strokeColor = "#ffffff";
                  bottomYardText10Digit.fillColor = "#ffffff";
                  bottomYardText10Digit.fontSize = text10DigitSize.height + "px";
                  bottomYardText10Digit.fontFamily = font;

                  var yardText0DigitSize = textSizes[0];
                  var bottomYardText0Digit = new paper.PointText(new paper.Point(yardLines[i] + yardLineTextHorizontalPadding + yardLineWidth, fieldSize.height - yardText0DigitSize.height / 2 - yardLineTextVerticalPadding));
                  bottomYardText0Digit.content = 0;
                  bottomYardText0Digit.strokeColor = "#ffffff";
                  bottomYardText0Digit.fillColor = "#ffffff";
                  bottomYardText0Digit.fontSize = yardText0DigitSize.height + "px";
                  bottomYardText0Digit.fontFamily = font;
                }
              }
            } else {
              if (yardLineSpacing > 5) {
                //only draw hashmarks if there is enough space
                scope.drawHashMark(yardLines[i], 0, true);
                scope.drawHashMark(yardLines[i], fieldSize.height, false);
                scope.drawHashMark(yardLines[i], topMiddleHashMarkY, true);
                scope.drawHashMark(yardLines[i], bottomMiddleHashMarkY, false);
              }
            }
          }

          var scrimmageLineX = yardLines[scope.lastPlay.Spot];
          var scrimmageLine = new paper.Path.Line(new paper.Point(scrimmageLineX, 0), new paper.Point(scrimmageLineX, fieldSize.height));
          scrimmageLine.strokeColor = "#ffff00";
          scrimmageLine.strokeWidth = 5;

          var arrowOffset;
          var arrowRotation;
          var triangleRadius = textHeight / 2;
          var arrowColor;
          if (scope.lastPlay.TeamSeasonId == scope.homeTeamSeasonId) {
            arrowOffset = triangleRadius;
            arrowRotation = 270;
            arrowColor = "#0000ff";
          } else {
            arrowOffset = -triangleRadius;
            arrowRotation = 90;
            arrowColor = "#ff0000";
          }
          var arrow = new paper.Path.RegularPolygon(new paper.Point(scrimmageLineX + arrowOffset, fieldSize.height / 2), 3, triangleRadius);
          arrow.fillColor = arrowColor;
          arrow.rotate(arrowRotation);

          var firstDownYardLine;
          if (scope.lastPlay.TeamSeasonId == scope.homeTeamSeasonId) {
            firstDownYardLine = scope.lastPlay.Spot - scope.lastPlay.Distance;
          } else {
            firstDownYardLine = scope.lastPlay.Spot + scope.lastPlay.Distance;
          }

          var firstDownLineX = yardLines[firstDownYardLine];
          var firstDownLine = new paper.Path.Line(new paper.Point(firstDownLineX, 0), new paper.Point(firstDownLineX, fieldSize.height));
          firstDownLine.strokeColor = "#00ffff";
          firstDownLine.strokeWidth = 5;

          paper.view.draw();
        };

        scope.drawHashMark = function(x, y, drawTopToBottom) {
          var hashMarkHeight = 10;
          var yCoord = drawTopToBottom ? y : y - hashMarkHeight;
          var mark = new paper.Path.Line(new paper.Point(x, yCoord), new paper.Point(x, yCoord + hashMarkHeight));
          mark.strokeColor = "#ffffff";
        };

        scope.getTextSize = function(text, height) {
          var canvas = footballFieldCanvas[0];
          var context = canvas.getContext("2d");
          context.font = height + "px " + font;
          var metrics = context.measureText(text);
          var width =  metrics.width;

          return { width: width, height: height};
        };

        scope.getSpot = function(play) {
          var team = (play.TeamSeasonId == scope.homeTeamSeasonId && play.Spot <= 50) || (play.TeamSeasonId == scope.awayTeamSeasonId && play.Spot > 50) ? scope.homeAcronym : scope.awayAcronym;
          var spot = play.Spot <= 50 ? play.Spot : 100 - play.Spot;

          return team + ' ' + spot;
        };

        scope.getScrimmageLeft = function(play) {
          if (play.TeamSeasonId == scope.homeTeamSeasonId) {
            return 100 - play.Spot;
          } else if (play.TeamSeasonId == scope.awayTeamSeasonId) {
            return play.Spot;
          }
        }

        scope.getGoalLeft = function(play) {
          var distance_offset = play.Distance;
          if (play.TeamSeasonId == scope.homeTeamSeasonId) {
            distance_offset = distance_offset * -1;
          }

          return scope.getScrimmageLeft(play) + distance_offset;
        }

        scope.downEmbelish = function(number) {
          if (number == 1) {
            return "1st";
          } else if (number == 2) {
            return "2nd";
          } else if (number == 3) {
            return "3rd";
          } else if (number == 4) {
            return "4th";
          }
        }
      }
    };
  }]);<|endoftext|>