<commit_before>/*
 * Copyright (C) 2019 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import {bool, func} from 'prop-types'
import React, {createRef} from 'react'
import CanvasRce from '@canvas/rce/react/CanvasRce'
import TinyMCEContentItem from '@canvas/tinymce-external-tools/TinyMCEContentItem'
import {Submission} from '@canvas/assignments/graphql/student/Submission'
import apiUserContent from '@canvas/util/jquery/apiUserContent'
import theme from '@instructure/canvas-theme'
import StudentViewContext from '../Context'

// This is how long we wait to see that changes have stopped before actually
// saving the draft
const saveDraftDelayMS = 1000

export default class TextEntry extends React.Component {
  static propTypes = {
    createSubmissionDraft: func,
    focusOnInit: bool.isRequired,
    onContentsChanged: func,
    submission: Submission.shape,
    readOnly: bool
  }

  _isMounted = false

  _isInitted = false

  _saveDraftTimer = null

  _lastSavedContent = null

  _rceRef = createRef()

  handleMessage = e => {
    const editor = this._rceRef.current
    if (editor == null || e.data.subject !== 'A2ExternalContentReady') {
      return
    }

    e.data.content_items
      .map(contentItem => TinyMCEContentItem.fromJSON(contentItem).codePayload)
      .forEach(code => {
        editor.insertCode(code)
      })
  }

  getDraftBody = () => {
    const {submission} = this.props
    if (['graded', 'submitted'].includes(submission.state)) {
      // If this attempt has been submitted/graded, use it. (It could be null
      // if a grade was given without a proper submission; the RCE will throw
      // an error in that case, so default to an empty string.)
      return submission.body || ''
    } else if (submission.submissionDraft != null) {
      // If a draft object exists, get the submission contents from it
      return submission.submissionDraft.body || ''
    }

    // If the submission is marked as unsubmitted and there's no draft object,
    // the user has started a new attempt but not entered any text, so return
    // an empty string. The body attribute may contain the contents of a
    // previous attempt, which we don't want.
    return ''
  }

  componentDidMount() {
    this._isMounted = true

    window.addEventListener('message', this.handleMessage)
  }

  componentDidUpdate(prevProps) {
    if (this._tinyeditor == null) {
      return
    }

    if (this.props.submission.attempt !== prevProps.submission.attempt) {
      const body = this.getDraftBody()
      if (body !== this._tinyeditor.getContent()) {
        this._tinyeditor.setContent(body)
      }
      this._lastSavedContent = body

      if (this.props.focusOnInit) {
        this._rceRef.current.focus()
      }
    }
  }

  componentWillUnmount() {
    this._isMounted = false
    clearTimeout(this._saveDraftTimer)
    window.removeEventListener('message', this.handleMessage)
  }

  checkForChanges = newContent => {
    // The idea here:
    // - Every time this function is called (currently when the RCE content changes),
    //   check whether the contents of the editor have changed, assuming we're
    //   in a state where we care about changes.
    // - If we see changes, call the onContentsChanged prop, and schedule a
    //   timer to actually save the draft. Further changes to the content will
    //   cancel/re-schedule this timer, so that we only actually save the draft
    //   after the user has stopped making changes for some time.
    if (!this._isInitted) {
      return
    }

    const {submission} = this.props

    const isNewAttempt = submission.submissionDraft == null && submission.state === 'unsubmitted'
    // If read-only *or* this is a brand new attempt with no content,
    // we don't want to save a draft, so don't bother comparing
    if (isNewAttempt && newContent === '') {
      return
    }

    const editorContents = newContent
    if (this._lastSavedContent !== editorContents) {
      this._lastSavedContent = editorContents

      this.props.onContentsChanged()

      clearTimeout(this._saveDraftTimer)
      this._saveDraftTimer = setTimeout(() => {
        this.saveSubmissionDraft({attempt: submission.attempt, rceText: editorContents})
      }, saveDraftDelayMS)
    }
  }

  // Note: I believe there's a bug in tinymce, that
  // if you set focus:true to give the editor focus on init,
  // then the internal bookkeeping doesn't know it has focus
  // and it does not handle the focusout event correctly.
  // Start w/o focus, then give it focus after initialization
  // in this.handleRCEInit
  handleRCEInit = tinyeditor => {
    this._tinyeditor = tinyeditor

    const draftBody = this.getDraftBody()
    tinyeditor.setContent(draftBody)
    this._lastSavedContent = draftBody

    if (this.props.focusOnInit) {
      this._rceRef.current.focus()
    }
    this._isInitted = true
  }

  handleEditorFocus = _event => {
    // these two lines put the caret at the end of the text when focused
    this._tinyeditor.selection.select(this._tinyeditor.getBody(), true)
    this._tinyeditor.selection.collapse(false)
  }

  getRCEText = () => {
    return this._rceRef.current?.getCode()
  }

  saveSubmissionDraft = async ({attempt, rceText}) => {
    await this.props.createSubmissionDraft({
      variables: {
        id: this.props.submission.id,
        activeSubmissionType: 'online_text_entry',
        attempt: attempt || 1,
        body: rceText
      }
    })
  }

  renderContent() {
    return (
      <div
        data-testid="read-only-content"
        dangerouslySetInnerHTML={{
          __html: apiUserContent.convert(this.props.submission.body)
        }}
      />
    )
  }

  renderRCE(context) {
    return (
      <div
        data-testid="text-editor"
        style={{padding: `${theme.variables.spacing.small} ${theme.variables.spacing.xLarge}`}}
      >
        <CanvasRce
          ref={this._rceRef}
          autosave={false}
          defaultContent={this.getDraftBody()}
          editorOptions={{
            focus: false
          }}
          height={300}
          readOnly={context.isObserver}
          textareaId="textentry_text"
          onFocus={this.handleEditorFocus}
          onBlur={() => {}}
          onInit={this.handleRCEInit}
          onContentChange={content => {
            this.checkForChanges(content)
          }}
        />
      </div>
    )
  }

  render() {
    return (
      <>
        {this.props.readOnly ? (
          this.renderContent()
        ) : (
          <StudentViewContext.Consumer>
            {context => this.renderRCE(context)}
          </StudentViewContext.Consumer>
        )}
      </>
    )
  }
}
<commit_msg>Ensure TextEntry focus method can be run<commit_after>/*
 * Copyright (C) 2019 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import {bool, func} from 'prop-types'
import React, {createRef} from 'react'
import CanvasRce from '@canvas/rce/react/CanvasRce'
import TinyMCEContentItem from '@canvas/tinymce-external-tools/TinyMCEContentItem'
import {Submission} from '@canvas/assignments/graphql/student/Submission'
import apiUserContent from '@canvas/util/jquery/apiUserContent'
import theme from '@instructure/canvas-theme'
import StudentViewContext from '../Context'

// This is how long we wait to see that changes have stopped before actually
// saving the draft
const saveDraftDelayMS = 1000

export default class TextEntry extends React.Component {
  static propTypes = {
    createSubmissionDraft: func,
    focusOnInit: bool.isRequired,
    onContentsChanged: func,
    submission: Submission.shape,
    readOnly: bool
  }

  _isMounted = false

  _isInitted = false

  _saveDraftTimer = null

  _lastSavedContent = null

  _rceRef = createRef()

  handleMessage = e => {
    const editor = this._rceRef.current
    if (editor == null || e.data.subject !== 'A2ExternalContentReady') {
      return
    }

    e.data.content_items
      .map(contentItem => TinyMCEContentItem.fromJSON(contentItem).codePayload)
      .forEach(code => {
        editor.insertCode(code)
      })
  }

  getDraftBody = () => {
    const {submission} = this.props
    if (['graded', 'submitted'].includes(submission.state)) {
      // If this attempt has been submitted/graded, use it. (It could be null
      // if a grade was given without a proper submission; the RCE will throw
      // an error in that case, so default to an empty string.)
      return submission.body || ''
    } else if (submission.submissionDraft != null) {
      // If a draft object exists, get the submission contents from it
      return submission.submissionDraft.body || ''
    }

    // If the submission is marked as unsubmitted and there's no draft object,
    // the user has started a new attempt but not entered any text, so return
    // an empty string. The body attribute may contain the contents of a
    // previous attempt, which we don't want.
    return ''
  }

  componentDidMount() {
    this._isMounted = true

    window.addEventListener('message', this.handleMessage)
  }

  componentDidUpdate(prevProps) {
    if (this._tinyeditor == null) {
      return
    }

    if (this.props.submission.attempt !== prevProps.submission.attempt) {
      const body = this.getDraftBody()
      if (body !== this._tinyeditor.getContent()) {
        this._tinyeditor.setContent(body)
      }
      this._lastSavedContent = body

      if (this.props.focusOnInit) {
        this._rceRef?.current?.focus()
      }
    }
  }

  componentWillUnmount() {
    this._isMounted = false
    clearTimeout(this._saveDraftTimer)
    window.removeEventListener('message', this.handleMessage)
  }

  checkForChanges = newContent => {
    // The idea here:
    // - Every time this function is called (currently when the RCE content changes),
    //   check whether the contents of the editor have changed, assuming we're
    //   in a state where we care about changes.
    // - If we see changes, call the onContentsChanged prop, and schedule a
    //   timer to actually save the draft. Further changes to the content will
    //   cancel/re-schedule this timer, so that we only actually save the draft
    //   after the user has stopped making changes for some time.
    if (!this._isInitted) {
      return
    }

    const {submission} = this.props

    const isNewAttempt = submission.submissionDraft == null && submission.state === 'unsubmitted'
    // If read-only *or* this is a brand new attempt with no content,
    // we don't want to save a draft, so don't bother comparing
    if (isNewAttempt && newContent === '') {
      return
    }

    const editorContents = newContent
    if (this._lastSavedContent !== editorContents) {
      this._lastSavedContent = editorContents

      this.props.onContentsChanged()

      clearTimeout(this._saveDraftTimer)
      this._saveDraftTimer = setTimeout(() => {
        this.saveSubmissionDraft({attempt: submission.attempt, rceText: editorContents})
      }, saveDraftDelayMS)
    }
  }

  // Note: I believe there's a bug in tinymce, that
  // if you set focus:true to give the editor focus on init,
  // then the internal bookkeeping doesn't know it has focus
  // and it does not handle the focusout event correctly.
  // Start w/o focus, then give it focus after initialization
  // in this.handleRCEInit
  handleRCEInit = tinyeditor => {
    this._tinyeditor = tinyeditor

    const draftBody = this.getDraftBody()
    tinyeditor.setContent(draftBody)
    this._lastSavedContent = draftBody

    if (this.props.focusOnInit) {
      this._rceRef.current.focus()
    }
    this._isInitted = true
  }

  handleEditorFocus = _event => {
    // these two lines put the caret at the end of the text when focused
    this._tinyeditor.selection.select(this._tinyeditor.getBody(), true)
    this._tinyeditor.selection.collapse(false)
  }

  getRCEText = () => {
    return this._rceRef.current?.getCode()
  }

  saveSubmissionDraft = async ({attempt, rceText}) => {
    await this.props.createSubmissionDraft({
      variables: {
        id: this.props.submission.id,
        activeSubmissionType: 'online_text_entry',
        attempt: attempt || 1,
        body: rceText
      }
    })
  }

  renderContent() {
    return (
      <div
        data-testid="read-only-content"
        dangerouslySetInnerHTML={{
          __html: apiUserContent.convert(this.props.submission.body)
        }}
      />
    )
  }

  renderRCE(context) {
    return (
      <div
        data-testid="text-editor"
        style={{padding: `${theme.variables.spacing.small} ${theme.variables.spacing.xLarge}`}}
      >
        <CanvasRce
          ref={this._rceRef}
          autosave={false}
          defaultContent={this.getDraftBody()}
          editorOptions={{
            focus: false
          }}
          height={300}
          readOnly={context.isObserver}
          textareaId="textentry_text"
          onFocus={this.handleEditorFocus}
          onBlur={() => {}}
          onInit={this.handleRCEInit}
          onContentChange={content => {
            this.checkForChanges(content)
          }}
        />
      </div>
    )
  }

  render() {
    return (
      <>
        {this.props.readOnly ? (
          this.renderContent()
        ) : (
          <StudentViewContext.Consumer>
            {context => this.renderRCE(context)}
          </StudentViewContext.Consumer>
        )}
      </>
    )
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright 2016 EPAM Systems
 *
 *
 * This file is part of EPAM Report Portal.
 * https://github.com/reportportal/service-ui
 *
 * Report Portal is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Report Portal is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Report Portal.  If not, see <http://www.gnu.org/licenses/>.
 */

define(function (require, exports, module) {
    'use strict';

    var $ = require('jquery');
    var Backbone = require('backbone');
    var App = require('app');
    var UserModel = require('model/UserModel');
    var SingletonUserStorage = require('storage/SingletonUserStorage');

    var Header = require('sections/header');
    var Content = require('sections/content');
    var Sidebar = require('sections/sidebar');
    var Footer = require('sections/footer');

    var config = App.getInstance();

    var MainView = Backbone.View.extend({
        initialize: function (options) {
            this.$el = options.el;
            this.contextName = 'admin';
            this.$body = $("#mainContainer", this.$el);
            this.user = new UserModel();

            this.userStorage = new SingletonUserStorage();
        },

        events: {
            'click #logout': 'onClickLogout'
        },

        render: function (options) {
            config.currentProjectsSettings = {};
            var lastURL = this.userStorage.get('lastActiveURL') || config.userModel.get('defaultProject');
            this.sidebarView = new Sidebar({
                tpl: 'tpl-admin-side-bar',
                lastURL: lastURL
            }).render();
            this.headerView = new Header({
                isAdminPage: true,
                tpl: 'tpl-admin-header',
                currentPage: options.page,
                lastURL: lastURL
            }).render();
            this.footerView = new Footer().render();

            this.contentView = new Content({
                isAdminPage: true,
                contextName: this.contextName,
                el: this.$body,
                queryString: options.queryString
            }).render(options);
            return this;
        },

        onClickLogout: function (e) {
            e.preventDefault();
            this.user.logout();
        },

        update: function (options) {
            this.contentView.update(options);
            this.headerView.update(options);
        },

        destroy: function () {
            this.contentView.destroy();
            this.contentView = null;
            this.sidebarView.destroy();
            this.sidebarView = null;
            this.headerView.destroy();
            this.headerView = null;
            this.footerView.destroy();
            this.footerView = null;

            this.undelegateEvents();
            this.stopListening();
            this.$el && this.$el.off();
        }
    });

    return {
        MainView: MainView
    };
});
<commit_msg>EPMRPP-23337. Fix for managment board page, which was not scrolled to the top after rendering in FF browser.<commit_after>/*
 * Copyright 2016 EPAM Systems
 *
 *
 * This file is part of EPAM Report Portal.
 * https://github.com/reportportal/service-ui
 *
 * Report Portal is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Report Portal is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Report Portal.  If not, see <http://www.gnu.org/licenses/>.
 */

define(function (require, exports, module) {
    'use strict';

    var $ = require('jquery');
    var Backbone = require('backbone');
    var App = require('app');
    var UserModel = require('model/UserModel');
    var SingletonUserStorage = require('storage/SingletonUserStorage');

    var Header = require('sections/header');
    var Content = require('sections/content');
    var Sidebar = require('sections/sidebar');
    var Footer = require('sections/footer');

    var config = App.getInstance();

    var MainView = Backbone.View.extend({
        initialize: function (options) {
            this.$el = options.el;
            this.contextName = 'admin';
            this.$body = $("#mainContainer", this.$el);
            this.user = new UserModel();

            this.userStorage = new SingletonUserStorage();
        },

        events: {
            'click #logout': 'onClickLogout'
        },

        render: function (options) {
            config.currentProjectsSettings = {};
            var lastURL = this.userStorage.get('lastActiveURL') || config.userModel.get('defaultProject');

            this.headerView = new Header({
                isAdminPage: true,
                tpl: 'tpl-admin-header',
                currentPage: options.page,
                lastURL: lastURL
            }).render();

            this.contentView = new Content({
                isAdminPage: true,
                contextName: this.contextName,
                el: this.$body,
                queryString: options.queryString
            }).render(options);

            this.footerView = new Footer().render();

            this.sidebarView = new Sidebar({
                tpl: 'tpl-admin-side-bar',
                lastURL: lastURL
            }).render();
            return this;
        },

        onClickLogout: function (e) {
            e.preventDefault();
            this.user.logout();
        },

        update: function (options) {
            this.contentView.update(options);
            this.headerView.update(options);
        },

        destroy: function () {
            this.contentView.destroy();
            this.contentView = null;
            this.sidebarView.destroy();
            this.sidebarView = null;
            this.headerView.destroy();
            this.headerView = null;
            this.footerView.destroy();
            this.footerView = null;

            this.undelegateEvents();
            this.stopListening();
            this.$el && this.$el.off();
        }
    });

    return {
        MainView: MainView
    };
});
<|endoftext|>


<commit_before>        $(document).ready(function() {
            $('#startDate, #endDate, #create_tender_enddate').datepicker({
                format: 'yyyy/mm/dd',
                startDate: '-5y'
            });

            $("#category_filter").select2({
                placeholder: "All categories"
            });

            $("#item_dropdown").select2({
                placeholder: "Select a items"

            });

            $("#location_filter").select2({
                placeholder: "All locations"
            });

            $("#status_filter").select2({
                placeholder: "All active statuses"
            });

            $("#location_filter, #item_dropdown, #status_filter, #price_from, #price_to, #date_from, #date_to").change(function() {
                enableFilterButtons();
            });

            $("#category_filter").change(function() {
                enableFilterButtons();
                $("#item_dropdown").select2('val', 'All');
                var str='';
                var categories=new Array();
                categories=$('#category_filter').val();
                if (categories!=null){
                    str += "categories="+categories;
                }
                $.ajax({
                    url: "/tenders/items",
                    type: "GET",
                    data:  str,
                    dataType:'json',

                    success: function(data) {
                        var html = '';
                        var len = data.length;
                        for (var i = 0; i < len; i++) {
                            html += '<option value="' + data[i].id + '">'
                                + data[i].name + '</option>';
                        }

                        $('#item_dropdown').html(html);
                    },
                    error:function(){
                        alert("ERROR");
                    }
                });
            });

            $.getJSON('/tenders/statuses', {
                  ajax : 'true'
                }, function(data){
                  var html;
                  var len = data.length;
                  for (var i = 0; i < len; i++) {
                      html += '<option value="' + data[i].id + '">'
                               + data[i].name + '</option>';
                  }
                  $('#status_filter').html(html);
            });

            $.getJSON('/tenders/locations', {
                ajax : 'true'
            }, function(loc){
                var html = ' ';
                var len = loc.length;
                for (var i = 0; i < len; i++) {
                    html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                }

                $('#location_filter').html(html);
            });

            $.getJSON('/tenders/categories', function(data){
                var html = ' ';
                var len = data.length;
                for (var i = 0; i < len; i++) {
                    html += '<option value="' + data[i].id + '">'
                            + data[i].name + '</option>';
                }

                $('#category_filter').html(html);
            });
            populateItemDropdown();
            showTenders();

            $('#search_input').keydown(function(event) {
                if (event.keyCode == 13) {
                    //search query

                    return false;
                }
            });

            $('#createTenderWindow').on('shown.bs.modal', function () {
                $('.datepicker').addClass('modal_datepicker');

                $("#create_tender_unit_category").select2({
                    placeholder: "Select a category"
                });

                $("#create_tender_unit_item").select2({
                    placeholder: "Select a item"
                });

                $("#create_tender_location").select2({
                    placeholder: "Select a location"
                });

                $.getJSON('/tenders/locations', {
                    ajax : 'true'
                }, function(loc){
                    var html = ' ';
                    var len = loc.length;
                    for (var i = 0; i < len; i++) {
                        html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                    }

                    $('#create_tender_location').html(html);
                });

                $.getJSON('/tenders/categories', {
                    ajax : 'true'
                }, function(loc){
                    var html = ' ';
                    var len = loc.length;
                    for (var i = 0; i < len; i++) {
                        html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                    }

                    $('#create_tender_unit_category').html(html);
                });

                $.getJSON('/tenders/items', {
                    ajax : 'true'
                }, function(loc){
                    var html = ' ';
                    var len = loc.length;
                    for (var i = 0; i < len; i++) {
                        html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                    }

                    $('#create_tender_unit_item').html(html);
                });
            });
        });

        function populateItemDropdown() {
            $.getJSON('/tenders/items', function (data) {
                var html = '';
                var len = data.length;
                for (var i = 0; i < len; i++) {
                    html += '<option value="' + data[i].id + '">'
                            + data[i].name + '</option>';
                }

                $('#item_dropdown').html(html);
            });
        }

        function showTenders() {
            $.getJSON('/tenders', function (data) {
                var html = '';
                var len = data.length;
                if(len > 0) {
                    for (var i = 0; i < len; i++) {
                        html += '
' + data[i].title + '	' +
                                '' + data[i].authorName + '	' +
                                '' + data[i].categories + '	' +
                                '' + data[i].locations + '	' +
                                '' + data[i].suitablePrice + '	' +
                                '' + data[i].status + '	' +
                                '' + data[i].proposals + '	' +
                                '
' +
                                '<select class="form-control items_number_dropdown action_button">' +
                                '<option value="view' + data[i].id + '">View</option>' +
                                '<option value="close' + data[i].id + '">Close</option>' +
                                '<option value="addproposal' + data[i].id + '">Add proposal</option>' +
                                '</select>' +
                                '

';
                    }

                    $('#user_message').html('');
                    $('#tender_items').show();
                    $('#tenders').html(html);

                    var pageItemsNumber = $('#pagination_itemsnum').val();
                    if(pageItemsNumber < len) {
                        $('#pagination').show();
                    } else {
                        $('#pagination').hide();
                    }
                } else {
                    $('#user_message').html('
By your filter parameters did not match any tender
');
                    $('#tender_items').hide();
                    $('#pagination').hide();
                }
            });
        }

        function clearFilters() {
            disableFilterButtons();
            $("#price_from").val("");
            $("#price_to").val("");
            $("#date_from").val("");
            $("#date_to").val("");
            $("#location_filter").select2('val', 'All');
            $("#category_filter").select2('val', 'All');
            $("#item_dropdown").select2('val', 'All');
            $("#status_filter").select2('val', 'All');

            showTenders();
        }

        function enableFilterButtons() {
            $("#filter_button").removeAttr("disabled");
            $("#clear_button").removeAttr("disabled");
        }

        function disableFilterButtons() {
            $("#filter_button").attr("disabled", "disabled");
            $("#clear_button").attr("disabled", "disabled");
        }

        function applyFilters() {
            disableFilterButtons();
            var str='';
            if($("#price_from").val()!=""){
                str+="minPrice="+$("#price_from").val();
            }
            if($("#price_to").val()!=""){
                str += (str.length==0)?"maxPrice="+$("#price_to").val():"&maxPrice="+$("#price_to").val();
            }
            var items=new Array();
            items=$('#item_dropdown').val();
            if (items!=null){
                str += (str.length==0)?"items="+items:"&items="+items;
            }
            var categories=new Array();
            categories=$('#category_filter').val();
            if (categories!=null){
                str += (str.length==0)?"categories="+categories:"&categories="+categories;
            }
            var locations=new Array();
            locations=$('#location_filter').val();
            if (locations!=null){
                str += (str.length==0)?"locations="+locations:"&locations="+locations;
            }
            var statuses=new Array();
            statuses=$('#status_filter').val();
            if (statuses!=null){
                str += (str.length==0)?"statuses="+statuses:"&statuses="+statuses;
            }
            if($("#date_from").val()!=""){
                str += (str.length==0)?"minDate="+$("#date_from").val():"&minDate="+$("#date_from").val();
            }
            if($("#date_to").val()!=""){
                str += (str.length==0)?"maxDate="+$("#date_to").val():"&maxDate="+$("#date_to").val();
            }
            $.ajax({
                url: "/tenders",
                type: "GET",
                data:  str,
                dataType:'json',

                success: function(data) {
                    var html = '';
                    var len = data.length;
                    if(len > 0) {
                        for (var i = 0; i < len; i++) {
                            html += '
' + data[i].title + '	' +
                                    '' + data[i].authorName + '	' +
                                    '' + data[i].categories + '	' +
                                    '' + data[i].locations + '	' +
                                    '' + data[i].suitablePrice + '	' +
                                    '' + data[i].status + '	' +
                                    '' + data[i].proposals + '	'+
                                    '
' +
                                    '<select class="form-control items_number_dropdown action_button">' +
                                    '<option value="view' + data[i].id + '">View</option>' +
                                    '<option value="close' + data[i].id + '">Close</option>' +
                                    '<option value="addproposal' + data[i].id + '">Add proposal</option>' +
                                    '</select>' +
                                    '

';
                        }

                        $('#user_message').html('');
                        $('#tender_items').show();
                        $('#tenders').html(html);

                        var pageItemsNumber = $('#pagination_itemsnum').val();
                        if(pageItemsNumber < len) {
                            $('#pagination').show();
                        } else {
                            $('#pagination').hide();
                        }
                    } else {
                        $('#user_message').html('
By your filter parameters did not match any tender
');
                        $('#tender_items').hide();
                        $('#pagination').hide();
                    }
                },
                error:function(){
                    alert("ERROR");
                }
            });

            $("#clear_button").removeAttr("disabled");
        }

        function showDeals() {
            window.location.href='/deals/';
        }<commit_msg>Fixed bug #36<commit_after>        $(document).ready(function() {
            $('#startDate, #endDate, #create_tender_enddate').datepicker({
                format: 'yyyy/mm/dd',
                startDate: '-5y'
            });

            $("#category_filter").select2({
                placeholder: "All categories"
            });

            $("#item_dropdown").select2({
                placeholder: "Select a items"

            });

            $("#location_filter").select2({
                placeholder: "All locations"
            });

            $("#status_filter").select2({
                placeholder: "All active statuses"
            });

            $("#location_filter, #item_dropdown, #status_filter, #price_from, #price_to, #date_from, #date_to").change(function() {
                enableFilterButtons();
            });

            $("#category_filter").change(function() {
                enableFilterButtons();
                $("#item_dropdown").select2('val', 'All');
                var str='';
                var categories=new Array();
                categories=$('#category_filter').val();
                if (categories!=null){
                    str += "categories="+categories;
                }
                $.ajax({
                    url: "/tenders/items",
                    type: "GET",
                    data:  str,
                    dataType:'json',

                    success: function(data) {
                        var html = '';
                        var len = data.length;
                        for (var i = 0; i < len; i++) {
                            html += '<option value="' + data[i].id + '">'
                                + data[i].name + '</option>';
                        }

                        $('#item_dropdown').html(html);
                    },
                    error:function(){
                        alert("ERROR");
                    }
                });
            });

            $.getJSON('/tenders/statuses', {
                  ajax : 'true'
                }, function(data){
                  var html;
                  var len = data.length;
                  for (var i = 0; i < len; i++) {
                      html += '<option value="' + data[i].id + '">'
                               + data[i].name + '</option>';
                  }
                  $('#status_filter').html(html);
            });

            $.getJSON('/tenders/locations', {
                ajax : 'true'
            }, function(loc){
                var html = ' ';
                var len = loc.length;
                for (var i = 0; i < len; i++) {
                    html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                }

                $('#location_filter').html(html);
            });

            $.getJSON('/tenders/categories', function(data){
                var html = ' ';
                var len = data.length;
                for (var i = 0; i < len; i++) {
                    html += '<option value="' + data[i].id + '">'
                            + data[i].name + '</option>';
                }

                $('#category_filter').html(html);
            });
            populateItemDropdown();
            showTenders();

            $('#search_input').keydown(function(event) {
                if (event.keyCode == 13) {
                    //search query

                    return false;
                }
            });

            $('#createTenderWindow').on('shown.bs.modal', function () {
                $('.datepicker').addClass('modal_datepicker');

                $("#create_tender_unit_category").select2({
                    placeholder: "Select a category"
                });

                $("#create_tender_unit_item").select2({
                    placeholder: "Select a item"
                });

                $("#create_tender_location").select2({
                    placeholder: "Select a location"
                });

                $.getJSON('/tenders/locations', {
                    ajax : 'true'
                }, function(loc){
                    var html = ' ';
                    var len = loc.length;
                    for (var i = 0; i < len; i++) {
                        html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                    }

                    $('#create_tender_location').html(html);
                });

                $.getJSON('/tenders/categories', {
                    ajax : 'true'
                }, function(loc){
                    var html = ' ';
                    var len = loc.length;
                    for (var i = 0; i < len; i++) {
                        html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                    }

                    $('#create_tender_unit_category').html(html);
                });

                $.getJSON('/tenders/items', {
                    ajax : 'true'
                }, function(loc){
                    var html = ' ';
                    var len = loc.length;
                    for (var i = 0; i < len; i++) {
                        html += '<option value="' + loc[i].id + '">'
                            +loc[i].name + '</option>';
                    }

                    $('#create_tender_unit_item').html(html);
                });
            });
        });

        function populateItemDropdown() {
            $.getJSON('/tenders/items', function (data) {
                var html = '';
                var len = data.length;
                for (var i = 0; i < len; i++) {
                    html += '<option value="' + data[i].id + '">'
                            + data[i].name + '</option>';
                }

                $('#item_dropdown').html(html);
            });
        }

        function showTenders() {
            $.getJSON('/tenders', function (data) {
                var html = '';
                var len = data.length;
                if(len > 0) {
                    for (var i = 0; i < len; i++) {
                        html += '
' + data[i].title + '	' +
                                '' + data[i].authorName + '	' +
                                '' + data[i].categories + '	' +
                                '' + data[i].locations + '	' +
                                '' + data[i].suitablePrice + '	' +
                                '' + data[i].status + '	' +
                                '' + data[i].proposals + '	' +
                                '
' +
                                '<select class="form-control items_number_dropdown action_button">' +
                                '<option value="view' + data[i].id + '">View</option>' +
                                '<option value="close' + data[i].id + '">Close</option>' +
                                '<option value="addproposal' + data[i].id + '">Add proposal</option>' +
                                '</select>' +
                                '

';
                    }

                    $('#user_message').html('');
                    $('#tender_items').show();
                    $('#tenders').html(html);

                    var pageItemsNumber = $('#pagination_itemsnum').val();
                    if(pageItemsNumber < len) {
                        $('#pagination').show();
                    } else {
                        $('#pagination').hide();
                    }
                } else {
                    $('#user_message').html('
Your filter parameters did not match any tender
');
                    $('#tender_items').hide();
                    $('#pagination').hide();
                }
            });
        }

        function clearFilters() {
            disableFilterButtons();
            $("#price_from").val("");
            $("#price_to").val("");
            $("#date_from").val("");
            $("#date_to").val("");
            $("#location_filter").select2('val', 'All');
            $("#category_filter").select2('val', 'All');
            $("#item_dropdown").select2('val', 'All');
            $("#status_filter").select2('val', 'All');

            showTenders();
        }

        function enableFilterButtons() {
            $("#filter_button").removeAttr("disabled");
            $("#clear_button").removeAttr("disabled");
        }

        function disableFilterButtons() {
            $("#filter_button").attr("disabled", "disabled");
            $("#clear_button").attr("disabled", "disabled");
        }

        function applyFilters() {
            disableFilterButtons();
            var str='';
            if($("#price_from").val()!=""){
                str+="minPrice="+$("#price_from").val();
            }
            if($("#price_to").val()!=""){
                str += (str.length==0)?"maxPrice="+$("#price_to").val():"&maxPrice="+$("#price_to").val();
            }
            var items=new Array();
            items=$('#item_dropdown').val();
            if (items!=null){
                str += (str.length==0)?"items="+items:"&items="+items;
            }

            var categories = new Array();
            categories = $('#category_filter').val();

            $.ajax({
              dataType: "json",
              url: '/tenders/categories',
              async: false,
              success: function(data){
                    var dataLength = data.length;
                    var catNumber = categories.length;
                    for(var z = 0;z<catNumber;z++) {
                        for (var i = 0; i < dataLength; i++) {
                            if(categories[z] == data[i].parent) {
                                categories.push(""+data[i].id);
                            }
                        }
                    }
                }
            });

            if (categories != null){
                str += (str.length == 0)?"categories="+categories:"&categories="+categories;
            }

            var locations=new Array();
            locations=$('#location_filter').val();
            if (locations!=null){
                str += (str.length==0)?"locations="+locations:"&locations="+locations;
            }
            var statuses=new Array();
            statuses=$('#status_filter').val();
            if (statuses!=null){
                str += (str.length==0)?"statuses="+statuses:"&statuses="+statuses;
            }
            if($("#date_from").val()!=""){
                str += (str.length==0)?"minDate="+$("#date_from").val():"&minDate="+$("#date_from").val();
            }
            if($("#date_to").val()!=""){
                str += (str.length==0)?"maxDate="+$("#date_to").val():"&maxDate="+$("#date_to").val();
            }
            $.ajax({
                url: "/tenders",
                type: "GET",
                data:  str,
                dataType:'json',

                success: function(data) {
                    var html = '';
                    var len = data.length;
                    if(len > 0) {
                        for (var i = 0; i < len; i++) {
                            html += '
' + data[i].title + '	' +
                                    '' + data[i].authorName + '	' +
                                    '' + data[i].categories + '	' +
                                    '' + data[i].locations + '	' +
                                    '' + data[i].suitablePrice + '	' +
                                    '' + data[i].status + '	' +
                                    '' + data[i].proposals + '	'+
                                    '
' +
                                    '<select class="form-control items_number_dropdown action_button">' +
                                    '<option value="view' + data[i].id + '">View</option>' +
                                    '<option value="close' + data[i].id + '">Close</option>' +
                                    '<option value="addproposal' + data[i].id + '">Add proposal</option>' +
                                    '</select>' +
                                    '

';
                        }

                        $('#user_message').html('');
                        $('#tender_items').show();
                        $('#tenders').html(html);

                        var pageItemsNumber = $('#pagination_itemsnum').val();
                        if(pageItemsNumber < len) {
                            $('#pagination').show();
                        } else {
                            $('#pagination').hide();
                        }
                    } else {
                        $('#user_message').html('
Your filter parameters did not match any tender
');
                        $('#tender_items').hide();
                        $('#pagination').hide();
                    }
                },
                error:function(){
                    alert("ERROR");
                }
            });

            $("#clear_button").removeAttr("disabled");
        }

        function showDeals() {
            window.location.href='/deals/';
        }<|endoftext|>


<commit_before>import { h, Component } from 'preact' /** @jsx h */
import { Field } from 'redux-form'
import { TextField } from '~src/views/components/form'
import { Form } from './forms'
import { ButtonPrimary } from '../../views/components'

export default class MemoryCommentsForm extends Component {
  render() {
    const { handleSubmit, onSubmit, error } = this.props

    return (
      <Form
        error={error}
        handleSubmit={handleSubmit((...args) => {
          onSubmit(...args)
          //
          // blur fields after submit
          //
          this.comment.base.children[0].children.comment.blur()
          this.name.base.children[0].children.name.blur()
          this.email.base.children[0].children.email.blur()
        })}
      >
        <Field
          name="memoryId"
          type="hidden"
          component="input"
        />
        <Field
          label="Texto"
          name="comment"
          ref={(comment) => { this.comment = comment }}
          type="text"
          component={TextField}
        />
        <Field
          label="Nome completo"
          name="name"
          ref={(name) => { this.name = name }}
          type="text"
          component={TextField}
        />
        <Field
          label="Email"
          name="email"
          ref={(email) => { this.email = email }}
          type="email"
          component={TextField}
        />

        <ButtonPrimary TagName="button" type="submit">
          Enviar comentário
        </ButtonPrimary>
      </Form>
    )
  }
}
<commit_msg>fix(components): blur of memory comments form  after submit #93<commit_after>import { h, Component } from 'preact' /** @jsx h */
import { Field } from 'redux-form'
import { TextField } from '~src/views/components/form'
import { Form } from './forms'
import { ButtonPrimary } from '../../views/components'

export default class MemoryCommentsForm extends Component {
  render() {
    const { handleSubmit, onSubmit, error } = this.props

    return (
      <Form
        error={error}
        handleSubmit={handleSubmit((...args) => {
          onSubmit(...args)
          //
          // blur fields after submit
          //
          this.comment.base.children[0].children[1].children[1].children.comment.blur()
          this.name.base.children[0].children[1].children[1].children.name.blur()
          this.email.base.children[0].children[1].children[1].children.email.blur()
        })}
      >
        <Field
          name="memoryId"
          type="hidden"
          component="input"
        />
        <Field
          label="Texto"
          name="comment"
          ref={(comment) => { this.comment = comment }}
          type="text"
          component={TextField}
        />
        <Field
          label="Nome completo"
          name="name"
          ref={(name) => { this.name = name }}
          type="text"
          component={TextField}
        />
        <Field
          label="Email"
          name="email"
          ref={(email) => { this.email = email }}
          type="email"
          component={TextField}
        />

        <ButtonPrimary TagName="button" type="submit">
          Enviar comentário
        </ButtonPrimary>
      </Form>
    )
  }
}
<|endoftext|>


<commit_before>(function () {
    'use strict';

    /**
     * @param Base
     * @return {Chart}
     */
    const factory = function (Base) {

        const { Money } = require('@waves/data-entities');
        const tsUtils = require('ts-utils');

        class ChartFactory extends Base {

            /**
             * @param {JQuery} $element
             * @param {ChartFactory.IOptions} [options]
             * @param {Array} [data]
             */
            constructor($element, options, data) {
                super();

                this.$parent = $element;
                /**
                 * @type {ChartFactory.IOptions}
                 */
                this.options = Object.assign(Object.create(null), ChartFactory.defaultOptions, options);
                /**
                 * @type {Array}
                 */
                this.data = data;
                /**
                 * @type {HTMLCanvasElement}
                 */
                this.canvas = this._initializeCanvasElement($element);
                /**
                 * @type {CanvasRenderingContext2D}
                 */
                this.ctx = this.canvas.getContext('2d');

                this._render();
            }

            setOptions(options) {
                this.options = options;

                this._render();
            }

            setData(data) {
                this.data = data;

                this._render();
            }

            /**
             * @param {JQuery} $element
             * @return {HTMLCanvasElement}
             * @private
             */
            _initializeCanvasElement($element) {
                const canvas = document.createElement('canvas');
                canvas.style.position = 'absolute';
                canvas.style.left = '0';
                canvas.style.top = '0';

                if ($element.css('position') === 'static') {
                    $element.css('position', 'relative');
                }

                $element.append(canvas);
                canvas.width = $element.width();
                canvas.height = $element.height();
                return canvas;
            }

            _clear() {
                this._clearCanvas();
            }

            _clearCanvas() {
                this.canvas.width = this.canvas.width;
            }

            _render() {
                this._clear();

                if (!this.data || !this.data.length) {
                    return null;
                }

                if (!this.options.charts || !this.options.charts.length) {
                    return null;
                }

                const data = this._getChartData();

                data.forEach(item => {
                    this._drawChart(item);
                });
            }

            /**
             * @param {ChartFactory._IChartData} data
             * @private
             */
            _drawChart(data) {

                this.ctx.strokeStyle = data.lineColor;

                this.ctx.lineWidth = data.lineWidth;

                const first = data.coordinates[0];
                this.ctx.moveTo(first.x, first.y);

                data.coordinates.forEach(({ x, y }) => {
                    this.ctx.lineTo(x, y);
                });

                this.ctx.stroke();

                if (data.fillColor) {
                    this.ctx.fillStyle = data.fillColor;
                    if (data.gradientColor) {
                        const grd = this.ctx.createLinearGradient(0, 0, 0, data.height.toNumber());
                        grd.addColorStop(0, data.gradientColor[0]);
                        grd.addColorStop(1, data.gradientColor[1]);
                        this.ctx.fillStyle = grd;
                    } else {
                        this.ctx.fillStyle = data.fillColor;
                    }
                    const last = data.coordinates[data.coordinates.length - 1];
                    this.ctx.lineTo(last.x, data.height.toNumber());
                    this.ctx.lineTo(first.x, data.height.toNumber());
                    this.ctx.lineTo(first.x, first.y);

                    this.ctx.fill();
                }
            }

            _getChartData() {
                const height = new BigNumber(this.canvas.height);
                const width = new BigNumber(this.canvas.width);
                const maxChartHeight = height.times(0.9);

                return this.options.charts.map(chartOptions => {
                    const xValues = [];
                    const yValues = [];

                    this.data.forEach(item => {
                        const x = ChartFactory._getValues(item[chartOptions.axisX]);
                        const y = ChartFactory._getValues(item[chartOptions.axisY]);

                        if (tsUtils.isNotEmpty(x) && tsUtils.isNotEmpty(y)) {
                            xValues.push(x);
                            yValues.push(y);
                        }
                    });

                    const xMin = BigNumber.min(...xValues);
                    const xMax = BigNumber.max(...xValues);
                    const yMin = BigNumber.min(...yValues);
                    const yMax = BigNumber.max(...yValues);
                    const xDelta = xMax.minus(xMin);
                    const yDelta = yMax.minus(yMin);
                    const xFactor = width.div(xDelta);
                    const yFactor = maxChartHeight.div(yDelta);

                    const coordinates = [];

                    for (let i = 0; i < xValues.length; i++) {
                        const xValue = xValues[i];
                        const yValue = yValues[i];

                        const x = xValue.minus(xMin).times(xFactor).toNumber();
                        const y = height.minus(yValue.minus(yMin).times(yFactor)).toNumber();

                        coordinates.push({ x, y });
                    }

                    return {
                        height,
                        maxChartHeight,
                        width,
                        xValues,
                        yValues,
                        xMin,
                        xMax,
                        yMin,
                        yMax,
                        xDelta,
                        yDelta,
                        xFactor,
                        yFactor,
                        coordinates,
                        length: coordinates.length,
                        ...chartOptions
                    };
                });
            }

            static defaultOptions = {
                charts: [
                    {
                        axisX: 'timestamp',
                        axisY: 'rate',
                        lineColor: '#ef4829',
                        fillColor: '#FFF',
                        gradientColor: false,
                        lineWidth: 2
                    }
                ]
            };

            /**
             * @param item
             * @return {BigNumber}
             * @private
             */
            static _getValues(item) {
                switch (true) {
                    case item instanceof Date:
                        return new BigNumber(item.getTime());
                    case item instanceof Money:
                        return item.getTokens();
                    case item instanceof BigNumber:
                        return item;
                    case typeof item === 'number':
                        return new BigNumber(item);
                    default:
                        throw new Error('Wrong value type!');
                }
            }

        }

        return ChartFactory;
    };

    factory.$inject = ['Base', 'utils'];

    angular.module('app.ui').factory('ChartFactory', factory);
})();

/**
 * @name ChartFactory
 */

/**
 * @typedef {object} ChartFactory#IOptions
 * @property {ChartFactory.IChart[]} charts
 */

/**
 * @typedef {object} ChartFactory#IChart
 * @property {string} axisX
 * @property {string} axisY
 * @property {string} lineColor
 * @property {string} [fillColor]
 * @property {array} gradientColor
 */

/**
 * @typedef {object} ChartFactory#_IChartData
 * @property {BigNumber} height
 * @property {BigNumber} maxChartHeight
 * @property {BigNumber} width
 * @property {BigNumber[]} xValues
 * @property {BigNumber[]} yValues
 * @property {BigNumber} xMin
 * @property {BigNumber} xMax
 * @property {BigNumber} yMin
 * @property {BigNumber} yMax
 * @property {BigNumber} xDelta
 * @property {BigNumber} yDelta
 * @property {BigNumber} xFactor
 * @property {BigNumber} yFactor
 * @property {Array<{x: number, y: number}>} coordinates
 * @property {number} length
 * @property {string} axisX
 * @property {string} axisY
 * @property {string} lineColor
 * @property {string} [fillColor]
 * @property {array} gradientColor
 */
<commit_msg>CLIENT-1842: Fix chart factory<commit_after>(function () {
    'use strict';

    /**
     * @param Base
     * @return {Chart}
     */
    const factory = function (Base) {

        const { Money } = require('@waves/data-entities');
        const tsUtils = require('ts-utils');
        const SCALE = 3;

        class ChartFactory extends Base {

            /**
             * @param {JQuery} $element
             * @param {ChartFactory.IOptions} [options]
             * @param {Array} [data]
             */
            constructor($element, options, data) {
                super();

                this.$parent = $element;
                /**
                 * @type {ChartFactory.IOptions}
                 */
                this.options = Object.assign(Object.create(null), ChartFactory.defaultOptions, options);
                /**
                 * @type {Array}
                 */
                this.data = data;
                /**
                 * @type {HTMLCanvasElement}
                 */
                this.canvas = this._initializeCanvasElement($element);
                /**
                 * @type {CanvasRenderingContext2D}
                 */
                this.ctx = this.canvas.getContext('2d');

                this._render();
            }

            setOptions(options) {
                this.options = options;

                this._render();
            }

            setData(data) {
                this.data = data;

                this._render();
            }

            /**
             * @param {JQuery} $element
             * @return {HTMLCanvasElement}
             * @private
             */
            _initializeCanvasElement($element) {
                const canvas = document.createElement('canvas');
                const width = Math.round($element.width());
                const height = Math.round($element.height());

                canvas.style.position = 'absolute';
                canvas.style.left = '0';
                canvas.style.top = '0';
                canvas.style.width = `${width}px`;
                canvas.style.height = `${height}px`;

                if ($element.css('position') === 'static') {
                    $element.css('position', 'relative');
                }

                $element.append(canvas);
                canvas.width = width * SCALE;
                canvas.height = height * SCALE;
                return canvas;
            }

            _clear() {
                this._clearCanvas();
            }

            _clearCanvas() {
                this.canvas.width = this.canvas.width;
            }

            _render() {
                this._clear();

                if (!this.data || !this.data.length) {
                    return null;
                }

                if (!this.options.charts || !this.options.charts.length) {
                    return null;
                }

                const data = this._getChartData();

                data.forEach(item => {
                    this._drawChart(item);
                });
            }

            /**
             * @param {ChartFactory._IChartData} data
             * @private
             */
            _drawChart(data) {

                this.ctx.strokeStyle = data.lineColor;
                this.ctx.lineWidth = data.lineWidth * SCALE;

                const first = data.coordinates[0];
                this.ctx.moveTo(first.x, first.y);

                data.coordinates.forEach(({ x, y }) => {
                    this.ctx.lineTo(x, y);
                });

                this.ctx.stroke();

                if (data.fillColor) {
                    this.ctx.fillStyle = data.fillColor;
                    if (data.gradientColor) {
                        const grd = this.ctx.createLinearGradient(0, 0, 0, data.height.toNumber());
                        grd.addColorStop(0, data.gradientColor[0]);
                        grd.addColorStop(1, data.gradientColor[1]);
                        this.ctx.fillStyle = grd;
                    } else {
                        this.ctx.fillStyle = data.fillColor;
                    }
                    const last = data.coordinates[data.coordinates.length - 1];
                    this.ctx.lineTo(last.x, data.height.toNumber());
                    this.ctx.lineTo(first.x, data.height.toNumber());
                    this.ctx.lineTo(first.x, first.y);

                    this.ctx.fill();
                }
            }

            _getChartData() {
                const height = new BigNumber(this.canvas.height);
                const width = new BigNumber(this.canvas.width);
                const maxChartHeight = height.times(0.9);

                return this.options.charts.map(chartOptions => {
                    const xValues = [];
                    const yValues = [];

                    this.data.forEach(item => {
                        const x = ChartFactory._getValues(item[chartOptions.axisX]);
                        const y = ChartFactory._getValues(item[chartOptions.axisY]);

                        if (tsUtils.isNotEmpty(x) && tsUtils.isNotEmpty(y)) {
                            xValues.push(x);
                            yValues.push(y);
                        }
                    });

                    const xMin = BigNumber.min(...xValues);
                    const xMax = BigNumber.max(...xValues);
                    const yMin = BigNumber.min(...yValues);
                    const yMax = BigNumber.max(...yValues);
                    const xDelta = xMax.minus(xMin);
                    const yDelta = yMax.minus(yMin);
                    const xFactor = width.div(xDelta);
                    const yFactor = maxChartHeight.div(yDelta);

                    const coordinates = [];

                    for (let i = 0; i < xValues.length; i++) {
                        const xValue = xValues[i];
                        const yValue = yValues[i];

                        const x = xValue.minus(xMin).times(xFactor).toNumber();
                        const y = height.minus(yValue.minus(yMin).times(yFactor)).toNumber();

                        coordinates.push({ x, y });
                    }

                    return {
                        height,
                        maxChartHeight,
                        width,
                        xValues,
                        yValues,
                        xMin,
                        xMax,
                        yMin,
                        yMax,
                        xDelta,
                        yDelta,
                        xFactor,
                        yFactor,
                        coordinates,
                        length: coordinates.length,
                        ...chartOptions
                    };
                });
            }

            static defaultOptions = {
                charts: [
                    {
                        axisX: 'timestamp',
                        axisY: 'rate',
                        lineColor: '#ef4829',
                        fillColor: '#FFF',
                        gradientColor: false,
                        lineWidth: 2
                    }
                ]
            };

            /**
             * @param item
             * @return {BigNumber}
             * @private
             */
            static _getValues(item) {
                switch (true) {
                    case item instanceof Date:
                        return new BigNumber(item.getTime());
                    case item instanceof Money:
                        return item.getTokens();
                    case item instanceof BigNumber:
                        return item;
                    case typeof item === 'number':
                        return new BigNumber(item);
                    default:
                        throw new Error('Wrong value type!');
                }
            }

        }

        return ChartFactory;
    };

    factory.$inject = ['Base', 'utils'];

    angular.module('app.ui').factory('ChartFactory', factory);
})();

/**
 * @name ChartFactory
 */

/**
 * @typedef {object} ChartFactory#IOptions
 * @property {ChartFactory.IChart[]} charts
 */

/**
 * @typedef {object} ChartFactory#IChart
 * @property {string} axisX
 * @property {string} axisY
 * @property {string} lineColor
 * @property {string} [fillColor]
 * @property {array} gradientColor
 */

/**
 * @typedef {object} ChartFactory#_IChartData
 * @property {BigNumber} height
 * @property {BigNumber} maxChartHeight
 * @property {BigNumber} width
 * @property {BigNumber[]} xValues
 * @property {BigNumber[]} yValues
 * @property {BigNumber} xMin
 * @property {BigNumber} xMax
 * @property {BigNumber} yMin
 * @property {BigNumber} yMax
 * @property {BigNumber} xDelta
 * @property {BigNumber} yDelta
 * @property {BigNumber} xFactor
 * @property {BigNumber} yFactor
 * @property {Array<{x: number, y: number}>} coordinates
 * @property {number} length
 * @property {number} lineWidth
 * @property {string} axisX
 * @property {string} axisY
 * @property {string} lineColor
 * @property {string} [fillColor]
 * @property {array} gradientColor
 */
<|endoftext|>


<commit_before>angular.module('mwUI.List')
//TODO rename to mwListUrlActionButton
  .directive('mwListableAction', function ($timeout) {
    return {
      require: ['^mwListableBb', '^?mwListableBodyRowBb'],
      transclude: true,
      templateUrl: 'uikit/mw-list/directives/templates/mw_list_action_button.html',
      scope: {
        action: '&mwListableAction'
      },
      link: function (scope, elm, attr, ctrls) {
        var id,
          mwListableCtrl = ctrls[0],
          mwlistableBodyRowBbCtrl = ctrls[1];

        var addAction = function () {
          if (_.isNumber(id)) {
            var action = scope.action;
            var existingItem = _.findWhere(mwListableCtrl.actionColumns, {id: id});

            if (!existingItem) {
              var item = {id: id, actions: [action]};
              mwListableCtrl.actionColumns.push(item);
              existingItem = item;
            } else {
              existingItem.actions.push(action);
            }

            if(existingItem.actions.length > mwListableCtrl.maxActionColumnsAmount){
              mwListableCtrl.maxActionColumnsAmount = existingItem.actions.length;
            }
          }
        };

        var removeAction = function () {
          if (_.isNumber(id)) {
            var existingItem = _.findWhere(mwListableCtrl.actionColumns, {id: id});
            if (existingItem) {
              var indexOfExistingAction = _.indexOf(existingItem.actions, scope.action);
              existingItem.actions.splice(indexOfExistingAction, 1);

              if(existingItem.actions.length===0){
                var indexOfExistingItem = _.indexOf(mwListableCtrl.actionColumns, existingItem);
                mwListableCtrl.actionColumns.splice(indexOfExistingItem, 1);
              }
            }
          }
        };

        scope.execute = function () {
          $timeout(scope.action);
        };

        if (mwlistableBodyRowBbCtrl) {
          id = mwlistableBodyRowBbCtrl.getId();
        }

        addAction(attr.action);
        scope.$on('$destroy', removeAction);
      }
    };
  });<commit_msg>remove old todo<commit_after>angular.module('mwUI.List')

  .directive('mwListableAction', function ($timeout) {
    return {
      require: ['^mwListableBb', '^?mwListableBodyRowBb'],
      transclude: true,
      templateUrl: 'uikit/mw-list/directives/templates/mw_list_action_button.html',
      scope: {
        action: '&mwListableAction'
      },
      link: function (scope, elm, attr, ctrls) {
        var id,
          mwListableCtrl = ctrls[0],
          mwlistableBodyRowBbCtrl = ctrls[1];

        var addAction = function () {
          if (_.isNumber(id)) {
            var action = scope.action;
            var existingItem = _.findWhere(mwListableCtrl.actionColumns, {id: id});

            if (!existingItem) {
              var item = {id: id, actions: [action]};
              mwListableCtrl.actionColumns.push(item);
              existingItem = item;
            } else {
              existingItem.actions.push(action);
            }

            if(existingItem.actions.length > mwListableCtrl.maxActionColumnsAmount){
              mwListableCtrl.maxActionColumnsAmount = existingItem.actions.length;
            }
          }
        };

        var removeAction = function () {
          if (_.isNumber(id)) {
            var existingItem = _.findWhere(mwListableCtrl.actionColumns, {id: id});
            if (existingItem) {
              var indexOfExistingAction = _.indexOf(existingItem.actions, scope.action);
              existingItem.actions.splice(indexOfExistingAction, 1);

              if(existingItem.actions.length===0){
                var indexOfExistingItem = _.indexOf(mwListableCtrl.actionColumns, existingItem);
                mwListableCtrl.actionColumns.splice(indexOfExistingItem, 1);
              }
            }
          }
        };

        scope.execute = function () {
          $timeout(scope.action);
        };

        if (mwlistableBodyRowBbCtrl) {
          id = mwlistableBodyRowBbCtrl.getId();
        }

        addAction(attr.action);
        scope.$on('$destroy', removeAction);
      }
    };
  });<|endoftext|>


<commit_before>// FIXME large resolutions lead to too large framebuffers :-(
// FIXME animated shaders! check in redraw

goog.provide('ol.renderer.webgl.TileLayer');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('goog.vec.Vec4');
goog.require('goog.webgl');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.extent');
goog.require('ol.layer.Tile');
goog.require('ol.math');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.tilelayer.shader');
goog.require('ol.tilecoord');
goog.require('ol.vec.Mat4');
goog.require('ol.webgl.Buffer');

/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.Map} mapRenderer Map renderer.
 * @param {ol.layer.Tile} tileLayer Tile layer.
 */
ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {

  goog.base(this, mapRenderer, tileLayer);

  /**
   * @private
   * @type {ol.webgl.shader.Fragment}
   */
  this.fragmentShader_ =
      ol.renderer.webgl.tilelayer.shader.Fragment.getInstance();

  /**
   * @private
   * @type {ol.webgl.shader.Vertex}
   */
  this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance();

  /**
   * @private
   * @type {ol.renderer.webgl.tilelayer.shader.Locations}
   */
  this.locations_ = null;

  /**
   * @private
   * @type {ol.webgl.Buffer}
   */
  this.renderArrayBuffer_ = new ol.webgl.Buffer([
    0, 0, 0, 1,
    1, 0, 1, 1,
    0, 1, 0, 0,
    1, 1, 1, 0
  ]);

  /**
   * @private
   * @type {ol.TileRange}
   */
  this.renderedTileRange_ = null;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.renderedFramebufferExtent_ = null;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = -1;

};
goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
  var mapRenderer = this.getWebGLMapRenderer();
  var context = mapRenderer.getContext();
  context.deleteBuffer(this.renderArrayBuffer_);
  goog.base(this, 'disposeInternal');
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
  goog.base(this, 'handleWebGLContextLost');
  this.locations_ = null;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.prepareFrame =
    function(frameState, layerState, context) {

  var mapRenderer = this.getWebGLMapRenderer();
  var gl = context.getGL();

  var viewState = frameState.viewState;
  var projection = viewState.projection;

  var tileLayer = this.getLayer();
  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
  var tileSource = tileLayer.getSource();
  var tileGrid = tileSource.getTileGridForProjection(projection);
  var z = tileGrid.getZForResolution(viewState.resolution);
  var tileResolution = tileGrid.getResolution(z);

  var tilePixelSize =
      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
  var pixelRatio = tilePixelSize / tileGrid.getTileSize(z);
  var tilePixelResolution = tileResolution / pixelRatio;
  var tileGutter = tileSource.getGutter();

  var center = viewState.center;
  var extent;
  if (tileResolution == viewState.resolution) {
    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
    extent = ol.extent.getForViewAndSize(
        center, tileResolution, viewState.rotation, frameState.size);
  } else {
    extent = frameState.extent;
  }
  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
      extent, tileResolution);

  var framebufferExtent;
  if (!goog.isNull(this.renderedTileRange_) &&
      this.renderedTileRange_.equals(tileRange) &&
      this.renderedRevision_ == tileSource.getRevision()) {
    framebufferExtent = this.renderedFramebufferExtent_;
  } else {

    var tileRangeSize = tileRange.getSize();

    var maxDimension = Math.max(
        tileRangeSize[0] * tilePixelSize, tileRangeSize[1] * tilePixelSize);
    var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension);
    var framebufferExtentDimension = tilePixelResolution * framebufferDimension;
    var origin = tileGrid.getOrigin(z);
    var minX = origin[0] + tileRange.minX * tilePixelSize * tilePixelResolution;
    var minY = origin[1] + tileRange.minY * tilePixelSize * tilePixelResolution;
    framebufferExtent = [
      minX, minY,
      minX + framebufferExtentDimension, minY + framebufferExtentDimension
    ];

    this.bindFramebuffer(frameState, framebufferDimension);
    gl.viewport(0, 0, framebufferDimension, framebufferDimension);

    gl.clearColor(0, 0, 0, 0);
    gl.clear(goog.webgl.COLOR_BUFFER_BIT);
    gl.disable(goog.webgl.BLEND);

    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
    context.useProgram(program);
    if (goog.isNull(this.locations_)) {
      this.locations_ =
          new ol.renderer.webgl.tilelayer.shader.Locations(gl, program);
    }

    context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.renderArrayBuffer_);
    gl.enableVertexAttribArray(this.locations_.a_position);
    gl.vertexAttribPointer(
        this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0);
    gl.enableVertexAttribArray(this.locations_.a_texCoord);
    gl.vertexAttribPointer(
        this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8);
    gl.uniform1i(this.locations_.u_texture, 0);

    /**
     * @type {Object.<number, Object.<string, ol.Tile>>}
     */
    var tilesToDrawByZ = {};
    tilesToDrawByZ[z] = {};

    var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
      return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED &&
          mapRenderer.isTileTextureLoaded(tile);
    }, tileSource, pixelRatio, projection);
    var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
        tilesToDrawByZ, getTileIfLoaded);

    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
    var allTilesLoaded = true;
    var tmpExtent = ol.extent.createEmpty();
    var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
    var childTileRange, fullyLoaded, tile, tileState, x, y, tileExtent;
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {

        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
        if (goog.isDef(layerState.extent)) {
          // ignore tiles outside layer extent
          tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
          if (!ol.extent.intersects(tileExtent, layerState.extent)) {
            continue;
          }
        }
        tileState = tile.getState();
        if (tileState == ol.TileState.LOADED) {
          if (mapRenderer.isTileTextureLoaded(tile)) {
            tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
            continue;
          }
        } else if (tileState == ol.TileState.EMPTY ||
                   (tileState == ol.TileState.ERROR &&
                    !useInterimTilesOnError)) {
          continue;
        }

        allTilesLoaded = false;
        fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
            tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
        if (!fullyLoaded) {
          childTileRange = tileGrid.getTileCoordChildTileRange(
              tile.tileCoord, tmpTileRange, tmpExtent);
          if (!goog.isNull(childTileRange)) {
            findLoadedTiles(z + 1, childTileRange);
          }
        }

      }

    }

    /** @type {Array.<number>} */
    var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
    goog.array.sort(zs);
    var u_tileOffset = goog.vec.Vec4.createFloat32();
    var i, ii, sx, sy, tileKey, tilesToDraw, tx, ty;
    for (i = 0, ii = zs.length; i < ii; ++i) {
      tilesToDraw = tilesToDrawByZ[zs[i]];
      for (tileKey in tilesToDraw) {
        tile = tilesToDraw[tileKey];
        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
        sx = 2 * (tileExtent[2] - tileExtent[0]) /
            framebufferExtentDimension;
        sy = 2 * (tileExtent[3] - tileExtent[1]) /
            framebufferExtentDimension;
        tx = 2 * (tileExtent[0] - framebufferExtent[0]) /
            framebufferExtentDimension - 1;
        ty = 2 * (tileExtent[1] - framebufferExtent[1]) /
            framebufferExtentDimension - 1;
        goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty);
        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
        mapRenderer.bindTileTexture(tile, tilePixelSize,
            tileGutter * pixelRatio, goog.webgl.LINEAR, goog.webgl.LINEAR);
        gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
      }
    }

    if (allTilesLoaded) {
      this.renderedTileRange_ = tileRange;
      this.renderedFramebufferExtent_ = framebufferExtent;
      this.renderedRevision_ = tileSource.getRevision();
    } else {
      this.renderedTileRange_ = null;
      this.renderedFramebufferExtent_ = null;
      this.renderedRevision_ = -1;
      frameState.animate = true;
    }

  }

  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
  var tileTextureQueue = mapRenderer.getTileTextureQueue();
  this.manageTilePyramid(
      frameState, tileSource, tileGrid, pixelRatio, projection, extent, z,
      tileLayer.getPreload(),
      /**
       * @param {ol.Tile} tile Tile.
       */
      function(tile) {
        if (tile.getState() == ol.TileState.LOADED &&
            !mapRenderer.isTileTextureLoaded(tile) &&
            !tileTextureQueue.isKeyQueued(tile.getKey())) {
          tileTextureQueue.enqueue([
            tile,
            tileGrid.getTileCoordCenter(tile.tileCoord),
            tileGrid.getResolution(tile.tileCoord[0]),
            tilePixelSize, tileGutter * pixelRatio
          ]);
        }
      }, this);
  this.scheduleExpireCache(frameState, tileSource);
  this.updateLogos(frameState, tileSource);

  var texCoordMatrix = this.texCoordMatrix;
  goog.vec.Mat4.makeIdentity(texCoordMatrix);
  goog.vec.Mat4.translate(texCoordMatrix,
      (center[0] - framebufferExtent[0]) /
          (framebufferExtent[2] - framebufferExtent[0]),
      (center[1] - framebufferExtent[1]) /
          (framebufferExtent[3] - framebufferExtent[1]),
      0);
  if (viewState.rotation !== 0) {
    goog.vec.Mat4.rotateZ(texCoordMatrix, viewState.rotation);
  }
  goog.vec.Mat4.scale(texCoordMatrix,
      frameState.size[0] * viewState.resolution /
          (framebufferExtent[2] - framebufferExtent[0]),
      frameState.size[1] * viewState.resolution /
          (framebufferExtent[3] - framebufferExtent[1]),
      1);
  goog.vec.Mat4.translate(texCoordMatrix,
      -0.5,
      -0.5,
      0);

  return true;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel =
    function(pixel, frameState, callback, thisArg) {
  if (goog.isNull(this.framebuffer)) {
    return undefined;
  }
  var mapSize = this.getMap().getSize();

  var pixelOnMapScaled = [
    pixel[0] / mapSize[0],
    (mapSize[1] - pixel[1]) / mapSize[1]];

  var pixelOnFrameBufferScaled = [0, 0];
  ol.vec.Mat4.multVec2(
      this.texCoordMatrix, pixelOnMapScaled, pixelOnFrameBufferScaled);
  var pixelOnFrameBuffer = [
    pixelOnFrameBufferScaled[0] * this.framebufferDimension,
    pixelOnFrameBufferScaled[1] * this.framebufferDimension];

  var gl = this.getWebGLMapRenderer().getContext().getGL();
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
  var imageData = new Uint8Array(4);
  gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1,
      gl.RGBA, gl.UNSIGNED_BYTE, imageData);

  if (imageData[3] > 0) {
    return callback.call(thisArg, this.getLayer());
  } else {
    return undefined;
  }
};
<commit_msg>Use frame state for size<commit_after>// FIXME large resolutions lead to too large framebuffers :-(
// FIXME animated shaders! check in redraw

goog.provide('ol.renderer.webgl.TileLayer');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('goog.vec.Vec4');
goog.require('goog.webgl');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.extent');
goog.require('ol.layer.Tile');
goog.require('ol.math');
goog.require('ol.renderer.webgl.Layer');
goog.require('ol.renderer.webgl.tilelayer.shader');
goog.require('ol.tilecoord');
goog.require('ol.vec.Mat4');
goog.require('ol.webgl.Buffer');

/**
 * @constructor
 * @extends {ol.renderer.webgl.Layer}
 * @param {ol.renderer.Map} mapRenderer Map renderer.
 * @param {ol.layer.Tile} tileLayer Tile layer.
 */
ol.renderer.webgl.TileLayer = function(mapRenderer, tileLayer) {

  goog.base(this, mapRenderer, tileLayer);

  /**
   * @private
   * @type {ol.webgl.shader.Fragment}
   */
  this.fragmentShader_ =
      ol.renderer.webgl.tilelayer.shader.Fragment.getInstance();

  /**
   * @private
   * @type {ol.webgl.shader.Vertex}
   */
  this.vertexShader_ = ol.renderer.webgl.tilelayer.shader.Vertex.getInstance();

  /**
   * @private
   * @type {ol.renderer.webgl.tilelayer.shader.Locations}
   */
  this.locations_ = null;

  /**
   * @private
   * @type {ol.webgl.Buffer}
   */
  this.renderArrayBuffer_ = new ol.webgl.Buffer([
    0, 0, 0, 1,
    1, 0, 1, 1,
    0, 1, 0, 0,
    1, 1, 1, 0
  ]);

  /**
   * @private
   * @type {ol.TileRange}
   */
  this.renderedTileRange_ = null;

  /**
   * @private
   * @type {ol.Extent}
   */
  this.renderedFramebufferExtent_ = null;

  /**
   * @private
   * @type {number}
   */
  this.renderedRevision_ = -1;

};
goog.inherits(ol.renderer.webgl.TileLayer, ol.renderer.webgl.Layer);

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
  var mapRenderer = this.getWebGLMapRenderer();
  var context = mapRenderer.getContext();
  context.deleteBuffer(this.renderArrayBuffer_);
  goog.base(this, 'disposeInternal');
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.handleWebGLContextLost = function() {
  goog.base(this, 'handleWebGLContextLost');
  this.locations_ = null;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.prepareFrame =
    function(frameState, layerState, context) {

  var mapRenderer = this.getWebGLMapRenderer();
  var gl = context.getGL();

  var viewState = frameState.viewState;
  var projection = viewState.projection;

  var tileLayer = this.getLayer();
  goog.asserts.assertInstanceof(tileLayer, ol.layer.Tile);
  var tileSource = tileLayer.getSource();
  var tileGrid = tileSource.getTileGridForProjection(projection);
  var z = tileGrid.getZForResolution(viewState.resolution);
  var tileResolution = tileGrid.getResolution(z);

  var tilePixelSize =
      tileSource.getTilePixelSize(z, frameState.pixelRatio, projection);
  var pixelRatio = tilePixelSize / tileGrid.getTileSize(z);
  var tilePixelResolution = tileResolution / pixelRatio;
  var tileGutter = tileSource.getGutter();

  var center = viewState.center;
  var extent;
  if (tileResolution == viewState.resolution) {
    center = this.snapCenterToPixel(center, tileResolution, frameState.size);
    extent = ol.extent.getForViewAndSize(
        center, tileResolution, viewState.rotation, frameState.size);
  } else {
    extent = frameState.extent;
  }
  var tileRange = tileGrid.getTileRangeForExtentAndResolution(
      extent, tileResolution);

  var framebufferExtent;
  if (!goog.isNull(this.renderedTileRange_) &&
      this.renderedTileRange_.equals(tileRange) &&
      this.renderedRevision_ == tileSource.getRevision()) {
    framebufferExtent = this.renderedFramebufferExtent_;
  } else {

    var tileRangeSize = tileRange.getSize();

    var maxDimension = Math.max(
        tileRangeSize[0] * tilePixelSize, tileRangeSize[1] * tilePixelSize);
    var framebufferDimension = ol.math.roundUpToPowerOfTwo(maxDimension);
    var framebufferExtentDimension = tilePixelResolution * framebufferDimension;
    var origin = tileGrid.getOrigin(z);
    var minX = origin[0] + tileRange.minX * tilePixelSize * tilePixelResolution;
    var minY = origin[1] + tileRange.minY * tilePixelSize * tilePixelResolution;
    framebufferExtent = [
      minX, minY,
      minX + framebufferExtentDimension, minY + framebufferExtentDimension
    ];

    this.bindFramebuffer(frameState, framebufferDimension);
    gl.viewport(0, 0, framebufferDimension, framebufferDimension);

    gl.clearColor(0, 0, 0, 0);
    gl.clear(goog.webgl.COLOR_BUFFER_BIT);
    gl.disable(goog.webgl.BLEND);

    var program = context.getProgram(this.fragmentShader_, this.vertexShader_);
    context.useProgram(program);
    if (goog.isNull(this.locations_)) {
      this.locations_ =
          new ol.renderer.webgl.tilelayer.shader.Locations(gl, program);
    }

    context.bindBuffer(goog.webgl.ARRAY_BUFFER, this.renderArrayBuffer_);
    gl.enableVertexAttribArray(this.locations_.a_position);
    gl.vertexAttribPointer(
        this.locations_.a_position, 2, goog.webgl.FLOAT, false, 16, 0);
    gl.enableVertexAttribArray(this.locations_.a_texCoord);
    gl.vertexAttribPointer(
        this.locations_.a_texCoord, 2, goog.webgl.FLOAT, false, 16, 8);
    gl.uniform1i(this.locations_.u_texture, 0);

    /**
     * @type {Object.<number, Object.<string, ol.Tile>>}
     */
    var tilesToDrawByZ = {};
    tilesToDrawByZ[z] = {};

    var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
      return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED &&
          mapRenderer.isTileTextureLoaded(tile);
    }, tileSource, pixelRatio, projection);
    var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
        tilesToDrawByZ, getTileIfLoaded);

    var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
    var allTilesLoaded = true;
    var tmpExtent = ol.extent.createEmpty();
    var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
    var childTileRange, fullyLoaded, tile, tileState, x, y, tileExtent;
    for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
      for (y = tileRange.minY; y <= tileRange.maxY; ++y) {

        tile = tileSource.getTile(z, x, y, pixelRatio, projection);
        if (goog.isDef(layerState.extent)) {
          // ignore tiles outside layer extent
          tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
          if (!ol.extent.intersects(tileExtent, layerState.extent)) {
            continue;
          }
        }
        tileState = tile.getState();
        if (tileState == ol.TileState.LOADED) {
          if (mapRenderer.isTileTextureLoaded(tile)) {
            tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
            continue;
          }
        } else if (tileState == ol.TileState.EMPTY ||
                   (tileState == ol.TileState.ERROR &&
                    !useInterimTilesOnError)) {
          continue;
        }

        allTilesLoaded = false;
        fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
            tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
        if (!fullyLoaded) {
          childTileRange = tileGrid.getTileCoordChildTileRange(
              tile.tileCoord, tmpTileRange, tmpExtent);
          if (!goog.isNull(childTileRange)) {
            findLoadedTiles(z + 1, childTileRange);
          }
        }

      }

    }

    /** @type {Array.<number>} */
    var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
    goog.array.sort(zs);
    var u_tileOffset = goog.vec.Vec4.createFloat32();
    var i, ii, sx, sy, tileKey, tilesToDraw, tx, ty;
    for (i = 0, ii = zs.length; i < ii; ++i) {
      tilesToDraw = tilesToDrawByZ[zs[i]];
      for (tileKey in tilesToDraw) {
        tile = tilesToDraw[tileKey];
        tileExtent = tileGrid.getTileCoordExtent(tile.tileCoord, tmpExtent);
        sx = 2 * (tileExtent[2] - tileExtent[0]) /
            framebufferExtentDimension;
        sy = 2 * (tileExtent[3] - tileExtent[1]) /
            framebufferExtentDimension;
        tx = 2 * (tileExtent[0] - framebufferExtent[0]) /
            framebufferExtentDimension - 1;
        ty = 2 * (tileExtent[1] - framebufferExtent[1]) /
            framebufferExtentDimension - 1;
        goog.vec.Vec4.setFromValues(u_tileOffset, sx, sy, tx, ty);
        gl.uniform4fv(this.locations_.u_tileOffset, u_tileOffset);
        mapRenderer.bindTileTexture(tile, tilePixelSize,
            tileGutter * pixelRatio, goog.webgl.LINEAR, goog.webgl.LINEAR);
        gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4);
      }
    }

    if (allTilesLoaded) {
      this.renderedTileRange_ = tileRange;
      this.renderedFramebufferExtent_ = framebufferExtent;
      this.renderedRevision_ = tileSource.getRevision();
    } else {
      this.renderedTileRange_ = null;
      this.renderedFramebufferExtent_ = null;
      this.renderedRevision_ = -1;
      frameState.animate = true;
    }

  }

  this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
  var tileTextureQueue = mapRenderer.getTileTextureQueue();
  this.manageTilePyramid(
      frameState, tileSource, tileGrid, pixelRatio, projection, extent, z,
      tileLayer.getPreload(),
      /**
       * @param {ol.Tile} tile Tile.
       */
      function(tile) {
        if (tile.getState() == ol.TileState.LOADED &&
            !mapRenderer.isTileTextureLoaded(tile) &&
            !tileTextureQueue.isKeyQueued(tile.getKey())) {
          tileTextureQueue.enqueue([
            tile,
            tileGrid.getTileCoordCenter(tile.tileCoord),
            tileGrid.getResolution(tile.tileCoord[0]),
            tilePixelSize, tileGutter * pixelRatio
          ]);
        }
      }, this);
  this.scheduleExpireCache(frameState, tileSource);
  this.updateLogos(frameState, tileSource);

  var texCoordMatrix = this.texCoordMatrix;
  goog.vec.Mat4.makeIdentity(texCoordMatrix);
  goog.vec.Mat4.translate(texCoordMatrix,
      (center[0] - framebufferExtent[0]) /
          (framebufferExtent[2] - framebufferExtent[0]),
      (center[1] - framebufferExtent[1]) /
          (framebufferExtent[3] - framebufferExtent[1]),
      0);
  if (viewState.rotation !== 0) {
    goog.vec.Mat4.rotateZ(texCoordMatrix, viewState.rotation);
  }
  goog.vec.Mat4.scale(texCoordMatrix,
      frameState.size[0] * viewState.resolution /
          (framebufferExtent[2] - framebufferExtent[0]),
      frameState.size[1] * viewState.resolution /
          (framebufferExtent[3] - framebufferExtent[1]),
      1);
  goog.vec.Mat4.translate(texCoordMatrix,
      -0.5,
      -0.5,
      0);

  return true;
};

/**
 * @inheritDoc
 */
ol.renderer.webgl.TileLayer.prototype.forEachLayerAtPixel =
    function(pixel, frameState, callback, thisArg) {
  if (goog.isNull(this.framebuffer)) {
    return undefined;
  }

  var pixelOnMapScaled = [
    pixel[0] / frameState.size[0],
    (frameState.size[1] - pixel[1]) / frameState.size[1]];

  var pixelOnFrameBufferScaled = [0, 0];
  ol.vec.Mat4.multVec2(
      this.texCoordMatrix, pixelOnMapScaled, pixelOnFrameBufferScaled);
  var pixelOnFrameBuffer = [
    pixelOnFrameBufferScaled[0] * this.framebufferDimension,
    pixelOnFrameBufferScaled[1] * this.framebufferDimension];

  var gl = this.getWebGLMapRenderer().getContext().getGL();
  gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
  var imageData = new Uint8Array(4);
  gl.readPixels(pixelOnFrameBuffer[0], pixelOnFrameBuffer[1], 1, 1,
      gl.RGBA, gl.UNSIGNED_BYTE, imageData);

  if (imageData[3] > 0) {
    return callback.call(thisArg, this.getLayer());
  } else {
    return undefined;
  }
};
<|endoftext|>