<commit_before>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
CLASS({
  package: 'foam.browser.ui',
  name: 'BrowserView',
  extendsModel: 'foam.ui.DetailView',
  requires: [
    'foam.browser.BrowserConfig',
    'foam.browser.ui.StackView',
    'foam.graphics.ActionButtonCView',
    'foam.input.touch.GestureManager',
    'foam.input.touch.TouchManager',
    'foam.ui.DAOListView',
    'foam.ui.md.DetailView',
    'foam.ui.md.SharedStyles',
    'foam.ui.md.UpdateDetailView',
  ],

  exports: [
    'gestureManager',
    'stack',
    'touchManager',
  ],

  models: [
    {
      name: 'InnerBrowserView',
      extendsModel: 'foam.ui.DetailView',
      imports: [
        'stack',
      ],
      exports: [
        'selection$',
      ],
      properties: [
        {
          name: 'data',
          postSet: function(old, nu) {
            if (old) old.unsubscribe(old.MENU_CLOSE, this.onMenuTouch);
            if (nu) nu.subscribe(nu.MENU_CLOSE, this.onMenuTouch);
          },
        },
        {
          name: 'className',
          defaultValue: 'browser'
        },
        {
          name: 'selection',
          documentation: 'Used in the list view.',
          postSet: function(old, nu) {
            if (nu) {
              this.data.dao.find(nu.id, {
                put: function(obj) {
                  this.stack.pushView(this.data.detailView({
                    data: obj,
                    innerView: this.data.innerDetailView
                  }, this.Y));
                }.bind(this)
              });
              this.selection = '';
            }
          }
        },
        {
          name: 'menuExpanded',
          defaultValue: false
        },
        {
          name: 'menuOpen',
          defaultValue: false
        },
        {
          name: 'searchMode',
          defaultValue: false
        },
        {
          name: 'listView_',
          hidden: true,
          documentation: 'Internal. The View object created by the listView.'
        },
        {
          name: 'minWidth',
          getter: function() { return this.listView_.minWidth; }
        },
        {
          name: 'preferredWidth',
          getter: function() { return this.listView_.preferredWidth; }
        },
        {
          name: 'maxWidth',
          getter: function() { return this.listView_.maxWidth; }
        },
      ],

      actions: [
        {
          name: 'menuButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAGklEQVQ4y2NgGAVEg/9EAMo0jHp61NOjAAgAUWrXKeQhPE4AAAAASUVORK5CYII=',
          isAvailable: function() { return ! this.menuExpanded; },
          action: function() {
            this.menuOpen = true;
          }
        },
        {
          name: 'searchButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAvklEQVQ4Ec3BO4rCYBgAwA/NOf6gZ1jZRlDwVoreI4W9+DiS0VJTGytlthMCyZLSmYjvZuGk8lY5msf/ZLaaCll0s8XDUjKQW6lRRBcLPPzEh4kas2jnhGU0WOMQ7VRI0WCEW7TzxiAaDPGKdirk0WCMe7RzxCoabLCPduaoTeLDryem0UWB2trI0NjGE7voJlNo2uEqj25mDm5e7vamEa64yKMvuQtKKfqSK1FK0ZdcibMUfUlKZyn6k6T4Xn//4NdJ9ptTNwAAAABJRU5ErkJggg==',
          action: function() {
            this.searchMode = true;
          }
        },
        {
          name: 'backButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAPUlEQVQ4y2NgGLbgf8P/BtKU////+78WacpDSFMeSlPlYaQo/0OacjyAcg1wJ4WTGmHDS4sWaVrqhm/mBQAoLpX9t+4i2wAAAABJRU5ErkJggg==',
          action: function() {
            this.searchMode = false;
            this.data.search = '';
          }
        },
        {
          name: 'createButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAH0lEQVQ4y2NgGAUw8B8IRjXgUoQLUEfDaDyQqmF4AwADqmeZrHJtnQAAAABJRU5ErkJggg==',
          isAvailable: function() { return this.data.showAdd; },
          action: function() {
            var newObj = this.data.model.create();
            this.stack.pushView(this.data.detailView({
              data: newObj,
              innerView: this.data.innerDetailView,
              title: 'New ' + this.data.model.name
            }, this.Y));
          }
        },
      ],

      listeners: [
        {
          name: 'onMenuTouch',
          code: function() {
            this.menuOpen = false;
          }
        },
      ],
      templates: [
        function CSS() {/*
          .hidden {
            display: none !important;
          }
          .expand {
            flex-grow: 1;
          }

          .browser {
            display: flex;
            flex-direction: column;
            height: 100%;
            position: relative;
          }
          .browser-header {
            align-items: center;
            background-color: #3e50b4;
            color: #fff;
            display: flex;
            flex-grow: 0;
            flex-shrink: 0;
            height: 56px;
            padding: 0 12px;
          }
          .browser-header .title {
            font-size: 20px;
            font-weight: 500;
            margin-left: 12px;
          }

          .browser-header.search-header .md-text-field-input {
            color: #fff;
          }
          <% var prefixes = ['::-webkit-input', ':-moz', '::-moz', ':-ms-input'];
            for (var i = 0; i < prefixes.length; i++) { %>
              .browser-header.search-header .md-text-field-input><%= prefixes[i] %>-placeholder {
                color: #ccc;
              }
          <% } %>

          .browser-menu-container {
            position: fixed;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            z-index: 10;
          }
          .browser-menu-inner {
            position: relative;
            height: 100%;
            width: 100%;
          }

          .browser-menu-overlay {
            background-color: #000;
            opacity: 0.4;
            position: absolute;
            height: 100%;
            top: 0;
            left: 0;
            width: 100%;
          }

          .browser-menu {
            background-color: #fff;
            position: absolute;
            height: 100%;
            top: 0;
            left: 0;
            overflow-y: auto;
            width: 300px;
          }

          .browser-body {
            flex-grow: 1;
            overflow-x: hidden;
            overflow-y: auto;
          }
        */},
        function toHTML() {/*
          <div id="<%= this.id %>" <%= this.cssClassAttr() %>>
            <div id="<%= this.id %>-menu-container" class="browser-menu-container">

                <div id="<%= this.id %>-menu-overlay" class="browser-menu-overlay">

                  <%= this.data.menuFactory() %>

            <%
              this.setClass('hidden', function() { return ! self.menuOpen; },
                  this.id + '-menu-container');
                  this.on('click', this.onMenuTouch, this.id + '-menu-overlay');
            %>

            <div id="<%= this.id %>-header" class="browser-header">
              $$menuButton
              $$title{ mode: 'read-only', extraClassName: 'expand title' }
              $$searchButton

            <div id="<%= this.id %>-header-search" class="browser-header search-header">
              $$backButton
              $$search{ extraClassName: 'expand search-field' }

            <%
              this.setClass('hidden', function() { return self.searchMode; },
                  this.id + '-header');
              this.setClass('hidden', function() { return ! self.searchMode; },
                  this.id + '-header-search');
            %>

              <%= this.listView_ = this.data.listView({ data$: this.data.filteredDAO$ }, this.Y) %>

            <% if (this.data.showAdd) { %>

                $$createButton{
                  className: 'createButton',
                  color: 'white',
                  font: '30px Roboto Arial',
                  alpha: 1,
                  width: 44,
                  height: 44,
                  radius: 22,
                  background: '#e51c23'
                }

            <% } %>

        */},
      ],
    },
  ],

  properties: [
    {
      name: 'touchManager',
      factory: function() {
        return this.X.touchManager || this.TouchManager.create();
      }
    },
    {
      name: 'gestureManager',
      factory: function() {
        return this.X.gestureManager || this.GestureManager.create();
      }
    },
    {
      name: 'stack',
      factory: function() {
        return this.StackView.create();
      }
    },
  ],

  methods: [
    function init(args) {
      this.SUPER(args);
      this.Y.registerModel(this.ActionButtonCView.xbind({
        height: 24,
        width: 24
      }), 'foam.ui.ActionButton');

      this.SharedStyles.create();
    },
    function toHTML() {
      return this.stack.toHTML();
    },
    function initHTML() {
      this.SUPER();
      this.stack.initHTML();
      this.stack.pushView_(-1, this.InnerBrowserView.create({
        data: this.data
      }, this.Y));
    }
  ],
});
<commit_msg>Browser: Animate the menu in and out.<commit_after>/**
 * @license
 * Copyright 2015 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
CLASS({
  package: 'foam.browser.ui',
  name: 'BrowserView',
  extendsModel: 'foam.ui.DetailView',
  requires: [
    'foam.browser.BrowserConfig',
    'foam.browser.ui.StackView',
    'foam.graphics.ActionButtonCView',
    'foam.input.touch.GestureManager',
    'foam.input.touch.TouchManager',
    'foam.ui.DAOListView',
    'foam.ui.md.DetailView',
    'foam.ui.md.SharedStyles',
    'foam.ui.md.UpdateDetailView',
  ],

  exports: [
    'gestureManager',
    'stack',
    'touchManager',
  ],

  models: [
    {
      name: 'InnerBrowserView',
      extendsModel: 'foam.ui.DetailView',
      imports: [
        'stack',
      ],
      exports: [
        'selection$',
      ],
      properties: [
        {
          name: 'data',
          postSet: function(old, nu) {
            if (old) old.unsubscribe(old.MENU_CLOSE, this.onMenuTouch);
            if (nu) nu.subscribe(nu.MENU_CLOSE, this.onMenuTouch);
          },
        },
        {
          name: 'className',
          defaultValue: 'browser'
        },
        {
          name: 'selection',
          documentation: 'Used in the list view.',
          postSet: function(old, nu) {
            if (nu) {
              this.data.dao.find(nu.id, {
                put: function(obj) {
                  this.stack.pushView(this.data.detailView({
                    data: obj,
                    innerView: this.data.innerDetailView
                  }, this.Y));
                }.bind(this)
              });
              this.selection = '';
            }
          }
        },
        {
          name: 'menuExpanded',
          defaultValue: false
        },
        {
          name: 'menuOpen',
          defaultValue: false
        },
        {
          name: 'searchMode',
          defaultValue: false
        },
        {
          name: 'listView_',
          hidden: true,
          documentation: 'Internal. The View object created by the listView.'
        },
        {
          name: 'minWidth',
          getter: function() { return this.listView_.minWidth; }
        },
        {
          name: 'preferredWidth',
          getter: function() { return this.listView_.preferredWidth; }
        },
        {
          name: 'maxWidth',
          getter: function() { return this.listView_.maxWidth; }
        },
      ],

      actions: [
        {
          name: 'menuButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAGklEQVQ4y2NgGAVEg/9EAMo0jHp61NOjAAgAUWrXKeQhPE4AAAAASUVORK5CYII=',
          isAvailable: function() { return ! this.menuExpanded; },
          action: function() {
            this.menuOpen = true;
          }
        },
        {
          name: 'searchButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAvklEQVQ4Ec3BO4rCYBgAwA/NOf6gZ1jZRlDwVoreI4W9+DiS0VJTGytlthMCyZLSmYjvZuGk8lY5msf/ZLaaCll0s8XDUjKQW6lRRBcLPPzEh4kas2jnhGU0WOMQ7VRI0WCEW7TzxiAaDPGKdirk0WCMe7RzxCoabLCPduaoTeLDryem0UWB2trI0NjGE7voJlNo2uEqj25mDm5e7vamEa64yKMvuQtKKfqSK1FK0ZdcibMUfUlKZyn6k6T4Xn//4NdJ9ptTNwAAAABJRU5ErkJggg==',
          action: function() {
            this.searchMode = true;
          }
        },
        {
          name: 'backButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAPUlEQVQ4y2NgGLbgf8P/BtKU////+78WacpDSFMeSlPlYaQo/0OacjyAcg1wJ4WTGmHDS4sWaVrqhm/mBQAoLpX9t+4i2wAAAABJRU5ErkJggg==',
          action: function() {
            this.searchMode = false;
            this.data.search = '';
          }
        },
        {
          name: 'createButton',
          iconUrl: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAQAAABKfvVzAAAAH0lEQVQ4y2NgGAUw8B8IRjXgUoQLUEfDaDyQqmF4AwADqmeZrHJtnQAAAABJRU5ErkJggg==',
          isAvailable: function() { return this.data.showAdd; },
          action: function() {
            var newObj = this.data.model.create();
            this.stack.pushView(this.data.detailView({
              data: newObj,
              innerView: this.data.innerDetailView,
              title: 'New ' + this.data.model.name
            }, this.Y));
          }
        },
      ],

      listeners: [
        {
          name: 'onMenuTouch',
          code: function() {
            this.menuOpen = false;
          }
        },
      ],
      templates: [
        function CSS() {/*
          .hidden {
            display: none !important;
          }
          .expand {
            flex-grow: 1;
          }

          .browser {
            display: flex;
            flex-direction: column;
            height: 100%;
            position: relative;
          }
          .browser-header {
            align-items: center;
            background-color: #3e50b4;
            color: #fff;
            display: flex;
            flex-grow: 0;
            flex-shrink: 0;
            height: 56px;
            padding: 0 12px;
          }
          .browser-header .title {
            font-size: 20px;
            font-weight: 500;
            margin-left: 12px;
          }

          .browser-header.search-header .md-text-field-input {
            color: #fff;
          }
          <% var prefixes = ['::-webkit-input', ':-moz', '::-moz', ':-ms-input'];
            for (var i = 0; i < prefixes.length; i++) { %>
              .browser-header.search-header .md-text-field-input><%= prefixes[i] %>-placeholder {
                color: #ccc;
              }
          <% } %>

          <% var ANIMATION_TIME = '0.4s'; %>

          .browser-menu-container {
            visibility: hidden;
            position: fixed;
            top: 0;
            bottom: 0;
            left: 0;
            right: 0;
            z-index: 10;
            transition: visibility 0.01s linear <%= ANIMATION_TIME %>;
          }
          .browser-menu-container.menu-open {
            visibility: visible;
            transition: visibility <%= ANIMATION_TIME %> linear;
          }
          .browser-menu-inner {
            position: relative;
            height: 100%;
            width: 100%;
          }

          .browser-menu-overlay {
            background-color: #000;
            opacity: 0;
            position: absolute;
            height: 100%;
            top: 0;
            left: 0;
            width: 100%;
            transition: opacity <%= ANIMATION_TIME %> ease;
          }
          .menu-open .browser-menu-overlay {
            opacity: 0.4;
            transition: opacity <%= ANIMATION_TIME %> ease;
          }

          .browser-menu {
            background-color: #fff;
            position: absolute;
            height: 100%;
            top: 0;
            left: 0;
            transform: translate3d(-300px, 0, 0);
            overflow-y: auto;
            width: 300px;
            transition: transform <%= ANIMATION_TIME %> ease;
          }
          .menu-open .browser-menu {
            transform: translate3d(0, 0, 0);
            transition: transform <%= ANIMATION_TIME %> ease;
          }

          .browser-body {
            flex-grow: 1;
            overflow-x: hidden;
            overflow-y: auto;
          }
        */},
        function toHTML() {/*
          <div id="<%= this.id %>" <%= this.cssClassAttr() %>>
            <div id="<%= this.id %>-menu-container" class="browser-menu-container">

                <div id="<%= this.id %>-menu-overlay" class="browser-menu-overlay">

                  <%= this.data.menuFactory() %>

            <%
              this.setClass('menu-open', function() { return self.menuOpen; },
                  this.id + '-menu-container');
                  this.on('click', this.onMenuTouch, this.id + '-menu-overlay');
            %>

            <div id="<%= this.id %>-header" class="browser-header">
              $$menuButton
              $$title{ mode: 'read-only', extraClassName: 'expand title' }
              $$searchButton

            <div id="<%= this.id %>-header-search" class="browser-header search-header">
              $$backButton
              $$search{ extraClassName: 'expand search-field' }

            <%
              this.setClass('hidden', function() { return self.searchMode; },
                  this.id + '-header');
              this.setClass('hidden', function() { return ! self.searchMode; },
                  this.id + '-header-search');
            %>

              <%= this.listView_ = this.data.listView({ data$: this.data.filteredDAO$ }, this.Y) %>

            <% if (this.data.showAdd) { %>

                $$createButton{
                  className: 'createButton',
                  color: 'white',
                  font: '30px Roboto Arial',
                  alpha: 1,
                  width: 44,
                  height: 44,
                  radius: 22,
                  background: '#e51c23'
                }

            <% } %>

        */},
      ],
    },
  ],

  properties: [
    {
      name: 'touchManager',
      factory: function() {
        return this.X.touchManager || this.TouchManager.create();
      }
    },
    {
      name: 'gestureManager',
      factory: function() {
        return this.X.gestureManager || this.GestureManager.create();
      }
    },
    {
      name: 'stack',
      factory: function() {
        return this.StackView.create();
      }
    },
  ],

  methods: [
    function init(args) {
      this.SUPER(args);
      this.Y.registerModel(this.ActionButtonCView.xbind({
        height: 24,
        width: 24
      }), 'foam.ui.ActionButton');

      this.SharedStyles.create();
    },
    function toHTML() {
      return this.stack.toHTML();
    },
    function initHTML() {
      this.SUPER();
      this.stack.initHTML();
      this.stack.pushView_(-1, this.InnerBrowserView.create({
        data: this.data
      }, this.Y));
    }
  ],
});
<|endoftext|>


<commit_before>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t, _td } from '../../../languageHandler';
import CallHandler from '../../../CallHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import Stickerpicker from './Stickerpicker';

const formatButtonList = [
    _td("bold"),
    _td("italic"),
    _td("deleted"),
    _td("underlined"),
    _td("inline-code"),
    _td("block-quote"),
    _td("bulleted-list"),
    _td("numbered-list"),
];

export default class MessageComposer extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.onCallClick = this.onCallClick.bind(this);
        this.onHangupClick = this.onHangupClick.bind(this);
        this.onUploadClick = this.onUploadClick.bind(this);
        this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
        this.uploadFiles = this.uploadFiles.bind(this);
        this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
        this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
        this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
        this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
        this.onInputStateChanged = this.onInputStateChanged.bind(this);
        this.onEvent = this.onEvent.bind(this);
        this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);

        this.state = {
            inputState: {
                marks: [],
                blockType: null,
                isRichTextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
            },
            showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
            isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
        };
    }

    componentDidMount() {
        // N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler
        // for 'event' fires *after* 'RoomEvent', and our room won't have yet been
        // marked as encrypted.
        // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
        MatrixClientPeg.get().on("event", this.onEvent);
        this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
    }

    componentWillUnmount() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("event", this.onEvent);
        }
        if (this._roomStoreToken) {
            this._roomStoreToken.remove();
        }
    }

    onEvent(event) {
        if (event.getType() !== 'm.room.encryption') return;
        if (event.getRoomId() !== this.props.room.roomId) return;
        this.forceUpdate();
    }

    _onRoomViewStoreUpdate() {
        const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
        if (this.state.isQuoting === isQuoting) return;
        this.setState({ isQuoting });
    }

    onUploadClick(ev) {
        if (MatrixClientPeg.get().isGuest()) {
            dis.dispatch({action: 'view_set_mxid'});
            return;
        }

        this.refs.uploadInput.click();
    }

    onUploadFileSelected(files) {
        this.uploadFiles(files.target.files);
    }

    uploadFiles(files) {
        const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
        const TintableSvg = sdk.getComponent("elements.TintableSvg");

        const fileList = [];
        for (let i=0; i<files.length; i++) {
            fileList.push(

                <TintableSvg key={i} src="img/files.svg" width="16" height="16" /> { files[i].name || _t('Attachment') }

);
        }

        const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
        let replyToWarning = null;
        if (isQuoting) {
            replyToWarning =
{
                _t('At this time it is not possible to reply with a file so this will be sent without being a reply.')
            }
;
        }

        Modal.createTrackedDialog('Upload Files confirmation', '', QuestionDialog, {
            title: _t('Upload Files'),
            description: (

{ _t('Are you sure you want to upload the following files?') }

                        { fileList }

                    { replyToWarning }

            ),
            onFinished: (shouldUpload) => {
                if (shouldUpload) {
                    // MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
                    if (files) {
                        for (let i=0; i<files.length; i++) {
                            this.props.uploadFile(files[i]);
                        }
                    }
                }

                this.refs.uploadInput.value = null;
            },
        });
    }

    onHangupClick() {
        const call = CallHandler.getCallForRoom(this.props.room.roomId);
        //var call = CallHandler.getAnyActiveCall();
        if (!call) {
            return;
        }
        dis.dispatch({
            action: 'hangup',
            // hangup the call for this room, which may not be the room in props
            // (e.g. conferences which will hangup the 1:1 room instead)
            room_id: call.roomId,
        });
    }

    onCallClick(ev) {
        dis.dispatch({
            action: 'place_call',
            type: ev.shiftKey ? "screensharing" : "video",
            room_id: this.props.room.roomId,
        });
    }

    onVoiceCallClick(ev) {
        dis.dispatch({
            action: 'place_call',
            type: "voice",
            room_id: this.props.room.roomId,
        });
    }

    onInputStateChanged(inputState) {
        this.setState({inputState});
    }

    _onAutocompleteConfirm(range, completion) {
        if (this.messageComposerInput) {
            this.messageComposerInput.setDisplayedCompletion(range, completion);
        }
    }

    onFormatButtonClicked(name, event) {
        event.preventDefault();
        this.messageComposerInput.onFormatButtonClicked(name, event);
    }

    onToggleFormattingClicked() {
        SettingsStore.setValue("MessageComposer.showFormatting", null, SettingLevel.DEVICE, !this.state.showFormatting);
        this.setState({showFormatting: !this.state.showFormatting});
    }

    onToggleMarkdownClicked(e) {
        e.preventDefault(); // don't steal focus from the editor!
        this.messageComposerInput.enableRichtext(!this.state.inputState.isRichTextEnabled);
    }

    render() {
        const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
        const uploadInputStyle = {display: 'none'};
        const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        const TintableSvg = sdk.getComponent("elements.TintableSvg");
        const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");

        const controls = [];

        controls.push(

                <MemberAvatar member={me} width={24} height={24} />

,
        );

        let e2eImg, e2eTitle, e2eClass;
        const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
        if (roomIsEncrypted) {
            // FIXME: show a /!\ if there are untrusted devices in the room...
            e2eImg = 'img/e2e-verified.svg';
            e2eTitle = _t('Encrypted room');
            e2eClass = 'mx_MessageComposer_e2eIcon';
        } else {
            e2eImg = 'img/e2e-unencrypted.svg';
            e2eTitle = _t('Unencrypted room');
            e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
        }

        controls.push(
            ,
        );

        let callButton;
        let videoCallButton;
        let hangupButton;

        // Call buttons
        if (this.props.callState && this.props.callState !== 'ended') {
            hangupButton =

;
        } else {
            callButton =

                    <TintableSvg src="img/icon-call.svg" width="35" height="35" />

;
            videoCallButton =

                    <TintableSvg src="img/icons-video.svg" width="35" height="35" />

;
        }

        const canSendMessages = this.props.room.currentState.maySendMessage(
            MatrixClientPeg.get().credentials.userId);

        if (canSendMessages) {
            // This also currently includes the call buttons. Really we should
            // check separately for whether we can call, but this is slightly
            // complex because of conference calls.
            const uploadButton = (

                    <TintableSvg src="img/icons-upload.svg" width="35" height="35" />
                    <input ref="uploadInput" type="file"
                        style={uploadInputStyle}
                        multiple
                        onChange={this.onUploadFileSelected} />

            );

            const formattingButton = this.state.inputState.isRichTextEnabled ? (

            ) : null;

            let placeholderText;
            if (this.state.isQuoting) {
                if (roomIsEncrypted) {
                    placeholderText = _t('Send an encrypted reply…');
                } else {
                    placeholderText = _t('Send a reply (unencrypted)…');
                }
            } else {
                if (roomIsEncrypted) {
                    placeholderText = _t('Send an encrypted message…');
                } else {
                    placeholderText = _t('Send a message (unencrypted)…');
                }
            }

            const stickerpickerButton = <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />;

            controls.push(
                <MessageComposerInput
                    ref={(c) => this.messageComposerInput = c}
                    key="controls_input"
                    onResize={this.props.onResize}
                    room={this.props.room}
                    placeholder={placeholderText}
                    onFilesPasted={this.uploadFiles}
                    onInputStateChanged={this.onInputStateChanged} />,
                formattingButton,
                stickerpickerButton,
                uploadButton,
                hangupButton,
                callButton,
                videoCallButton,
            );
        } else {
            controls.push(

                    { _t('You do not have permission to post to this room') }

,
            );
        }

        let formatBar;
        if (this.state.showFormatting && this.state.inputState.isRichTextEnabled) {
            const {marks, blockType} = this.state.inputState;
            const formatButtons = formatButtonList.map((name) => {
                // special-case to match the md serializer and the special-case in MessageComposerInput.js
                const markName = name === 'inline-code' ? 'code' : name;
                const active = marks.some(mark => mark.type === markName) || blockType === name;
                const suffix = active ? '-on' : '';
                const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
                const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
                return <img className={className}
                            title={_t(name)}
                            onMouseDown={onFormatButtonClicked}
                            key={name}
                            src={`img/button-text-${name}${suffix}.svg`}
                            height="17" />;
                },
            );

            formatBar =

                        { formatButtons }

                        <img title={this.state.inputState.isRichTextEnabled ? _t("Turn Markdown on") : _t("Turn Markdown off")}
                             onMouseDown={this.onToggleMarkdownClicked}
                            className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
                            src={`img/button-md-${!this.state.inputState.isRichTextEnabled}.png`} />

        }

        return (

                        { controls }

                { formatBar }

        );
    }
}

MessageComposer.propTypes = {
    // a callback which is called when the height of the composer is
    // changed due to a change in content.
    onResize: PropTypes.func,

    // js-sdk Room object
    room: PropTypes.object.isRequired,

    // string representing the current voip call state
    callState: PropTypes.string,

    // callback when a file to upload is chosen
    uploadFile: PropTypes.func.isRequired,

    // string representing the current room app drawer state
    showApps: PropTypes.bool,
};
<commit_msg>wait for members loading to finish to show the avatar in the composer<commit_after>/*
Copyright 2015, 2016 OpenMarket Ltd
Copyright 2017 New Vector Ltd

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import PropTypes from 'prop-types';
import { _t, _td } from '../../../languageHandler';
import CallHandler from '../../../CallHandler';
import MatrixClientPeg from '../../../MatrixClientPeg';
import Modal from '../../../Modal';
import sdk from '../../../index';
import dis from '../../../dispatcher';
import RoomViewStore from '../../../stores/RoomViewStore';
import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
import Stickerpicker from './Stickerpicker';

const formatButtonList = [
    _td("bold"),
    _td("italic"),
    _td("deleted"),
    _td("underlined"),
    _td("inline-code"),
    _td("block-quote"),
    _td("bulleted-list"),
    _td("numbered-list"),
];

export default class MessageComposer extends React.Component {
    constructor(props, context) {
        super(props, context);
        this.onCallClick = this.onCallClick.bind(this);
        this.onHangupClick = this.onHangupClick.bind(this);
        this.onUploadClick = this.onUploadClick.bind(this);
        this.onUploadFileSelected = this.onUploadFileSelected.bind(this);
        this.uploadFiles = this.uploadFiles.bind(this);
        this.onVoiceCallClick = this.onVoiceCallClick.bind(this);
        this._onAutocompleteConfirm = this._onAutocompleteConfirm.bind(this);
        this.onToggleFormattingClicked = this.onToggleFormattingClicked.bind(this);
        this.onToggleMarkdownClicked = this.onToggleMarkdownClicked.bind(this);
        this.onInputStateChanged = this.onInputStateChanged.bind(this);
        this.onEvent = this.onEvent.bind(this);
        this._onRoomViewStoreUpdate = this._onRoomViewStoreUpdate.bind(this);

        this.state = {
            inputState: {
                marks: [],
                blockType: null,
                isRichTextEnabled: SettingsStore.getValue('MessageComposerInput.isRichTextEnabled'),
            },
            showFormatting: SettingsStore.getValue('MessageComposer.showFormatting'),
            isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
        };
    }

    componentDidMount() {
        // N.B. using 'event' rather than 'RoomEvents' otherwise the crypto handler
        // for 'event' fires *after* 'RoomEvent', and our room won't have yet been
        // marked as encrypted.
        // XXX: fragile as all hell - fixme somehow, perhaps with a dedicated Room.encryption event or something.
        MatrixClientPeg.get().on("event", this.onEvent);
        this._roomStoreToken = RoomViewStore.addListener(this._onRoomViewStoreUpdate);
        this._waitForOwnMember();
    }

    _waitForOwnMember() {
        // if we have the member already, do that
        const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
        if (me) {
            this.setState({me});
            return;
        }
        // Otherwise, wait for member loading to finish and then update the member for the avatar.
        // The members should already be loading, and loadMembersIfNeeded
        // will return the promise for the existing operation
        this.props.room.loadMembersIfNeeded().then(() => {
            const me = this.props.room.getMember(MatrixClientPeg.get().getUserId());
            this.setState({me});
        });
    }

    componentWillUnmount() {
        if (MatrixClientPeg.get()) {
            MatrixClientPeg.get().removeListener("event", this.onEvent);
        }
        if (this._roomStoreToken) {
            this._roomStoreToken.remove();
        }
    }

    onEvent(event) {
        if (event.getType() !== 'm.room.encryption') return;
        if (event.getRoomId() !== this.props.room.roomId) return;
        this.forceUpdate();
    }

    _onRoomViewStoreUpdate() {
        const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
        if (this.state.isQuoting === isQuoting) return;
        this.setState({ isQuoting });
    }

    onUploadClick(ev) {
        if (MatrixClientPeg.get().isGuest()) {
            dis.dispatch({action: 'view_set_mxid'});
            return;
        }

        this.refs.uploadInput.click();
    }

    onUploadFileSelected(files) {
        this.uploadFiles(files.target.files);
    }

    uploadFiles(files) {
        const QuestionDialog = sdk.getComponent("dialogs.QuestionDialog");
        const TintableSvg = sdk.getComponent("elements.TintableSvg");

        const fileList = [];
        for (let i=0; i<files.length; i++) {
            fileList.push(

                <TintableSvg key={i} src="img/files.svg" width="16" height="16" /> { files[i].name || _t('Attachment') }

);
        }

        const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
        let replyToWarning = null;
        if (isQuoting) {
            replyToWarning =
{
                _t('At this time it is not possible to reply with a file so this will be sent without being a reply.')
            }
;
        }

        Modal.createTrackedDialog('Upload Files confirmation', '', QuestionDialog, {
            title: _t('Upload Files'),
            description: (

{ _t('Are you sure you want to upload the following files?') }

                        { fileList }

                    { replyToWarning }

            ),
            onFinished: (shouldUpload) => {
                if (shouldUpload) {
                    // MessageComposer shouldn't have to rely on its parent passing in a callback to upload a file
                    if (files) {
                        for (let i=0; i<files.length; i++) {
                            this.props.uploadFile(files[i]);
                        }
                    }
                }

                this.refs.uploadInput.value = null;
            },
        });
    }

    onHangupClick() {
        const call = CallHandler.getCallForRoom(this.props.room.roomId);
        //var call = CallHandler.getAnyActiveCall();
        if (!call) {
            return;
        }
        dis.dispatch({
            action: 'hangup',
            // hangup the call for this room, which may not be the room in props
            // (e.g. conferences which will hangup the 1:1 room instead)
            room_id: call.roomId,
        });
    }

    onCallClick(ev) {
        dis.dispatch({
            action: 'place_call',
            type: ev.shiftKey ? "screensharing" : "video",
            room_id: this.props.room.roomId,
        });
    }

    onVoiceCallClick(ev) {
        dis.dispatch({
            action: 'place_call',
            type: "voice",
            room_id: this.props.room.roomId,
        });
    }

    onInputStateChanged(inputState) {
        this.setState({inputState});
    }

    _onAutocompleteConfirm(range, completion) {
        if (this.messageComposerInput) {
            this.messageComposerInput.setDisplayedCompletion(range, completion);
        }
    }

    onFormatButtonClicked(name, event) {
        event.preventDefault();
        this.messageComposerInput.onFormatButtonClicked(name, event);
    }

    onToggleFormattingClicked() {
        SettingsStore.setValue("MessageComposer.showFormatting", null, SettingLevel.DEVICE, !this.state.showFormatting);
        this.setState({showFormatting: !this.state.showFormatting});
    }

    onToggleMarkdownClicked(e) {
        e.preventDefault(); // don't steal focus from the editor!
        this.messageComposerInput.enableRichtext(!this.state.inputState.isRichTextEnabled);
    }

    render() {
        const uploadInputStyle = {display: 'none'};
        const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
        const TintableSvg = sdk.getComponent("elements.TintableSvg");
        const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");

        const controls = [];

        if (this.state.me) {
            controls.push(

                    <MemberAvatar member={this.state.me} width={24} height={24} />

,
            );
        }

        let e2eImg, e2eTitle, e2eClass;
        const roomIsEncrypted = MatrixClientPeg.get().isRoomEncrypted(this.props.room.roomId);
        if (roomIsEncrypted) {
            // FIXME: show a /!\ if there are untrusted devices in the room...
            e2eImg = 'img/e2e-verified.svg';
            e2eTitle = _t('Encrypted room');
            e2eClass = 'mx_MessageComposer_e2eIcon';
        } else {
            e2eImg = 'img/e2e-unencrypted.svg';
            e2eTitle = _t('Unencrypted room');
            e2eClass = 'mx_MessageComposer_e2eIcon mx_filterFlipColor';
        }

        controls.push(
            ,
        );

        let callButton;
        let videoCallButton;
        let hangupButton;

        // Call buttons
        if (this.props.callState && this.props.callState !== 'ended') {
            hangupButton =

;
        } else {
            callButton =

                    <TintableSvg src="img/icon-call.svg" width="35" height="35" />

;
            videoCallButton =

                    <TintableSvg src="img/icons-video.svg" width="35" height="35" />

;
        }

        const canSendMessages = this.props.room.currentState.maySendMessage(
            MatrixClientPeg.get().credentials.userId);

        if (canSendMessages) {
            // This also currently includes the call buttons. Really we should
            // check separately for whether we can call, but this is slightly
            // complex because of conference calls.
            const uploadButton = (

                    <TintableSvg src="img/icons-upload.svg" width="35" height="35" />
                    <input ref="uploadInput" type="file"
                        style={uploadInputStyle}
                        multiple
                        onChange={this.onUploadFileSelected} />

            );

            const formattingButton = this.state.inputState.isRichTextEnabled ? (

            ) : null;

            let placeholderText;
            if (this.state.isQuoting) {
                if (roomIsEncrypted) {
                    placeholderText = _t('Send an encrypted reply…');
                } else {
                    placeholderText = _t('Send a reply (unencrypted)…');
                }
            } else {
                if (roomIsEncrypted) {
                    placeholderText = _t('Send an encrypted message…');
                } else {
                    placeholderText = _t('Send a message (unencrypted)…');
                }
            }

            const stickerpickerButton = <Stickerpicker key='stickerpicker_controls_button' room={this.props.room} />;

            controls.push(
                <MessageComposerInput
                    ref={(c) => this.messageComposerInput = c}
                    key="controls_input"
                    onResize={this.props.onResize}
                    room={this.props.room}
                    placeholder={placeholderText}
                    onFilesPasted={this.uploadFiles}
                    onInputStateChanged={this.onInputStateChanged} />,
                formattingButton,
                stickerpickerButton,
                uploadButton,
                hangupButton,
                callButton,
                videoCallButton,
            );
        } else {
            controls.push(

                    { _t('You do not have permission to post to this room') }

,
            );
        }

        let formatBar;
        if (this.state.showFormatting && this.state.inputState.isRichTextEnabled) {
            const {marks, blockType} = this.state.inputState;
            const formatButtons = formatButtonList.map((name) => {
                // special-case to match the md serializer and the special-case in MessageComposerInput.js
                const markName = name === 'inline-code' ? 'code' : name;
                const active = marks.some(mark => mark.type === markName) || blockType === name;
                const suffix = active ? '-on' : '';
                const onFormatButtonClicked = this.onFormatButtonClicked.bind(this, name);
                const className = 'mx_MessageComposer_format_button mx_filterFlipColor';
                return <img className={className}
                            title={_t(name)}
                            onMouseDown={onFormatButtonClicked}
                            key={name}
                            src={`img/button-text-${name}${suffix}.svg`}
                            height="17" />;
                },
            );

            formatBar =

                        { formatButtons }

                        <img title={this.state.inputState.isRichTextEnabled ? _t("Turn Markdown on") : _t("Turn Markdown off")}
                             onMouseDown={this.onToggleMarkdownClicked}
                            className="mx_MessageComposer_formatbar_markdown mx_filterFlipColor"
                            src={`img/button-md-${!this.state.inputState.isRichTextEnabled}.png`} />

        }

        return (

                        { controls }

                { formatBar }

        );
    }
}

MessageComposer.propTypes = {
    // a callback which is called when the height of the composer is
    // changed due to a change in content.
    onResize: PropTypes.func,

    // js-sdk Room object
    room: PropTypes.object.isRequired,

    // string representing the current voip call state
    callState: PropTypes.string,

    // callback when a file to upload is chosen
    uploadFile: PropTypes.func.isRequired,

    // string representing the current room app drawer state
    showApps: PropTypes.bool,
};
<|endoftext|>


<commit_before>import React from 'react'
import ShowCard from './ShowCard'
import preload from '../public/data.json'

const Search = React.createClass({

  render () {
    return (

        {preload.shows.map((show) => {
          return (
            <ShowCard key={show.imdbID} {...show} />
          )
        })}

    )
  }
})
export default Search
<commit_msg>testing initialState in Search<commit_after>import React from 'react'
import ShowCard from './ShowCard'
import preload from '../public/data.json'

const Search = React.createClass({
  getInitialState() {
    return {
      searchTerm: 'this is default string'
    }
  },
  render () {
    return (

svideo

          <input type='text' placeholder='Search' value={this.state.searchTerm}/>

          {preload.shows.map((show) => {
            return (
              <ShowCard key={show.imdbID} {...show} />
            )
          })}

    )
  }
})
export default Search
<|endoftext|>


<commit_before>// TODO: Fix auto-variable extraction to ignore a longer
// word, such as sin.

/**
 * GUIWorK fitb property contains all JavaScript code associated with
 * the fill-in-the-blank question type.
 * Attempts to automatically extract strings from lists and one-letter
 * variables from formulas and add these to appropriate Context.
 * The code inherits from QuestionType.
 */
GUIWorK.fitb = Object.create(GUIWorK.QuestionType.prototype);

// First call to PGgen for a new problem generation.
GUIWorK.fitb.firstPGgenCall = true;

// Tell PGgen to clear the contextStrings array in preparation for
// generating fresh code for the problem.

// NOTE: Default WeBWorK strings are
//   DNE,INF,INFINITY,NONE,inf,infinity
// We're also telling WeBWorK to clear these, so they are only
// available if explicitly given as an answer or specified by the
// user as allowable strings.
GUIWorK.fitb.init =
function init() {
  GUIWorK.fitb.firstPGgenCall = true;
};

// Add all answer Strings, including those embedded within Lists,
// along with any additional explicitly specified strings, to Context.
// Also add all one-character variable names within formulas to Context.
GUIWorK.fitb.PGgen =
function PGgen(questionElt) {
    var outString = '';
    var nQuestion = getQuestionNum(questionElt);
    var stringList = new GUIWorK.Set(); // Context strings for this question
    var varList = new GUIWorK.Set();    // Variable names for this question

    // If first call for a new problem generation, create empty string
    // and variable arrays and tell WeBWorK to clear its strings and
    // variables from Context.
    if (GUIWorK.fitb.firstPGgenCall) {
      GUIWorK.fitb.contextStrings = new GUIWorK.Set();
      GUIWorK.fitb.contextVariables = new GUIWorK.Set();
      outString += 'Context()->strings->are();\n';
      outString += 'Context()->variables->are();\n';
      GUIWorK.fitb.firstPGgenCall = false;
    }

    // Compile list of new string answers, saving them so that
    // later questions will not attempt to also add them to context.
    // Similarly, extract variable names from formulas.
    // Throw error if any answers are blank.
    var answerElts = questionElt.getElementsByClassName("fitb_answer");
    var answerTypeElts = questionElt.getElementsByClassName("fitb_ansType");
    for (var i=0; i<answerElts.length; i++) {
      var answer = answerElts[i].value.trim();
      var selectAnswerType = answerTypeElts[i];
      if (/^\s*$/.test(answer)) {
        throw "Question " + nQuestion + " has a blank answer.";
      }
      if (selectAnswerType.value == "String" ||
          (selectAnswerType.selectedIndex == 0 && GUIWorK.fitb.isLetters(answer))) {
	 stringList.include(answer);
      }
      // Find all of the (one-letter) variables in this formula.
      // In, e.g., sin(x)y, this will find x and y but not (any part of) sin.
      else if (selectAnswerType.value == "Formula") {
         var letterRE = /(^|[^A-Za-z])([A-Za-z])(?![A-Za-z])/g;
         var varArray;
	 while ((varArray=letterRE.exec(answer)) != null) {
	   var variable = varArray[2];
           varList.include(variable);
	 }
      }
      // Check for strings embedded within a list answer.
      else if (selectAnswerType.value == "List") {
        // Remove any paren chars around the list or sublists
	// as well as splitting the list on commas
        var elements = answer.split(/[,\(\[\{\}\]\)]/);
	for (var w=0; w<elements.length; w++) {
           element = elements[w].trim();
	   if (GUIWorK.fitb.isLetters(element)) {
	      stringList.include(element);
           }
	}
      }
    }
    // Add any additional legal-answer strings to the list of
    // recognized answers.
    var allowableText = questionElt.getElementsByClassName("fitb_allowableText")[0].value;
    if (!(/^[,|\s]*$/.test(allowableText))) { // not merely separator chars
      if (/[^,A-Za-z\s]/.test(allowableText)) {
        throw "Question " + nQuestion + " 'Additional allowable input' box contains bad character(s).";
      }
      var additionalStringList = allowableText.split(/\s+|\s*,\s*/);
      for (var i=0; i<additionalStringList.length; i++) {
         var answer = additionalStringList[i];
         stringList.include(answer);
      }
    }
    // Reduce string and variable lists to those that are new
    // to the overall problem and update the problem lists.
    stringList = stringList.diff(GUIWorK.fitb.contextStrings);
    varList = varList.diff(GUIWorK.fitb.contextVariables);
    GUIWorK.fitb.contextStrings = GUIWorK.fitb.contextStrings.union(stringList);
    GUIWorK.fitb.contextVariables = GUIWorK.fitb.contextVariables.union(varList);

    // Add new answer and variable strings to the context so that
    // WeBWorK will recognize them.
    if (stringList.length > 0) {
      outString += 'Context()->strings->add(';
      for (var i=0; i<stringList.length; i++) {
        outString += stringList[i] + '=>{},';
      }
      outString += ');\n';
    }
    if (varList.length > 0) {
      outString += 'Context()->variables->add(';
      for (var i=0; i<varList.length; i++) {
        outString += varList[i] + "=>'Real',";
      }
      outString += ');\n\n';
    }
    return outString;
  };

  // Add the question/answer-blank pairs to this question
GUIWorK.fitb.PGMLgen =
function PGMLgen(questionElt) {
    var outString = '';
    var nQuestion = getQuestionNum(questionElt);

    // First output any preliminary text.
    var prelimText = questionElt.getElementsByClassName("fitb_prelim")[0].value;
    outString += encodeLaTeXMathModePGML(prelimText);
    outString += '\n\n';

    // Next output the question/answer-blank pairs
    var blankSizeText = questionElt.getElementsByClassName("fitb_blankSize")[0].value;
    var blankText = '[_______________]'; // default blank is medium length
    if (blankSizeText=="short") {
      blankText = '[_]';
    }
    else if (blankSizeText=="long") {
      blankText = '[________________________________________]';
    }
    var qaPairs = questionElt.getElementsByClassName("fitb_qaPair");
    for (var i=0; i<qaPairs.length; i++) {
      var qaPair = qaPairs[i];
      outString += '*' + qaPair.getElementsByClassName("fitb_letter")[0].textContent + ")* ";
      outString += encodeLaTeXMathModePGML(qaPair.getElementsByClassName("fitb_question")[0].value);
      outString += '\n  ';
      outString += blankText;
      outString += '{';
      var rawAnswer = qaPair.getElementsByClassName("fitb_answer")[0].value;
      var selectAnswerType = qaPair.getElementsByClassName("fitb_ansType")[0];

      // If no answer type specified, let WeBWorK try to figure it out by
      // simply quoting the answer.  Otherwise, pass answer as a quoted
      // argument to the specified MathObjects constructor.
      if (selectAnswerType.selectedIndex == 0) {
        outString += '"' + rawAnswer + '"';
      }
      else {
        outString += selectAnswerType.value + '("' + rawAnswer + '")';
      }
      outString += '}\n\n';
    }
    return outString;
  };

/******* Utilities *******/

GUIWorK.fitb.isLetters =
function isLetters(answer) {
  return /^[A-Za-z]+$/.test(answer);
}

/******* Event handlers ********/

// Add a question/answer pair after the current q/a pair.
GUIWorK.fitb.addAnswer =
function addAnswer(addButton) {
    // Retrieve pointers to the current q/a div and
    // the div containing all q/a pairs.
    var qaDiv = addButton.parentNode.parentNode;
    var qaPairsDiv = qaDiv.parentNode;

    // Create the new q/a div as a clone of the current one
    // and insert it following the current answer box.
    var newQaDiv = qaDiv.cloneNode(true);
    qaPairsDiv.insertBefore(newQaDiv, qaDiv.nextSibling);

    // Clear the question and answer boxes (input elements might have
    // value attributes set).
    var question = newQaDiv.getElementsByClassName("fitb_question")[0];
    question.value = '';
    var answer = newQaDiv.getElementsByClassName("fitb_answer")[0];
    answer.value = '';

    // Increment the newly created div's letter as well as
    // the letters of all subsequent q/a divs.
    var nextNode = newQaDiv;
    do {
       if (nextNode.nodeType == Node.ELEMENT_NODE) {
       	  var letterSpan = nextNode.getElementsByClassName("fitb_letter")[0];
       	  letterSpan.textContent = nextLetter(letterSpan.textContent);
       }
       nextNode = nextNode.nextSibling;
    } while (nextNode);
  };

  // Delete the current answer box.
GUIWorK.fitb.delAnswer =
function delAnswer(delButton)
  {
    // Retrieve pointers to the current q/a div and
    // the div containing all q/a pairs.
    var qaDiv = delButton.parentNode.parentNode;
    var qaPairsDiv = qaDiv.parentNode;

    // Don't delete if there is only one q/a div remaining.
    if (qaPairsDiv.childElementCount <= 1) {
       window.alert("Cannot delete last remaining question/answer pair.");
       return;
    }

    // Delete this q/a pair.  Capture next sibling first.
    var nextNode = qaDiv.nextSibling;
    qaPairsDiv.removeChild(qaDiv);

    // Decrement the letters of all subsequent answers.
    while (nextNode) {
       if (nextNode.nodeType == Node.ELEMENT_NODE) {
       	  var letterSpan = nextNode.getElementsByClassName("fitb_letter")[0];
       	  letterSpan.textContent = prevLetter(letterSpan.textContent);
       }
       nextNode = nextNode.nextSibling;
    }
  };

<commit_msg>Matrix fill-in now works correctly.<commit_after>// TODO: Fix auto-variable extraction to ignore a longer
//   word, such as sin.

// TODO: Add options specific to data types.  For instance,
//   for Formula, allow user to specify symbols representing
//   functions (handy for sequence representation).

/**
 * GUIWorK fitb property contains all JavaScript code associated with
 * the fill-in-the-blank question type.
 * Attempts to automatically extract strings from lists and one-letter
 * variables from formulas and add these to appropriate Context.
 * The code inherits from QuestionType.
 */
GUIWorK.fitb = Object.create(GUIWorK.QuestionType.prototype);

// First call to PGgen for a new problem generation.
GUIWorK.fitb.firstPGgenCall = true;

// Tell PGgen to clear the contextStrings array in preparation for
// generating fresh code for the problem.

// NOTE: Default WeBWorK strings are
//   DNE,INF,INFINITY,NONE,inf,infinity
// We're also telling WeBWorK to clear these, so they are only
// available if explicitly given as an answer or specified by the
// user as allowable strings.
GUIWorK.fitb.init =
function init() {
  GUIWorK.fitb.firstPGgenCall = true;
};

// Add all answer Strings, including those embedded within Lists,
// along with any additional explicitly specified strings, to Context.
// Also add all one-character variable names within formulas to Context.
GUIWorK.fitb.PGgen =
function PGgen(questionElt) {
    var outString = '';
    var nQuestion = getQuestionNum(questionElt);
    var stringList = new GUIWorK.Set(); // Context strings for this question
    var varList = new GUIWorK.Set();    // Variable names for this question

    // If first call for a new problem generation, create empty string
    // and variable arrays and tell WeBWorK to clear its strings and
    // variables from Context.
    if (GUIWorK.fitb.firstPGgenCall) {
      GUIWorK.fitb.contextStrings = new GUIWorK.Set();
      GUIWorK.fitb.contextVariables = new GUIWorK.Set();
      outString += 'Context()->strings->are();\n';
      outString += 'Context()->variables->are();\n';
      GUIWorK.fitb.firstPGgenCall = false;
    }

    // Compile list of new string answers, saving them so that
    // later questions will not attempt to also add them to context.
    // Similarly, extract variable names from formulas.
    // Throw error if any answers are blank.
    var answerElts = questionElt.getElementsByClassName("fitb_answer");
    var answerTypeElts = questionElt.getElementsByClassName("fitb_ansType");
    for (var i=0; i<answerElts.length; i++) {
      var answer = answerElts[i].value.trim();
      var selectAnswerType = answerTypeElts[i];
      if (/^\s*$/.test(answer)) {
        throw "Question " + nQuestion + " has a blank answer.";
      }
      if (selectAnswerType.value == "String" ||
          (selectAnswerType.selectedIndex == 0 && GUIWorK.fitb.isLetters(answer))) {
	 stringList.include(answer);
      }
      // Find all of the (one-letter) variables in this formula.
      // In, e.g., sin(x)y, this will find x and y but not (any part of) sin.
      else if (selectAnswerType.value == "Formula") {
         var letterRE = /(^|[^A-Za-z])([A-Za-z])(?![A-Za-z])/g;
         var varArray;
	 while ((varArray=letterRE.exec(answer)) != null) {
	   var variable = varArray[2];
           varList.include(variable);
	 }
      }
      // Check for strings embedded within a list answer.
      else if (selectAnswerType.value == "List") {
        // Remove any paren chars around the list or sublists
	// as well as splitting the list on commas
        var elements = answer.split(/[,\(\[\{\}\]\)]/);
	for (var w=0; w<elements.length; w++) {
           element = elements[w].trim();
	   if (GUIWorK.fitb.isLetters(element)) {
	      stringList.include(element);
           }
	}
      }
      // Check for Matrix type. Matrix value needs to be wrapped in
      // Matrix Context, at least in ww_version 2.12.
      else if (selectAnswerType.value == "Matrix") {
        outString += 'Context("Matrix");\n';
	outString += GUIWorK.fitb.matrixVar(nQuestion,i)
                     + ' = Matrix(' + answer + ');\n';
        outString += 'Context("Numeric");\n';
      }
    }
    // Add any additional legal-answer strings to the list of
    // recognized answers.
    var allowableText = questionElt.getElementsByClassName("fitb_allowableText")[0].value;
    if (!(/^[,|\s]*$/.test(allowableText))) { // not merely separator chars
      var additionalStringList = allowableText.split(/\s+|\s*,\s*/);
      for (var i=0; i<additionalStringList.length; i++) {
         var answer = additionalStringList[i];
         stringList.include(answer);
      }
    }
    // Reduce string and variable lists to those that are new
    // to the overall problem and update the problem lists.
    stringList = stringList.diff(GUIWorK.fitb.contextStrings);
    varList = varList.diff(GUIWorK.fitb.contextVariables);
    GUIWorK.fitb.contextStrings = GUIWorK.fitb.contextStrings.union(stringList);
    GUIWorK.fitb.contextVariables = GUIWorK.fitb.contextVariables.union(varList);

    // Add new answer and variable strings to the context so that
    // WeBWorK will recognize them.
    if (stringList.length > 0) {
      outString += 'Context()->strings->add(';
      for (var i=0; i<stringList.length; i++) {
        outString += stringList[i] + '=>{},';
      }
      outString += ');\n';
    }
    if (varList.length > 0) {
      outString += 'Context()->variables->add(';
      for (var i=0; i<varList.length; i++) {
        outString += varList[i] + "=>'Real',";
      }
      outString += ');\n\n';
    }
    return outString;
  };

  // Add the question/answer-blank pairs to this question
GUIWorK.fitb.PGMLgen =
function PGMLgen(questionElt) {
    var outString = '';
    var nQuestion = getQuestionNum(questionElt);

    // First output any preliminary text.
    var prelimText = questionElt.getElementsByClassName("fitb_prelim")[0].value;
    outString += encodeLaTeXMathModePGML(prelimText);
    outString += '\n\n';

    // Next output the question/answer-blank pairs
    var blankSizeText = questionElt.getElementsByClassName("fitb_blankSize")[0].value;
    var blankText = '[_______________]'; // default blank is medium length
    if (blankSizeText=="short") {
      blankText = '[_]';
    }
    else if (blankSizeText=="long") {
      blankText = '[________________________________________________________]';
    }
    var qaPairs = questionElt.getElementsByClassName("fitb_qaPair");
    for (var i=0; i<qaPairs.length; i++) {
      var qaPair = qaPairs[i];
      outString += '*' + qaPair.getElementsByClassName("fitb_letter")[0].textContent + ")* ";
      outString += encodeLaTeXMathModePGML(qaPair.getElementsByClassName("fitb_question")[0].value);
      outString += '\n  ';
      outString += blankText;
      var rawAnswer = qaPair.getElementsByClassName("fitb_answer")[0].value;
      var selectAnswerType = qaPair.getElementsByClassName("fitb_ansType")[0];

      // If no answer type specified, let WeBWorK try to figure it out by
      // simply quoting the answer.  If Matrix type, use matrix variable
      // constructed during PG generation.
      // Otherwise, pass answer as a quoted
      // argument to the specified MathObjects constructor.
      if (selectAnswerType.selectedIndex == 0) {
        outString += '{"' + rawAnswer + '"';
      }
      else if (selectAnswerType.value == "Matrix") {
        outString += '*{' + GUIWorK.fitb.matrixVar(nQuestion,i);
      }
      else {
        outString += '{' + selectAnswerType.value + '("' + rawAnswer + '")';
      }
      outString += '}\n\n';
    }
    return outString;
  };

/******* Utilities *******/

GUIWorK.fitb.isLetters =
function isLetters(answer) {
  return /^[A-Za-z]+$/.test(answer);
}

// Passed question number and 0-based number of the part of the
// question, returns Perl variable unique to this question and part.
GUIWorK.fitb.matrixVar =
function matrixVar(question, part) {
  return "$fitb_matQ" + question
         + String.fromCharCode('a'.charCodeAt(0) + part);
}

/******* Event handlers ********/

// Add a question/answer pair after the current q/a pair.
GUIWorK.fitb.addAnswer =
function addAnswer(addButton) {
    // Retrieve pointers to the current q/a div and
    // the div containing all q/a pairs.
    var qaDiv = addButton.parentNode.parentNode;
    var qaPairsDiv = qaDiv.parentNode;

    // Create the new q/a div as a clone of the current one
    // and insert it following the current answer box.
    var newQaDiv = qaDiv.cloneNode(true);
    qaPairsDiv.insertBefore(newQaDiv, qaDiv.nextSibling);

    // Clear the question and answer boxes (input elements might have
    // value attributes set).
    var question = newQaDiv.getElementsByClassName("fitb_question")[0];
    question.value = '';
    var answer = newQaDiv.getElementsByClassName("fitb_answer")[0];
    answer.value = '';

    // Increment the newly created div's letter as well as
    // the letters of all subsequent q/a divs.
    var nextNode = newQaDiv;
    do {
       if (nextNode.nodeType == Node.ELEMENT_NODE) {
       	  var letterSpan = nextNode.getElementsByClassName("fitb_letter")[0];
       	  letterSpan.textContent = nextLetter(letterSpan.textContent);
       }
       nextNode = nextNode.nextSibling;
    } while (nextNode);
  };

  // Delete the current answer box.
GUIWorK.fitb.delAnswer =
function delAnswer(delButton)
  {
    // Retrieve pointers to the current q/a div and
    // the div containing all q/a pairs.
    var qaDiv = delButton.parentNode.parentNode;
    var qaPairsDiv = qaDiv.parentNode;

    // Don't delete if there is only one q/a div remaining.
    if (qaPairsDiv.childElementCount <= 1) {
       window.alert("Cannot delete last remaining question/answer pair.");
       return;
    }

    // Delete this q/a pair.  Capture next sibling first.
    var nextNode = qaDiv.nextSibling;
    qaPairsDiv.removeChild(qaDiv);

    // Decrement the letters of all subsequent answers.
    while (nextNode) {
       if (nextNode.nodeType == Node.ELEMENT_NODE) {
       	  var letterSpan = nextNode.getElementsByClassName("fitb_letter")[0];
       	  letterSpan.textContent = prevLetter(letterSpan.textContent);
       }
       nextNode = nextNode.nextSibling;
    }
  };

<|endoftext|>


<commit_before>/**
 * @namespace hs.search
 * @memberOf hs
 */
define(['angular', 'ol'],

    function(angular, ol) {
        angular.module('hs.styles', ['hs.map'])
            .service("hs.styles.service", ['$http',
                function($http) {
                    this.pin_white_blue = new ol.style.Style({
                        image: new ol.style.Icon({
                            src: hsl_path + 'img/pin_white_blue32.png',
                            crossOrigin: 'anonymous',
                            anchor: [0.5, 1]
                        })
                    });
                    this.pin_white_blue_highlight = function(feature, resolution) {
                        return [new ol.style.Style({
                            image: new ol.style.Icon({
                                src: feature.get('highlighted') ? hsl_path + 'img/pin_white_red32.png' : hsl_path + 'img/pin_white_blue32.png',
                                crossOrigin: 'anonymous',
                                anchor: [0.5, 1]
                            })
                        })]
                    };
                    this.measure_style = new ol.style.Style({
                        fill: new ol.style.Fill({
                            color: 'rgbaa(255, 255, 255, 1)'
                        }),
                        stroke: new ol.style.Stroke({
                            color: '#ffcc33',
                            width: 2
                        }),
                        image: new ol.style.Circle({
                            radius: 7,
                            fill: new ol.style.Fill({
                                color: '#ffcc33'
                            })
                        })
                    });
                    this.simple_style = new ol.style.Style({
                        fill: new ol.style.Fill({
                            color: 'rgbaa(255, 255, 255, 1)'
                        }),
                        stroke: new ol.style.Stroke({
                            color: '#ffcc33',
                            width: 1
                        }),
                        image: new ol.style.Circle({
                            radius: 7,
                            fill: new ol.style.Fill({
                                color: '#ffcc33'
                            })
                        })
                    });
                    var me = this;
                }
            ])

        .directive('hs.styler.directive', function() {
            return {
                templateUrl: hsl_path + 'components/styles/partials/styler.html'
            };
        })

        .directive('hs.styler.colorDirective', function() {
            return {

                scope: {
                    color: '=info'
                },
                link: function(scope, elem, attrs) {
                    scope.colors = [{
                        'background-color': 'rgba(244, 235, 55, 1)'
                    }, {
                        'background-color': 'rgba(205, 220, 57, 1)'
                    }, {
                        'background-color': 'rgba(98, 175, 68, 1)'
                    }, {
                        'background-color': 'rgba(0, 157, 87, 1)'
                    }, {
                        'background-color': 'rgba(11, 169, 204, 1)'
                    }, {
                        'background-color': 'rgba(65, 134, 240, 1)'
                    }, {
                        'background-color': 'rgba(63, 91, 169, 1)'
                    }, {
                        'background-color': 'rgba(124, 53, 146, 1)'
                    }, {
                        'background-color': 'rgba(166, 27, 74, 1)'
                    }, {
                        'background-color': 'rgba(219, 68, 54, 1)'
                    }, {
                        'background-color': 'rgba(248, 151, 27, 1)'
                    }, {
                        'background-color': 'rgba(244, 180, 0, 1)'
                    }, {
                        'background-color': 'rgba(121, 80, 70, 1)'
                    }, {
                        'background-color': 'rgba(249, 247, 166, 1)'
                    }, {
                        'background-color': 'rgba(230, 238, 163, 1)'
                    }, {
                        'background-color': 'rgba(183, 219, 171, 1)'
                    }, {
                        'background-color': 'rgba(124, 207, 169, 1)'
                    }, {
                        'background-color': 'rgba(147, 215, 232, 1)'
                    }, {
                        'background-color': 'rgba(159, 195, 255, 1)'
                    }, {
                        'background-color': 'rgba(167, 181, 215, 1)'
                    }, {
                        'background-color': 'rgba(198, 164, 207, 1)'
                    }, {
                        'background-color': 'rgba(214, 152, 173, 1)'
                    }, {
                        'background-color': 'rgba(238, 156, 150, 1)'
                    }, {
                        'background-color': 'rgba(250, 209, 153, 1)'
                    }, {
                        'background-color': 'rgba(255, 221, 94, 1)'
                    }, {
                        'background-color': 'rgba(178, 145, 137, 1)'
                    }, {
                        'background-color': 'rgba(255, 255, 255, 1)'
                    }, {
                        'background-color': 'rgba(204, 204, 204, 1)'
                    }, {
                        'background-color': 'rgba(119, 119, 119, 1)'
                    }, {
                        'background-color': 'rgba(0, 0, 0, 1)'
                    }];
                    scope.colorSelected = function(col) {
                        scope.color = col;
                    }
                },
                templateUrl: hsl_path + 'components/styles/partials/color.html'
            };
        })

        .service("hs.styler.service", [
            function() {
                this.layer = null;
            }
        ])

        .controller('hs.styler.controller', ['$scope', 'hs.styler.service', '$sce',
            function($scope, service, $sce) {
                $scope.service = service;
                $scope.icons = ["bag1.svg", "banking4.svg", "bar.svg", "beach17.svg", "bicycles.svg", "building103.svg", "bus4.svg", "cabinet9.svg", "camping13.svg", "caravan.svg", "church15.svg", "church1.svg", "coffee-shop1.svg", "disabled.svg", "favourite28.svg", "football1.svg", "footprint.svg", "gift-shop.svg", "gps35.svg", "gps36.svg", "gps37.svg", "gps40.svg", "gps41.svg", "gps42.svg", "gps43.svg", "gps5.svg", "hospital.svg", "hot-air-balloon2.svg", "information78.svg", "library21.svg", "location4.svg", "location6.svg", "luggage12.svg", "luggage13.svg", "map-pointer7.svg", "map-pointer8.svg", "monument1.svg", "mountain42.svg", "museum35.svg", "park11.svg", "parking28.svg", "pharmacy17.svg", "pin10.svg", "pin63.svg", "pin65.svg", "police-station.svg", "port2.svg", "restaurant52.svg", "road-sign1.svg", "sailing-boat2.svg", "ski1.svg", "swimming26.svg", "telephone119.svg", "toilets2.svg", "train-station.svg", "university2.svg", "warning.svg", "wifi8.svg"];
                $scope.imagetypes = [{
                    name: 'none',
                    hrname: 'None'
                }, {
                    name: 'icon',
                    hrname: 'Icon'
                }, {
                    name: 'circle',
                    hrname: 'Circle'
                }];
                $scope.imagetype = $scope.imagetypes[0].name;
                $scope.radius = 5;
                $scope.linewidth = 2;
                $scope.iconlinewidth = 2;
                $scope.save = function() {
                    if (service.layer == null) return;
                    var style_json = {};
                    if (angular.isDefined($scope.fillcolor) && $scope.fillcolor != null) {
                        style_json.fill = new ol.style.Fill({
                            color: $scope.fillcolor['background-color']
                        })
                    }
                    if (angular.isDefined($scope.linecolor) && $scope.linecolor != null && $scope.linewidth > 0) {
                        style_json.stroke = new ol.style.Stroke({
                            color: $scope.linecolor['background-color'],
                            width: angular.isDefined($scope.linewidth) ? parseFloat($scope.linewidth) : 1
                        })
                    }
                    if ($scope.imagetype != 'none') {
                        if ($scope.imagetype == 'circle' && (angular.isDefined($scope.iconfillcolor) || angular.isDefined($scope.iconlinecolor))) {
                            var circle_json = {
                                radius: angular.isDefined($scope.radius) ? parseFloat($scope.radius) : 5
                            };
                            if (angular.isDefined($scope.iconfillcolor) && $scope.iconfillcolor != null) {
                                circle_json.fill = new ol.style.Fill({
                                    color: $scope.iconfillcolor['background-color']
                                });
                            }
                            if (angular.isDefined($scope.iconlinecolor) && $scope.iconlinecolor != null && angular.isDefined($scope.iconlinewidth) && $scope.iconlinewidth > 0) {
                                circle_json.stroke = new ol.style.Stroke({
                                    color: $scope.iconlinecolor['background-color'],
                                    width: $scope.iconlinewidth,
                                    radius: angular.isDefined($scope.radius) ? parseFloat($scope.radius) : 5
                                })
                            }
                            style_json.image = new ol.style.Circle(circle_json);
                        }
                        if ($scope.imagetype == 'icon' && angular.isDefined($scope.serialized_icon)) {
                            var img = new Image();
                            img.src = $scope.serialized_icon;
                            img.onload = function() {
                                var icon_json = {
                                    img: img,
                                    imgSize: [img.width, img.height],
                                    anchor: [0.6, 0.8],
                                    crossOrigin: 'anonymous'
                                };
                                style_json.image = new ol.style.Icon(icon_json);
                                service.layer.setStyle(new ol.style.Style(style_json));
                            }
                        }
                    }
                    var style = new ol.style.Style(style_json);
                    angular.forEach(service.layer.getSource().getFeatures(), function(f) {
                        f.setStyle(null);
                    })
                    service.layer.setStyle(style);
                }

                $scope.iconSelected = function(i) {
                    $.ajax({
                        url: $scope.hsl_path + 'components/styles/img/svg/' + i,
                        cache: true,
                        success: function(r) {
                            $scope.iconimage = $sce.trustAsHtml(r.documentElement.outerHTML);
                            if (!$scope.$$phase) $scope.$digest();
                            colorIcon();
                            $scope.save()
                        }
                    });

                }

                function colorIcon() {
                    var $b =
                        $('.hs-styler-selected-icon-box path');
                    if (angular.isDefined($scope.iconfillcolor) && $scope.iconfillcolor != null) $b.css('fill', $scope.iconfillcolor['background-color'])
                    if (angular.isDefined($scope.iconlinecolor) && $scope.iconlinecolor != null) $b.css('stroke', $scope.iconlinecolor['background-color'])
                    if (angular.isDefined($scope.iconlinewidth) && $scope.iconlinewidth != null) $b.css('stroke-width', $scope.iconlinewidth);
                    $scope.serialized_icon = 'data:image/svg+xml;base64,' + window.btoa($('.hs-styler-selected-icon-box').html());
                }

                $scope.setImageType = function(t) {
                    $scope.imagetype = t;
                    $scope.save();
                }

                $scope.$watch('linecolor', $scope.save);
                $scope.$watch('service.layer', function() {
                    $scope.hasLine = false;
                    $scope.hasPoly = false;
                    $scope.hasPoint = false;
                    if (angular.isUndefined(service.layer) || service.layer == null) return;

                    angular.forEach(service.layer.getSource().getFeatures(), function(f) {
                        $scope.hasLine = $scope.hasLine || f.getGeometry().getType() == 'LineString';
                        $scope.hasPoly = $scope.hasPoly || f.getGeometry().getType() == 'Polygon';
                        $scope.hasPoint = $scope.hasPoint || f.getGeometry().getType() == 'Point';
                    })
                });
                $scope.$watch('fillcolor', $scope.save);
                $scope.$watch('iconfillcolor', function() {
                    if ($scope.imagetype == 'icon') colorIcon();
                    $scope.save()
                });
                $scope.$watch('iconlinecolor', function() {
                    if ($scope.imagetype == 'icon') colorIcon();
                    $scope.save()
                });
                $scope.$watch('iconlinewidth', function() {
                    if ($scope.imagetype == 'icon') colorIcon();
                    $scope.save()
                });
                $scope.$watch('radius', function() {
                    $scope.save()
                });

                $scope.$emit('scope_loaded', "styler");
            }
        ]);
    })
<commit_msg>Styler detection of MultiPolygon and MultiPoint<commit_after>/**
 * @namespace hs.search
 * @memberOf hs
 */
define(['angular', 'ol'],

    function(angular, ol) {
        angular.module('hs.styles', ['hs.map'])
            .service("hs.styles.service", ['$http',
                function($http) {
                    this.pin_white_blue = new ol.style.Style({
                        image: new ol.style.Icon({
                            src: hsl_path + 'img/pin_white_blue32.png',
                            crossOrigin: 'anonymous',
                            anchor: [0.5, 1]
                        })
                    });
                    this.pin_white_blue_highlight = function(feature, resolution) {
                        return [new ol.style.Style({
                            image: new ol.style.Icon({
                                src: feature.get('highlighted') ? hsl_path + 'img/pin_white_red32.png' : hsl_path + 'img/pin_white_blue32.png',
                                crossOrigin: 'anonymous',
                                anchor: [0.5, 1]
                            })
                        })]
                    };
                    this.measure_style = new ol.style.Style({
                        fill: new ol.style.Fill({
                            color: 'rgbaa(255, 255, 255, 1)'
                        }),
                        stroke: new ol.style.Stroke({
                            color: '#ffcc33',
                            width: 2
                        }),
                        image: new ol.style.Circle({
                            radius: 7,
                            fill: new ol.style.Fill({
                                color: '#ffcc33'
                            })
                        })
                    });
                    this.simple_style = new ol.style.Style({
                        fill: new ol.style.Fill({
                            color: 'rgbaa(255, 255, 255, 1)'
                        }),
                        stroke: new ol.style.Stroke({
                            color: '#ffcc33',
                            width: 1
                        }),
                        image: new ol.style.Circle({
                            radius: 7,
                            fill: new ol.style.Fill({
                                color: '#ffcc33'
                            })
                        })
                    });
                    var me = this;
                }
            ])

        .directive('hs.styler.directive', function() {
            return {
                templateUrl: hsl_path + 'components/styles/partials/styler.html'
            };
        })

        .directive('hs.styler.colorDirective', function() {
            return {

                scope: {
                    color: '=info'
                },
                link: function(scope, elem, attrs) {
                    scope.colors = [{
                        'background-color': 'rgba(244, 235, 55, 1)'
                    }, {
                        'background-color': 'rgba(205, 220, 57, 1)'
                    }, {
                        'background-color': 'rgba(98, 175, 68, 1)'
                    }, {
                        'background-color': 'rgba(0, 157, 87, 1)'
                    }, {
                        'background-color': 'rgba(11, 169, 204, 1)'
                    }, {
                        'background-color': 'rgba(65, 134, 240, 1)'
                    }, {
                        'background-color': 'rgba(63, 91, 169, 1)'
                    }, {
                        'background-color': 'rgba(124, 53, 146, 1)'
                    }, {
                        'background-color': 'rgba(166, 27, 74, 1)'
                    }, {
                        'background-color': 'rgba(219, 68, 54, 1)'
                    }, {
                        'background-color': 'rgba(248, 151, 27, 1)'
                    }, {
                        'background-color': 'rgba(244, 180, 0, 1)'
                    }, {
                        'background-color': 'rgba(121, 80, 70, 1)'
                    }, {
                        'background-color': 'rgba(249, 247, 166, 1)'
                    }, {
                        'background-color': 'rgba(230, 238, 163, 1)'
                    }, {
                        'background-color': 'rgba(183, 219, 171, 1)'
                    }, {
                        'background-color': 'rgba(124, 207, 169, 1)'
                    }, {
                        'background-color': 'rgba(147, 215, 232, 1)'
                    }, {
                        'background-color': 'rgba(159, 195, 255, 1)'
                    }, {
                        'background-color': 'rgba(167, 181, 215, 1)'
                    }, {
                        'background-color': 'rgba(198, 164, 207, 1)'
                    }, {
                        'background-color': 'rgba(214, 152, 173, 1)'
                    }, {
                        'background-color': 'rgba(238, 156, 150, 1)'
                    }, {
                        'background-color': 'rgba(250, 209, 153, 1)'
                    }, {
                        'background-color': 'rgba(255, 221, 94, 1)'
                    }, {
                        'background-color': 'rgba(178, 145, 137, 1)'
                    }, {
                        'background-color': 'rgba(255, 255, 255, 1)'
                    }, {
                        'background-color': 'rgba(204, 204, 204, 1)'
                    }, {
                        'background-color': 'rgba(119, 119, 119, 1)'
                    }, {
                        'background-color': 'rgba(0, 0, 0, 1)'
                    }];
                    scope.colorSelected = function(col) {
                        scope.color = col;
                    }
                },
                templateUrl: hsl_path + 'components/styles/partials/color.html'
            };
        })

        .service("hs.styler.service", [
            function() {
                this.layer = null;
            }
        ])

        .controller('hs.styler.controller', ['$scope', 'hs.styler.service', '$sce',
            function($scope, service, $sce) {
                $scope.service = service;
                $scope.icons = ["bag1.svg", "banking4.svg", "bar.svg", "beach17.svg", "bicycles.svg", "building103.svg", "bus4.svg", "cabinet9.svg", "camping13.svg", "caravan.svg", "church15.svg", "church1.svg", "coffee-shop1.svg", "disabled.svg", "favourite28.svg", "football1.svg", "footprint.svg", "gift-shop.svg", "gps35.svg", "gps36.svg", "gps37.svg", "gps40.svg", "gps41.svg", "gps42.svg", "gps43.svg", "gps5.svg", "hospital.svg", "hot-air-balloon2.svg", "information78.svg", "library21.svg", "location4.svg", "location6.svg", "luggage12.svg", "luggage13.svg", "map-pointer7.svg", "map-pointer8.svg", "monument1.svg", "mountain42.svg", "museum35.svg", "park11.svg", "parking28.svg", "pharmacy17.svg", "pin10.svg", "pin63.svg", "pin65.svg", "police-station.svg", "port2.svg", "restaurant52.svg", "road-sign1.svg", "sailing-boat2.svg", "ski1.svg", "swimming26.svg", "telephone119.svg", "toilets2.svg", "train-station.svg", "university2.svg", "warning.svg", "wifi8.svg"];
                $scope.imagetypes = [{
                    name: 'none',
                    hrname: 'None'
                }, {
                    name: 'icon',
                    hrname: 'Icon'
                }, {
                    name: 'circle',
                    hrname: 'Circle'
                }];
                $scope.imagetype = $scope.imagetypes[0].name;
                $scope.radius = 5;
                $scope.linewidth = 2;
                $scope.iconlinewidth = 2;
                $scope.save = function() {
                    if (service.layer == null) return;
                    var style_json = {};
                    if (angular.isDefined($scope.fillcolor) && $scope.fillcolor != null) {
                        style_json.fill = new ol.style.Fill({
                            color: $scope.fillcolor['background-color']
                        })
                    }
                    if (angular.isDefined($scope.linecolor) && $scope.linecolor != null && $scope.linewidth > 0) {
                        style_json.stroke = new ol.style.Stroke({
                            color: $scope.linecolor['background-color'],
                            width: angular.isDefined($scope.linewidth) ? parseFloat($scope.linewidth) : 1
                        })
                    }
                    if ($scope.imagetype != 'none') {
                        if ($scope.imagetype == 'circle' && (angular.isDefined($scope.iconfillcolor) || angular.isDefined($scope.iconlinecolor))) {
                            var circle_json = {
                                radius: angular.isDefined($scope.radius) ? parseFloat($scope.radius) : 5
                            };
                            if (angular.isDefined($scope.iconfillcolor) && $scope.iconfillcolor != null) {
                                circle_json.fill = new ol.style.Fill({
                                    color: $scope.iconfillcolor['background-color']
                                });
                            }
                            if (angular.isDefined($scope.iconlinecolor) && $scope.iconlinecolor != null && angular.isDefined($scope.iconlinewidth) && $scope.iconlinewidth > 0) {
                                circle_json.stroke = new ol.style.Stroke({
                                    color: $scope.iconlinecolor['background-color'],
                                    width: $scope.iconlinewidth,
                                    radius: angular.isDefined($scope.radius) ? parseFloat($scope.radius) : 5
                                })
                            }
                            style_json.image = new ol.style.Circle(circle_json);
                        }
                        if ($scope.imagetype == 'icon' && angular.isDefined($scope.serialized_icon)) {
                            var img = new Image();
                            img.src = $scope.serialized_icon;
                            img.onload = function() {
                                var icon_json = {
                                    img: img,
                                    imgSize: [img.width, img.height],
                                    anchor: [0.6, 0.8],
                                    crossOrigin: 'anonymous'
                                };
                                style_json.image = new ol.style.Icon(icon_json);
                                service.layer.setStyle(new ol.style.Style(style_json));
                            }
                        }
                    }
                    var style = new ol.style.Style(style_json);
                    angular.forEach(service.layer.getSource().getFeatures(), function(f) {
                        f.setStyle(null);
                    })
                    service.layer.setStyle(style);
                }

                $scope.iconSelected = function(i) {
                    $.ajax({
                        url: $scope.hsl_path + 'components/styles/img/svg/' + i,
                        cache: true,
                        success: function(r) {
                            $scope.iconimage = $sce.trustAsHtml(r.documentElement.outerHTML);
                            if (!$scope.$$phase) $scope.$digest();
                            colorIcon();
                            $scope.save()
                        }
                    });

                }

                function colorIcon() {
                    var $b =
                        $('.hs-styler-selected-icon-box path');
                    if (angular.isDefined($scope.iconfillcolor) && $scope.iconfillcolor != null) $b.css('fill', $scope.iconfillcolor['background-color'])
                    if (angular.isDefined($scope.iconlinecolor) && $scope.iconlinecolor != null) $b.css('stroke', $scope.iconlinecolor['background-color'])
                    if (angular.isDefined($scope.iconlinewidth) && $scope.iconlinewidth != null) $b.css('stroke-width', $scope.iconlinewidth);
                    $scope.serialized_icon = 'data:image/svg+xml;base64,' + window.btoa($('.hs-styler-selected-icon-box').html());
                }

                $scope.setImageType = function(t) {
                    $scope.imagetype = t;
                    $scope.save();
                }

                $scope.$watch('linecolor', $scope.save);
                $scope.$watch('service.layer', function() {
                    $scope.hasLine = false;
                    $scope.hasPoly = false;
                    $scope.hasPoint = false;
                    if (angular.isUndefined(service.layer) || service.layer == null) return;

                    angular.forEach(service.layer.getSource().getFeatures(), function(f) {
                        $scope.hasLine = $scope.hasLine || f.getGeometry().getType() == 'LineString';
                        $scope.hasPoly = $scope.hasPoly || f.getGeometry().getType() == 'Polygon';
                        $scope.hasPoly = $scope.hasPoly || f.getGeometry().getType() == 'MultiPolygon';
                        $scope.hasPoint = $scope.hasPoint || f.getGeometry().getType() == 'Point';
                        $scope.hasPoint = $scope.hasPoint || f.getGeometry().getType() == 'MultiPoint';
                    })
                });
                $scope.$watch('fillcolor', $scope.save);
                $scope.$watch('iconfillcolor', function() {
                    if ($scope.imagetype == 'icon') colorIcon();
                    $scope.save()
                });
                $scope.$watch('iconlinecolor', function() {
                    if ($scope.imagetype == 'icon') colorIcon();
                    $scope.save()
                });
                $scope.$watch('iconlinewidth', function() {
                    if ($scope.imagetype == 'icon') colorIcon();
                    $scope.save()
                });
                $scope.$watch('radius', function() {
                    $scope.save()
                });

                $scope.$emit('scope_loaded', "styler");
            }
        ]);
    })
<|endoftext|>