<commit_before>/**
 * Copyright (C) 2022  The Software Heritage developers
 * See the AUTHORS file at the top-level directory of this distribution
 * License: GNU Affero General Public License version 3, or any later version
 * See top-level LICENSE file for more information
 */

import {handleFetchError, csrfPost,
        getHumanReadableDate} from 'utils/functions';
import userRequestsFilterCheckboxFn from 'utils/requests-filter-checkbox.ejs';
import {swhSpinnerSrc} from 'utils/constants';

let requestBrowseTable;

const addForgeCheckboxId = 'swh-add-forge-user-filter';
const userRequestsFilterCheckbox = userRequestsFilterCheckboxFn({
  'inputId': addForgeCheckboxId,
  'checked': true // by default, display only user requests
});

export function onCreateRequestPageLoad() {
  $(document).ready(() => {
    $('#requestCreateForm').submit(async function(event) {
      event.preventDefault();
      try {
        const response = await csrfPost($(this).attr('action'),
                                        {'Content-Type': 'application/x-www-form-urlencoded'},
                                        $(this).serialize());
        handleFetchError(response);
        $('#userMessageDetail').empty();
        $('#userMessage').text('Your request has been submitted');
        $('#userMessage').removeClass('badge-danger');
        $('#userMessage').addClass('badge-success');
        requestBrowseTable.draw(); // redraw the table to update the list
      } catch (errorResponse) {
        $('#userMessageDetail').empty();

        let errorMessage;
        let errorMessageDetail = '';
        const errorData = await errorResponse.json();
        // if (errorResponse.content_type === 'text/plain') { // does not work?
        if (errorResponse.status === 409) {
          errorMessage = errorData;
        } else { // assuming json response
          // const exception = errorData['exception'];
          errorMessage = 'An unknown error occurred during the request creation';
          try {
            const reason = JSON.parse(errorData['reason']);
            Object.entries(reason).forEach((keys, _) => {
              const key = keys[0];
              const message = keys[1][0]; // take only the first issue
              errorMessageDetail += `\n${key}: ${message}`;
            });
          } catch (_) {
            errorMessageDetail = errorData['reason']; // can't parse it, leave it raw
          }
        }
        $('#userMessage').text(
          errorMessageDetail ? `Error: ${errorMessageDetail}` : errorMessage
        );
        $('#userMessage').removeClass('badge-success');
        $('#userMessage').addClass('badge-danger');
      }
    });

    populateRequestBrowseList(); // Load existing requests
  });
}

export function populateRequestBrowseList() {
  requestBrowseTable = $('#add-forge-request-browse')
    .on('error.dt', (e, settings, techNote, message) => {
      $('#add-forge-browse-request-error').text(message);
    })
    .DataTable({
      serverSide: true,
      processing: true,
      language: {
        processing: `<img src="${swhSpinnerSrc}"></img>`
      },
      retrieve: true,
      searching: true,
      info: false,
      // Layout configuration, see [1] for more details
      // [1] https://datatables.net/reference/option/dom
      dom: '<"row"<"col-sm-3"l><"col-sm-6 text-left user-requests-filter"><"col-sm-3"f>>' +
           '<"row"<"col-sm-12"tr>>' +
           '<"row"<"col-sm-5"i><"col-sm-7"p>>',
      ajax: {
        'url': Urls.add_forge_request_list_datatables(),
        data: (d) => {
          const checked = $(`#${addForgeCheckboxId}`).prop('checked');
          // If this function is called while the page is loading, 'checked' is
          // undefined. As the checkbox defaults to being checked, coerce this to true.
          if (swh.webapp.isUserLoggedIn() && (checked === undefined || checked)) {
            d.user_requests_only = '1';
          }
        }
      },
      fnInitComplete: function() {
        if (swh.webapp.isUserLoggedIn()) {
          $('div.user-requests-filter').html(userRequestsFilterCheckbox);
          $(`#${addForgeCheckboxId}`).on('change', () => {
            requestBrowseTable.draw();
          });
        }
      },
      columns: [
        {
          data: 'submission_date',
          name: 'submission_date',
          render: getHumanReadableDate
        },
        {
          data: 'forge_type',
          name: 'forge_type'
        },
        {
          data: 'forge_url',
          name: 'forge_url'
        },
        {
          data: 'status',
          name: 'status',
          render: function(data, type, row, meta) {
            return swh.add_forge.formatRequestStatusName(data);
          }
        }
      ]
    });
}
<commit_msg>Save Forge Now: Fix XSS in request list<commit_after>/**
 * Copyright (C) 2022  The Software Heritage developers
 * See the AUTHORS file at the top-level directory of this distribution
 * License: GNU Affero General Public License version 3, or any later version
 * See top-level LICENSE file for more information
 */

import {handleFetchError, csrfPost,
        getHumanReadableDate} from 'utils/functions';
import userRequestsFilterCheckboxFn from 'utils/requests-filter-checkbox.ejs';
import {swhSpinnerSrc} from 'utils/constants';

let requestBrowseTable;

const addForgeCheckboxId = 'swh-add-forge-user-filter';
const userRequestsFilterCheckbox = userRequestsFilterCheckboxFn({
  'inputId': addForgeCheckboxId,
  'checked': true // by default, display only user requests
});

export function onCreateRequestPageLoad() {
  $(document).ready(() => {
    $('#requestCreateForm').submit(async function(event) {
      event.preventDefault();
      try {
        const response = await csrfPost($(this).attr('action'),
                                        {'Content-Type': 'application/x-www-form-urlencoded'},
                                        $(this).serialize());
        handleFetchError(response);
        $('#userMessageDetail').empty();
        $('#userMessage').text('Your request has been submitted');
        $('#userMessage').removeClass('badge-danger');
        $('#userMessage').addClass('badge-success');
        requestBrowseTable.draw(); // redraw the table to update the list
      } catch (errorResponse) {
        $('#userMessageDetail').empty();

        let errorMessage;
        let errorMessageDetail = '';
        const errorData = await errorResponse.json();
        // if (errorResponse.content_type === 'text/plain') { // does not work?
        if (errorResponse.status === 409) {
          errorMessage = errorData;
        } else { // assuming json response
          // const exception = errorData['exception'];
          errorMessage = 'An unknown error occurred during the request creation';
          try {
            const reason = JSON.parse(errorData['reason']);
            Object.entries(reason).forEach((keys, _) => {
              const key = keys[0];
              const message = keys[1][0]; // take only the first issue
              errorMessageDetail += `\n${key}: ${message}`;
            });
          } catch (_) {
            errorMessageDetail = errorData['reason']; // can't parse it, leave it raw
          }
        }
        $('#userMessage').text(
          errorMessageDetail ? `Error: ${errorMessageDetail}` : errorMessage
        );
        $('#userMessage').removeClass('badge-success');
        $('#userMessage').addClass('badge-danger');
      }
    });

    populateRequestBrowseList(); // Load existing requests
  });
}

export function populateRequestBrowseList() {
  requestBrowseTable = $('#add-forge-request-browse')
    .on('error.dt', (e, settings, techNote, message) => {
      $('#add-forge-browse-request-error').text(message);
    })
    .DataTable({
      serverSide: true,
      processing: true,
      language: {
        processing: `<img src="${swhSpinnerSrc}"></img>`
      },
      retrieve: true,
      searching: true,
      info: false,
      // Layout configuration, see [1] for more details
      // [1] https://datatables.net/reference/option/dom
      dom: '<"row"<"col-sm-3"l><"col-sm-6 text-left user-requests-filter"><"col-sm-3"f>>' +
           '<"row"<"col-sm-12"tr>>' +
           '<"row"<"col-sm-5"i><"col-sm-7"p>>',
      ajax: {
        'url': Urls.add_forge_request_list_datatables(),
        data: (d) => {
          const checked = $(`#${addForgeCheckboxId}`).prop('checked');
          // If this function is called while the page is loading, 'checked' is
          // undefined. As the checkbox defaults to being checked, coerce this to true.
          if (swh.webapp.isUserLoggedIn() && (checked === undefined || checked)) {
            d.user_requests_only = '1';
          }
        }
      },
      fnInitComplete: function() {
        if (swh.webapp.isUserLoggedIn()) {
          $('div.user-requests-filter').html(userRequestsFilterCheckbox);
          $(`#${addForgeCheckboxId}`).on('change', () => {
            requestBrowseTable.draw();
          });
        }
      },
      columns: [
        {
          data: 'submission_date',
          name: 'submission_date',
          render: getHumanReadableDate
        },
        {
          data: 'forge_type',
          name: 'forge_type',
          render: $.fn.dataTable.render.text()
        },
        {
          data: 'forge_url',
          name: 'forge_url',
          render: $.fn.dataTable.render.text()
        },
        {
          data: 'status',
          name: 'status',
          render: function(data, type, row, meta) {
            return swh.add_forge.formatRequestStatusName(data);
          }
        }
      ]
    });
}
<|endoftext|>


<commit_before>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import StoryPropTypes from '../../types';
import MediaOutput from '../media/output';

function defaultForUndefined(value, def) {
  return value === undefined ? def : value;
}

function VideoOutput({ element, box }) {
  const { resource, loop } = element;

  const sourceProps = {
    type: resource.mimeType,
    src: resource.src,
  };

  const props = {
    autoPlay: 'autoplay',
    poster: defaultForUndefined(element.poster, resource.poster),
    artwork: defaultForUndefined(element.poster, resource.poster),
    title: defaultForUndefined(element.title, resource.title),
    alt: defaultForUndefined(element.alt, resource.alt),
    layout: 'fill',
    loop: loop ? 'loop' : undefined,
  };

  const tracks = defaultForUndefined(element.tracks, resource.tracks);

  // crossorigin='anonymous' is required to play videos from other domains.
  return (
    <MediaOutput element={element} box={box}>
      <amp-video {...props} id={`el-${element.id}-media`}>
        <source {...sourceProps} />
        {tracks &&
          tracks.map(({ srclang, label, kind, track, id }) => (
            <track
              srcLang={srclang}
              label={label}
              kind={kind}
              src={track}
              key={id}
            />
          ))}
      </amp-video>
    </MediaOutput>
  );
}

VideoOutput.propTypes = {
  element: StoryPropTypes.elements.video.isRequired,
  box: StoryPropTypes.box.isRequired,
};

export default VideoOutput;
<commit_msg>tracks are just on elements.<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * 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
 *
 *     https://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.
 */

/**
 * Internal dependencies
 */
import StoryPropTypes from '../../types';
import MediaOutput from '../media/output';

function defaultForUndefined(value, def) {
  return value === undefined ? def : value;
}

function VideoOutput({ element, box }) {
  const { resource, loop, tracks } = element;

  const sourceProps = {
    type: resource.mimeType,
    src: resource.src,
  };

  const props = {
    autoPlay: 'autoplay',
    poster: defaultForUndefined(element.poster, resource.poster),
    artwork: defaultForUndefined(element.poster, resource.poster),
    title: defaultForUndefined(element.title, resource.title),
    alt: defaultForUndefined(element.alt, resource.alt),
    layout: 'fill',
    loop: loop ? 'loop' : undefined,
  };

  // crossorigin='anonymous' is required to play videos from other domains.
  return (
    <MediaOutput element={element} box={box}>
      <amp-video {...props} id={`el-${element.id}-media`}>
        <source {...sourceProps} />
        {tracks &&
          tracks.map(({ srclang, label, kind, track, id }) => (
            <track
              srcLang={srclang}
              label={label}
              kind={kind}
              src={track}
              key={id}
            />
          ))}
      </amp-video>
    </MediaOutput>
  );
}

VideoOutput.propTypes = {
  element: StoryPropTypes.elements.video.isRequired,
  box: StoryPropTypes.box.isRequired,
};

export default VideoOutput;
<|endoftext|>


<commit_before>/**
 * @module ios-driver
 */

UIAAlert.prototype.defaultButton_origin = UIAAlert.prototype.defaultButton;

/**
 * dismiss the alert.
 */
UIAAlert.prototype.dismiss = function () {
    var button = this.cancelButton();
    if (button.type() === "UIAElementNil") {
        log("cancel is not there,trying default.");
        button = this.defaultButton();
        if (button.type() === "UIAElementNil") {
            log("default button missing, trying zeroth button");
            button = this.buttons()[0];
            if (button.type() === "UIAElementNil") {
                throw new UIAutomationException("this alert doesn't have the normal buttons.", 7);
            }
        }
    } else {
        log("cancel found");
    }

    // Apple's [UIAElement tap] implementation can wait for animations which we need to do in this case.
    // Furthermore, it seems impossible to determine if the element is in an alert before tapping, so we
    // directly call the original implementation here.
    button.tap_original();
    UIAutomation.cache.clearAlert();
    UIATarget.localTarget().delay(0.5);
}

/**
 * accept the alert.
 */
UIAAlert.prototype.accept = function () {
    var button = this.defaultButton();
    if (button.type() === "UIAElementNil") {
        var buttons = this.buttons();
        button = buttons["OK"];
        if (button.type() === "UIAElementNil" && buttons.length > 0) {
            button = buttons[buttons.length - 1];
            if (button.type() === "UIAElementNil") {
                throw new UIAutomationException("this alert doesn't have a default normal buttons.", 7);
            }
        }
    }
    button.tap();
    UIATarget.localTarget().delay(0.5);
}

/**
 * send the value to the alert, assuming it has a text field.
 * @param value
 */
UIAAlert.prototype.sendKeys = function (value) {
    var criteria = {"OR": [
        {"l10n": "none", "expected": "UIATextField", "matching": "exact", "method": "type"},
        {"l10n": "none", "expected": "UIASecureTextField", "matching": "exact", "method": "type"}
    ]};
    try {
        this.element(-1, criteria);
    } catch (err) {
        throw new UIAutomationException("this alert doesn't accept inputs.", 12);
    }
    var app = UIAutomation.cache.get(1);
    var keyboard = app.keyboard();
    keyboard.typeString(value);

}

UIAAlert.prototype.getText = function () {
    var criteria = {"l10n": "none", "expected": "UIAStaticText", "matching": "exact", "method": "type"};
    var texts = this.elements2(-1, criteria);
    if (texts.length > 1) {
        return texts[1].name();
    } else {
        throw new UIAutomationException("this alert doesn't have any text."+this.tree(false));
    }
}
/**
 * return the default button, but also send an event that the alert disappeared.
 * @return {UIAButton} the default button for the alert.
 */
UIAAlert.prototype.defaultButton = function () {
    var res = this.defaultButton_origin();
    res.tap = function () {
        if (this.isVisible()) {
            var rect = this.rect();
            var x = rect.origin.x + (rect.size.width / 2);
            var y = rect.origin.y + (rect.size.height / 2);
            var point = {
                'x': Math.floor(x),
                'y': Math.floor(y)
            };
            UIATarget.localTarget().tap(point);
            UIAutomation.cache.clearAlert();
        } else {
            throw new UIAutomationException("element is not visible", 11);
        }
    }
    return res;
}
UIAAlert.prototype.cancelButton_origin = UIAAlert.prototype.cancelButton;

/**
 * return the cancel button, but also send an event that the alert disappeared.
 * @return {UIAButton} the cancel button for the alert.
 */
UIAAlert.prototype.cancelButton = function () {
    var res = this.cancelButton_origin();

    res.tap = function () {
        if (this.isVisible()) {
            var rect = this.rect();
            var x = rect.origin.x + (rect.size.width / 2);
            var y = rect.origin.y + (rect.size.height / 2);
            var point = {
                'x': Math.floor(x),
                'y': Math.floor(y)
            };
            UIATarget.localTarget().tap(point);
            UIAutomation.cache.clearAlert();
        } else {
            throw new UIAutomationException("element is not visible", 11);
        }
    }
    return res;
}

UIAAlert.prototype.isSSLWarning = function(){
    if (this.name() === "Cannot Verify Server Identity"){
        return true;
    }
    return false;
}

UIAAlert.prototype.isAlternateKeyboard = function(){
    if (this.name() === "Alternate Keyboards"){
        return true;
    }
    return false;
}
<commit_msg>Apply fix to UIAAlert's accept button (along with dismiss).<commit_after>/**
 * @module ios-driver
 */

UIAAlert.prototype.defaultButton_origin = UIAAlert.prototype.defaultButton;

/**
 * dismiss the alert.
 */
UIAAlert.prototype.dismiss = function () {
    var button = this.cancelButton();
    if (button.type() === "UIAElementNil") {
        log("cancel is not there,trying default.");
        button = this.defaultButton();
        if (button.type() === "UIAElementNil") {
            log("default button missing, trying zeroth button");
            button = this.buttons()[0];
            if (button.type() === "UIAElementNil") {
                throw new UIAutomationException("this alert doesn't have the normal buttons.", 7);
            }
        }
    } else {
        log("cancel found");
    }

    // Apple's [UIAElement tap] implementation can wait for animations which we need to do in this case.
    // Furthermore, it seems impossible to determine if the element is in an alert before tapping, so we
    // directly call the original implementation here.
    button.tap_original();
    UIAutomation.cache.clearAlert();
    UIATarget.localTarget().delay(0.5);
}

/**
 * accept the alert.
 */
UIAAlert.prototype.accept = function () {
    var button = this.defaultButton();
    if (button.type() === "UIAElementNil") {
        var buttons = this.buttons();
        button = buttons["OK"];
        if (button.type() === "UIAElementNil" && buttons.length > 0) {
            button = buttons[buttons.length - 1];
            if (button.type() === "UIAElementNil") {
                throw new UIAutomationException("this alert doesn't have a default normal buttons.", 7);
            }
        }
    }

    // Apple's [UIAElement tap] implementation can wait for animations which we need to do in this case.
    // Furthermore, it seems impossible to determine if the element is in an alert before tapping, so we
    // directly call the original implementation here.
    button.tap_original();
    UIAutomation.cache.clearAlert();
    UIATarget.localTarget().delay(0.5);
}

/**
 * send the value to the alert, assuming it has a text field.
 * @param value
 */
UIAAlert.prototype.sendKeys = function (value) {
    var criteria = {"OR": [
        {"l10n": "none", "expected": "UIATextField", "matching": "exact", "method": "type"},
        {"l10n": "none", "expected": "UIASecureTextField", "matching": "exact", "method": "type"}
    ]};
    try {
        this.element(-1, criteria);
    } catch (err) {
        throw new UIAutomationException("this alert doesn't accept inputs.", 12);
    }
    var app = UIAutomation.cache.get(1);
    var keyboard = app.keyboard();
    keyboard.typeString(value);

}

UIAAlert.prototype.getText = function () {
    var criteria = {"l10n": "none", "expected": "UIAStaticText", "matching": "exact", "method": "type"};
    var texts = this.elements2(-1, criteria);
    if (texts.length > 1) {
        return texts[1].name();
    } else {
        throw new UIAutomationException("this alert doesn't have any text."+this.tree(false));
    }
}
/**
 * return the default button, but also send an event that the alert disappeared.
 * @return {UIAButton} the default button for the alert.
 */
UIAAlert.prototype.defaultButton = function () {
    var res = this.defaultButton_origin();
    res.tap = function () {
        if (this.isVisible()) {
            var rect = this.rect();
            var x = rect.origin.x + (rect.size.width / 2);
            var y = rect.origin.y + (rect.size.height / 2);
            var point = {
                'x': Math.floor(x),
                'y': Math.floor(y)
            };
            UIATarget.localTarget().tap(point);
            UIAutomation.cache.clearAlert();
        } else {
            throw new UIAutomationException("element is not visible", 11);
        }
    }
    return res;
}
UIAAlert.prototype.cancelButton_origin = UIAAlert.prototype.cancelButton;

/**
 * return the cancel button, but also send an event that the alert disappeared.
 * @return {UIAButton} the cancel button for the alert.
 */
UIAAlert.prototype.cancelButton = function () {
    var res = this.cancelButton_origin();

    res.tap = function () {
        if (this.isVisible()) {
            var rect = this.rect();
            var x = rect.origin.x + (rect.size.width / 2);
            var y = rect.origin.y + (rect.size.height / 2);
            var point = {
                'x': Math.floor(x),
                'y': Math.floor(y)
            };
            UIATarget.localTarget().tap(point);
            UIAutomation.cache.clearAlert();
        } else {
            throw new UIAutomationException("element is not visible", 11);
        }
    }
    return res;
}

UIAAlert.prototype.isSSLWarning = function(){
    if (this.name() === "Cannot Verify Server Identity"){
        return true;
    }
    return false;
}

UIAAlert.prototype.isAlternateKeyboard = function(){
    if (this.name() === "Alternate Keyboards"){
        return true;
    }
    return false;
}
<|endoftext|>


<commit_before>import styled from 'styled-components'
import { getColor } from '../../styles/utilities/color'
import { rgba } from '../../utilities/color'

export const CheckMarkCardUI = styled('label')`
  box-sizing: border-box;
  position: relative;
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: center;
  width: 100%;
  max-width: ${({ maxWidth }) => (maxWidth ? maxWidth : '100px')};
  height: ${({ height }) => (height ? height : 'auto')};
  min-height: 100px;
  border: 1px solid ${rgba(getColor('grey.700'), 0.7)};
  border-radius: 4px;
  background: white;
  box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
  transition: all 0.15s;
  will-change: box-shadow, border;
  cursor: pointer;

  &:hover {
    box-shadow: 0px 4px 20px rgba(0, 0, 0, 0.1);
    transform: translateY(-2px);
  }

  &.is-checked,
  &.is-focused,
  &.is-focused.is-checked,
  &:focus-within,
  &.is-checked:focus-within {
    border-color: transparent;
    box-shadow: 0px 0px 0 2px ${getColor('blue.500')};
  }

  &.is-disabled {
    cursor: not-allowed;
    opacity: 0.4;
    box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);

    &:hover {
      box-shadow: 0px 1px 3px rgba(0, 0, 0, 0.1);
      transform: translateY(0);
    }
  }

  &.with-status {
    cursor: default;
    border-color: transparent;
    box-shadow: 0px 0px 0 2px
      ${({ withStatus }) =>
        Boolean(withStatus) ? withStatus.color : 'rgba(0, 0, 0, 0.1)'};

    &:hover {
      transform: translateY(0);
    }
  }
`

export const MarkUI = styled('div')`
  position: absolute;
  top: -2px;
  left: -2px;
  height: 28px;
  width: 28px;
  border-radius: 4px 0px 5px;
  opacity: ${({ markShown }) => (markShown ? '1' : '0')};
  transition: opacity 0.15s;
  will-change: opacity;
  background: ${({ color }) => color};
  z-index: 100;

  .mark-icon {
    color: white;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }

  .TooltipTrigger {
    display: block;
    width: 100%;
    height: 100%;
  }
`
<commit_msg>d300 - CheckmarkCard<commit_after>import styled from 'styled-components'
import { getColor } from '../../styles/utilities/color'
import { d300, d300Effect } from '../../styles/mixins/depth.css'
import { rgba } from '../../utilities/color'

export const CheckMarkCardUI = styled('label')`
  box-sizing: border-box;
  position: relative;
  display: flex;
  align-items: center;
  flex-direction: column;
  justify-content: center;
  width: 100%;
  max-width: ${({ maxWidth }) => (maxWidth ? maxWidth : '100px')};
  height: ${({ height }) => (height ? height : 'auto')};
  min-height: 100px;
  border-radius: 4px;
  ${d300}
  cursor: pointer;

  &:hover {
    ${d300Effect}
    transform: translateY(-2px);
  }

  &.is-checked,
  &.is-focused,
  &.is-focused.is-checked,
  &:focus-within,
  &.is-checked:focus-within {
    box-shadow: 0px 0px 0 2px ${getColor('blue.500')};
  }

  &.is-disabled {
    ${d300}
    color: ${rgba(getColor('charcoal.500'), 0.85)};
    opacity: 0.8;
    cursor: not-allowed;

    &:hover {
      ${d300}
      transform: translateY(0);
    }
  }

  &.with-status {
    cursor: default;
    box-shadow: 0px 0px 0 2px
      ${({ withStatus }) =>
        Boolean(withStatus) ? withStatus.color : 'rgba(0, 0, 0, 0.1)'};

    &:hover {
      transform: translateY(0);
    }
  }
`

export const MarkUI = styled('div')`
  position: absolute;
  top: -2px;
  left: -2px;
  height: 28px;
  width: 28px;
  border-radius: 4px 0px 5px;
  opacity: ${({ markShown }) => (markShown ? '1' : '0')};
  transition: opacity 0.15s;
  will-change: opacity;
  background: ${({ color }) => color};
  z-index: 100;

  .mark-icon {
    color: white;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
  }

  .TooltipTrigger {
    display: block;
    width: 100%;
    height: 100%;
  }
`
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import SideBar from 'react-sidebar';
import map from 'lodash/map';
import './ValidationBar.scss';

import { validateTOS, validateRecord } from '../../../utils/validators';

const styles = {
  sidebar: {
    width: 300,
    height: '100%',
    padding: '10px 0 0 20px',
    backgroundColor: 'white',
    zIndex: '99'
  },
  sidebarLink: {
    display: 'block',
    color: '#757575',
    textDecoration: 'none'
  },
  divider: {
    margin: '8px 0',
    height: 1,
    backgroundColor: '#757575'
  },
  content: {
    height: '100%'
  }
};

export class ValidationBar extends Component {
  constructor (props) {
    super(props);
    this.renderContent = this.renderContent.bind(this);
  }

  generateInvalidAttributes (validate, values) {
    const invalidAttributes = validate(values, this.props.attributeTypes);
    const mappedInvalidAttributes = map(invalidAttributes, (item, index) => (

        {'• '}{this.props.attributeTypes[item].name}

    ));

    if (invalidAttributes.length) {
      return (

          {mappedInvalidAttributes}

      );
    }
  }

  generateAttributeSection (validate, element, index) {
    const invalidAttributes = validate(element, this.props.attributeTypes);

    if (invalidAttributes.length) {
      return (

{element.name}

            {map(invalidAttributes, (item, index) => (

                {'• '}{this.props.attributeTypes[item].name}

            ))}

      );
    }
  }

  renderContent () {
    const { selectedTOS } = this.props;

    const invalidTOSAttributes = this.generateInvalidAttributes(validateTOS, selectedTOS);
    const invalidPhaseAttributes = undefined;
    const invalidActionAttributes = undefined;
    const invalidRecordAttributes = map(selectedTOS.records, (record, index) => (
        this.generateAttributeSection(validateRecord, record, index)
      )
    );

    if (invalidTOSAttributes ||
        invalidPhaseAttributes ||
        invalidActionAttributes ||
        invalidRecordAttributes) {
      return (

Puuttuvat metatiedot

          {invalidTOSAttributes &&

TOS-metatiedot
}
          {invalidTOSAttributes}
          {invalidPhaseAttributes &&

Käsittelyvaiheet
}
          {invalidPhaseAttributes}
          {invalidActionAttributes &&

Toimenpiteet
}
          {invalidActionAttributes}
          {invalidRecordAttributes &&

Asiakirjat
}
          {invalidRecordAttributes}

      );
    }
    return (

    );
  }

  render () {
    const { children, selectedTOS } = this.props;
    const sidebarContent = selectedTOS.id ? this.renderContent() :
;

    return (

        <SideBar
          sidebar={sidebarContent}
          open={this.props.is_open}
          onSetOpen={() => this.props.setValidationVisibility(false)}
          pullRight={true}
          styles={styles}>
          {children}
        </SideBar>

    );
  }
};

ValidationBar.propTypes = {
  attributeTypes: React.PropTypes.object.isRequired,
  children: React.PropTypes.array.isRequired,
  is_open: React.PropTypes.bool.isRequired,
  selectedTOS: React.PropTypes.object.isRequired,
  setValidationVisibility: React.PropTypes.func.isRequired
};

export default ValidationBar;
<commit_msg>Fix extra headers still showing<commit_after>import React, { Component } from 'react';
import SideBar from 'react-sidebar';
import map from 'lodash/map';
import './ValidationBar.scss';

import { validateTOS, validateRecord } from '../../../utils/validators';

const styles = {
  sidebar: {
    width: 300,
    height: '100%',
    padding: '10px 0 0 20px',
    backgroundColor: 'white',
    zIndex: '99'
  },
  sidebarLink: {
    display: 'block',
    color: '#757575',
    textDecoration: 'none'
  },
  divider: {
    margin: '8px 0',
    height: 1,
    backgroundColor: '#757575'
  },
  content: {
    height: '100%'
  }
};

export class ValidationBar extends Component {
  constructor (props) {
    super(props);
    this.renderContent = this.renderContent.bind(this);
  }

  generateInvalidAttributes (validate, values) {
    const invalidAttributes = validate(values, this.props.attributeTypes);
    const mappedInvalidAttributes = map(invalidAttributes, (item, index) => (

        {'• '}{this.props.attributeTypes[item].name}

    ));

    if (invalidAttributes.length) {
      return (

          {mappedInvalidAttributes}

      );
    }
  }

  generateAttributeSection (validate, element, index) {
    const invalidAttributes = validate(element, this.props.attributeTypes);

    if (invalidAttributes.length) {
      return (

{element.name}

            {map(invalidAttributes, (item, index) => (

                {'• '}{this.props.attributeTypes[item].name}

            ))}

      );
    }
  }

  renderContent () {
    const { selectedTOS } = this.props;

    const invalidTOSAttributes = this.generateInvalidAttributes(validateTOS, selectedTOS);
    const invalidPhaseAttributes = [];
    const invalidActionAttributes = [];
    const invalidRecordAttributes = map(selectedTOS.records, (record, index) => (
        this.generateAttributeSection(validateRecord, record, index)
      )
    );

    if (invalidTOSAttributes ||
        invalidPhaseAttributes ||
        invalidActionAttributes ||
        invalidRecordAttributes) {
      return (

Puuttuvat metatiedot

          {invalidTOSAttributes &&

TOS-metatiedot
}
          {invalidTOSAttributes}
          {invalidPhaseAttributes.length > 0 &&

Käsittelyvaiheet
}
          {invalidPhaseAttributes}
          {invalidActionAttributes.length > 0 &&

Toimenpiteet
}
          {invalidActionAttributes}
          {invalidRecordAttributes.length > 0 &&

Asiakirjat
}
          {invalidRecordAttributes}

      );
    }
    return (

    );
  }

  render () {
    const { children, selectedTOS } = this.props;
    const sidebarContent = selectedTOS.id ? this.renderContent() :
;

    return (

        <SideBar
          sidebar={sidebarContent}
          open={this.props.is_open}
          onSetOpen={() => this.props.setValidationVisibility(false)}
          pullRight={true}
          styles={styles}>
          {children}
        </SideBar>

    );
  }
};

ValidationBar.propTypes = {
  attributeTypes: React.PropTypes.object.isRequired,
  children: React.PropTypes.array.isRequired,
  is_open: React.PropTypes.bool.isRequired,
  selectedTOS: React.PropTypes.object.isRequired,
  setValidationVisibility: React.PropTypes.func.isRequired
};

export default ValidationBar;
<|endoftext|>


<commit_before>import { height, offset } from '../../utils/dom.js'
import debounce from '../../utils/debounce.js'
import { getScrollTarget } from '../../utils/scroll.js'
import { listenOpts } from '../../utils/event.js'

import Vue from 'vue'
export default Vue.extend({
  name: 'QInfiniteScroll',

  props: {
    handler: {
      type: Function,
      required: true
    },
    inline: Boolean,
    offset: {
      type: Number,
      default: 0
    }
  },

  data () {
    return {
      index: 0,
      fetching: false,
      working: true
    }
  },

  methods: {
    poll () {
      if (this.fetching || !this.working) {
        return
      }

      let
        containerHeight = height(this.scrollContainer),
        containerBottom = offset(this.scrollContainer).top + containerHeight,
        triggerPosition = offset(this.element).top + height(this.element) - (this.offset || containerHeight)

      if (triggerPosition < containerBottom) {
        this.loadMore()
      }
    },

    loadMore () {
      if (this.fetching || !this.working) {
        return
      }

      this.index++
      this.fetching = true
      this.handler(this.index, stopLoading => {
        this.fetching = false
        if (stopLoading) {
          this.stop()
          return
        }
        if (this.element.closest('body')) {
          this.poll()
        }
      })
    },

    reset () {
      this.index = 0
    },

    resume () {
      this.working = true
      this.scrollContainer.addEventListener('scroll', this.poll, listenOpts.passive)
      this.immediatePoll()
    },

    stop () {
      this.working = false
      this.fetching = false
      this.scrollContainer.removeEventListener('scroll', this.poll, listenOpts.passive)
    }
  },

  mounted () {
    this.$nextTick(() => {
      this.element = this.$refs.content

      this.scrollContainer = this.inline ? this.$el : getScrollTarget(this.$el)
      if (this.working) {
        this.scrollContainer.addEventListener('scroll', this.poll, listenOpts.passive)
      }

      this.poll()
      this.immediatePoll = this.poll
      this.poll = debounce(this.poll, 50)
    })
  },

  beforeDestroy () {
    this.scrollContainer.removeEventListener('scroll', this.poll, listenOpts.passive)
  },

  render (h) {
    return h('div', { staticClass: 'q-infinite-scroll' }, [
      h('div', {
        ref: 'content',
        staticClass: 'q-infinite-scroll-content'
      }, this.$slots.default),

      this.fetching
        ? h('div', { staticClass: 'q-infinite-scroll-message' }, this.$slots.message)
        : null
    ])
  }
})
<commit_msg>feat: Curate QInfiniteScroll<commit_after>import { height, offset } from '../../utils/dom.js'
import debounce from '../../utils/debounce.js'
import { getScrollTarget } from '../../utils/scroll.js'
import { listenOpts } from '../../utils/event.js'

import Vue from 'vue'
export default Vue.extend({
  name: 'QInfiniteScroll',

  props: {
    handler: {
      type: Function,
      required: true
    },
    inline: Boolean,
    offset: {
      type: Number,
      default: 0
    }
  },

  data () {
    return {
      index: 0,
      fetching: false,
      working: true
    }
  },

  methods: {
    poll () {
      if (this.fetching || !this.working) {
        return
      }

      let
        containerHeight = height(this.scrollContainer),
        containerBottom = offset(this.scrollContainer).top + containerHeight,
        triggerPosition = offset(this.element).top + height(this.element) - (this.offset || containerHeight)

      if (triggerPosition < containerBottom) {
        this.loadMore()
      }
    },

    loadMore () {
      if (this.fetching || !this.working) {
        return
      }

      this.index++
      this.fetching = true
      this.handler(this.index, stopLoading => {
        this.fetching = false
        if (stopLoading) {
          this.stop()
          return
        }
        if (this.element.closest('body')) {
          this.poll()
        }
      })
    },

    reset () {
      this.index = 0
    },

    resume () {
      this.working = true
      this.scrollContainer.addEventListener('scroll', this.poll, listenOpts.passive)
      this.immediatePoll()
    },

    stop () {
      this.working = false
      this.fetching = false
      this.scrollContainer.removeEventListener('scroll', this.poll, listenOpts.passive)
    }
  },

  mounted () {
    this.element = this.$refs.content

    this.scrollContainer = this.inline ? this.$el : getScrollTarget(this.$el)
    if (this.working) {
      this.scrollContainer.addEventListener('scroll', this.poll, listenOpts.passive)
    }

    this.poll()
    this.immediatePoll = this.poll
    this.poll = debounce(this.poll, 50)
  },

  beforeDestroy () {
    this.scrollContainer.removeEventListener('scroll', this.poll, listenOpts.passive)
  },

  render (h) {
    return h('div', { staticClass: 'q-infinite-scroll' }, [
      h('div', {
        ref: 'content',
        staticClass: 'q-infinite-scroll__content'
      }, this.$slots.default),

      this.fetching
        ? h('div', { staticClass: 'q-infinite-scroll__message' }, this.$slots.message)
        : null
    ])
  }
})
<|endoftext|>


<commit_before>import React from "react";
import { TOGGLE_NARRATIVE } from "../../actions/types";
import { tabSingle, darkGrey } from "../../globalStyles";

export const renderNarrativeToggle = (dispatch, narrativeIsDisplayed) => {
  return (
    <button
      style={{
        ...tabSingle,
        fontSize: 14,
        backgroundColor: "inherit",
        zIndex: 100,
        position: "absolute",
        right: 20,
        top: -1,
        cursor: "pointer",
        color: darkGrey
      }}
      onClick={() => dispatch({type: TOGGLE_NARRATIVE, display: !narrativeIsDisplayed})}
    >
      {narrativeIsDisplayed ? "explore the data yourself" : "return to the narrative"}
    </button>
  );
};
<commit_msg>Tie narrative toggle to top right of the viewport<commit_after>import React from "react";
import { TOGGLE_NARRATIVE } from "../../actions/types";
import { tabSingle, darkGrey } from "../../globalStyles";

export const renderNarrativeToggle = (dispatch, narrativeIsDisplayed) => {
  return (
    <button
      style={{
        ...tabSingle,
        fontSize: 14,
        backgroundColor: "inherit",
        zIndex: 100,
        position: "fixed",
        right: 5,
        top: -1,
        cursor: "pointer",
        color: darkGrey
      }}
      onClick={() => dispatch({type: TOGGLE_NARRATIVE, display: !narrativeIsDisplayed})}
    >
      {narrativeIsDisplayed ? "explore the data yourself" : "return to the narrative"}
    </button>
  );
};
<|endoftext|>


<commit_before>import PropTypes from 'prop-types';
import React from 'react';
import { push } from 'react-router-redux';
import Title from 'react-title-component';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import Dialog from 'material-ui/Dialog';
import AppButton from '../../common/AppButton';
import messages from '../../../resources/messages';
import { updateTopic, fetchTopicSummary, setTopicNeedsNewSnapshot } from '../../../actions/topicActions';
import { updateFeedback } from '../../../actions/appActions';
import BackLinkingControlBar from '../BackLinkingControlBar';
import Permissioned from '../../common/Permissioned';
import { PERMISSION_TOPIC_WRITE } from '../../../lib/auth';
import TopicForm, { TOPIC_FORM_MODE_EDIT } from './TopicForm';

const localMessages = {
  editTopicTitle: { id: 'topic.edit.title', defaultMessage: 'Edit Topic Settings' },
  editTopicText: { id: 'topic.edit.text', defaultMessage: 'You can update this Topic. If you make changes to the query, media sourcs, or dates, those will be reflected in the next snapshot you run.' },
  editTopic: { id: 'topic.edit', defaultMessage: 'Edit Topic' },
  editTopicCollectionsTitle: { id: 'topic.edit.editTopicCollectionsTitle', defaultMessage: 'Edit Sources and Collections' },
  editTopicCollectionsIntro: { id: 'topic.edit.editTopicCollectionsIntro', defaultMessage: 'The following are the Sources and Collections associated with this topic.' },
  feedback: { id: 'topic.edit.save.feedback', defaultMessage: 'We saved your changes' },
  failed: { id: 'topic.edit.save.failed', defaultMessage: 'Sorry, that didn\'t work!' },
  editRisk: { id: 'topic.edit.save.risk', defaultMessage: 'You have modified this topic and if you proceed you may corrupt your topic!' },
  riskConfirmTitle: { id: 'topic.edit.save.riskConfirmTitle', defaultMessage: 'Warning! Narrowing these query parameters (date range, seed query and/or media) will cause a re-spider - continue at your own risk!' },
  handleRiskDescription: { id: 'topic.edit.save.handleRiskDescription', defaultMessage: 'If you proceed, you could potentially corrupt your topic!' },
  // editRisk: { id: 'topic.edit.save.risk', defaultMessage: 'You have modified this topic and if you proceed you may corrupt your topic!' },
};

class EditTopicContainer extends React.Component {
  state = {
    editConfirmationOpen: false,
  }
  handleCancelSave = () => {
    this.setState({ editConfirmationOpen: false });
  };
  handleConfirmSave = () => {
    const { formData, topicInfo, reallyHandleSave } = this.props;
    this.setState({ editConfirmationOpen: false });
    reallyHandleSave(formData.values, topicInfo);
  };
  handleRequestSave = (values) => {
    const { topicInfo, reallyHandleSave } = this.props;
    if (this.riskModifiedTopicSpidering(values)) {
      this.setState({ editConfirmationOpen: true });
    } else {
      reallyHandleSave(values, topicInfo);
    }
  };
  riskModifiedTopicSpidering = (values) => {
    const { formData } = this.props;
    const modifiedFields = Object.keys(values).filter((f) => {
      if (f === 'solr_seed_query') { // if modified seed query
        return values[f] !== formData.initial[f];
      } else if (f === 'start_date') {
        return values[f] > formData.initial[f]; // if more restrictive dates
      } else if (f === 'end_date') {
        return values[f] < formData.initial[f];
      } else if (f === 'sources') { // if more restrictive sources
        return values[f].length < formData.initial[f].length;
      } else if (f === 'collections') { // if more restrictive sources
        return values[f].length < formData.initial[f].length;
      }
      return false;
    // evalueate further here but for now return true for a dialog info
    });
    if (modifiedFields.length !== undefined && modifiedFields.length > 0) {
      return true;
    }
    return false;
  };
  render() {
    const { topicInfo, topicId } = this.props;
    const { formatMessage } = this.props.intl;
    let initialValues = {};
    let dialogContent = null;
    if (topicInfo) {
      // load sources and collections in a backwards compatible way
      const sources = topicInfo.media ? topicInfo.media.map(t => ({ ...t })) : [];
      const collections = topicInfo.media_tags ? topicInfo.media_tags.map(t => ({ ...t, name: t.label })) : [];
      const sourcesAndCollections = sources.concat(collections);
      initialValues = {
        buttonLabel: formatMessage(messages.save),
        ...topicInfo,
        sourcesAndCollections,
      };
    }
    if (this.state.editConfirmationOpen) {
      const dialogActions = [
        <AppButton
          label={formatMessage(messages.cancel)}
          primary
          onTouchTap={this.handleCancelSave}
        />,
        <AppButton
          label={formatMessage(messages.confirm)}
          primary
          onTouchTap={this.handleConfirmSave}
        />,
      ];
      dialogContent = (
        <Dialog
          title={formatMessage(localMessages.riskConfirmTitle)}
          actions={dialogActions}
          modal
          open={this.state.editConfirmationOpen}
          onRequestClose={this.handleConfirmCancel}
          className="app-dialog"
        >

<FormattedMessage {...localMessages.handleRiskDescription} />

        </Dialog>
      );
    }
    return (

        <BackLinkingControlBar message={messages.backToTopic} linkTo={`/topics/${topicId}/summary`} />
        <Grid>
          <Title render={formatMessage(localMessages.editTopicTitle)} />
          <Row>
            <Col lg={12}>

<FormattedMessage {...localMessages.editTopicTitle} />

<FormattedMessage {...localMessages.editTopicText} />

            </Col>
          </Row>
          {dialogContent}
          <Permissioned onlyTopic={PERMISSION_TOPIC_WRITE}>
            <TopicForm
              topicId={topicId}
              initialValues={initialValues}
              onSubmit={this.handleRequestSave}
              title={formatMessage(localMessages.editTopicCollectionsTitle)}
              intro={formatMessage(localMessages.editTopicCollectionsIntro)}
              mode={TOPIC_FORM_MODE_EDIT}
            />
          </Permissioned>
        </Grid>

    );
  }
}

EditTopicContainer.propTypes = {
  // from context
  location: PropTypes.object.isRequired,
  intl: PropTypes.object.isRequired,
  params: PropTypes.object,
  // from state
  timespan: PropTypes.object,
  filters: PropTypes.object.isRequired,
  topicId: PropTypes.number,
  topicInfo: PropTypes.object,
  formData: PropTypes.object,
  // from dispatch/merge
  reallyHandleSave: PropTypes.func.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  filters: state.topics.selected.filters,
  topicId: parseInt(ownProps.params.topicId, 10),
  topicInfo: state.topics.selected.info,
  timespan: state.topics.selected.timespans.selected,
  snapshots: state.topics.selected.snapshots.list,
  user: state.user,
  formData: state.form.topicForm,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  reallyHandleSave: (values, topicInfo) => {
    const infoToSave = { ...values };   // clone it so we can edit as needed
    infoToSave.is_public = infoToSave.is_public ? 1 : 0;
    infoToSave.is_logogram = infoToSave.is_logogram ? 1 : 0;
    infoToSave.ch_monitor_id = values.ch_monitor_id === undefined ? '' : values.ch_monitor_id;
    if ('sourcesAndCollections' in values) {
      infoToSave['sources[]'] = values.sourcesAndCollections.filter(s => s.media_id).map(s => s.media_id);
      infoToSave['collections[]'] = values.sourcesAndCollections.filter(s => s.tags_id).map(s => s.tags_id);
    } else {
      infoToSave['sources[]'] = '';
      infoToSave['collections[]'] = '';
    }
    return dispatch(updateTopic(ownProps.params.topicId, infoToSave))
      .then((results) => {
        if (results.topics_id) {
          // let them know it worked
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.feedback) }));
          // if the dates changed tell them it needs a new snapshot
          if ((infoToSave.start_date !== topicInfo.start_date) || (infoToSave.end_date !== topicInfo.end_date)) {
            dispatch(setTopicNeedsNewSnapshot());
          }
          // update topic info and redirect back to topic summary
          dispatch(fetchTopicSummary(results.topics_id))
            .then(() => dispatch(push(`/topics/${results.topics_id}/summary`)));
        } else {
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.failed) }));
        }
      }
    );
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      EditTopicContainer
    )
  );
<commit_msg>#756 should remove cached form data<commit_after>import PropTypes from 'prop-types';
import React from 'react';
import { push } from 'react-router-redux';
import Title from 'react-title-component';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl } from 'react-intl';
import { Grid, Row, Col } from 'react-flexbox-grid/lib';
import Dialog from 'material-ui/Dialog';
import AppButton from '../../common/AppButton';
import messages from '../../../resources/messages';
import { updateTopic, fetchTopicSummary, setTopicNeedsNewSnapshot } from '../../../actions/topicActions';
import { updateFeedback } from '../../../actions/appActions';
import BackLinkingControlBar from '../BackLinkingControlBar';
import Permissioned from '../../common/Permissioned';
import { PERMISSION_TOPIC_WRITE } from '../../../lib/auth';
import TopicForm, { TOPIC_FORM_MODE_EDIT } from './TopicForm';

const localMessages = {
  editTopicTitle: { id: 'topic.edit.title', defaultMessage: 'Edit Topic Settings' },
  editTopicText: { id: 'topic.edit.text', defaultMessage: 'You can update this Topic. If you make changes to the query, media sourcs, or dates, those will be reflected in the next snapshot you run.' },
  editTopic: { id: 'topic.edit', defaultMessage: 'Edit Topic' },
  editTopicCollectionsTitle: { id: 'topic.edit.editTopicCollectionsTitle', defaultMessage: 'Edit Sources and Collections' },
  editTopicCollectionsIntro: { id: 'topic.edit.editTopicCollectionsIntro', defaultMessage: 'The following are the Sources and Collections associated with this topic.' },
  feedback: { id: 'topic.edit.save.feedback', defaultMessage: 'We saved your changes' },
  failed: { id: 'topic.edit.save.failed', defaultMessage: 'Sorry, that didn\'t work!' },
  editRisk: { id: 'topic.edit.save.risk', defaultMessage: 'You have modified this topic and if you proceed you may corrupt your topic!' },
  riskConfirmTitle: { id: 'topic.edit.save.riskConfirmTitle', defaultMessage: 'Warning! Narrowing these query parameters (date range, seed query and/or media) will cause a re-spider - continue at your own risk!' },
  handleRiskDescription: { id: 'topic.edit.save.handleRiskDescription', defaultMessage: 'If you proceed, you could potentially corrupt your topic!' },
  // editRisk: { id: 'topic.edit.save.risk', defaultMessage: 'You have modified this topic and if you proceed you may corrupt your topic!' },
};

class EditTopicContainer extends React.Component {
  state = {
    editConfirmationOpen: false,
  }
  handleCancelSave = () => {
    this.setState({ editConfirmationOpen: false });
  };
  handleConfirmSave = () => {
    const { formData, topicInfo, reallyHandleSave } = this.props;
    this.setState({ editConfirmationOpen: false });
    reallyHandleSave(formData.values, topicInfo);
  };
  handleRequestSave = (values) => {
    const { topicInfo, reallyHandleSave } = this.props;
    if (this.riskModifiedTopicSpidering(values)) {
      this.setState({ editConfirmationOpen: true });
    } else {
      reallyHandleSave(values, topicInfo);
    }
  };
  riskModifiedTopicSpidering = (values) => {
    const { formData } = this.props;
    const modifiedFields = Object.keys(values).filter((f) => {
      if (f === 'solr_seed_query') { // if modified seed query
        return values[f] !== formData.initial[f];
      } else if (f === 'start_date') {
        return values[f] > formData.initial[f]; // if more restrictive dates
      } else if (f === 'end_date') {
        return values[f] < formData.initial[f];
      } else if (f === 'sources') { // if more restrictive sources
        return values[f].length < formData.initial[f].length;
      } else if (f === 'collections') { // if more restrictive sources
        return values[f].length < formData.initial[f].length;
      }
      return false;
    // evalueate further here but for now return true for a dialog info
    });
    if (modifiedFields.length !== undefined && modifiedFields.length > 0) {
      return true;
    }
    return false;
  };
  render() {
    const { topicInfo, topicId } = this.props;
    const { formatMessage } = this.props.intl;
    let initialValues = {};
    let dialogContent = null;
    if (topicInfo) {
      // load sources and collections in a backwards compatible way
      const sources = topicInfo.media ? topicInfo.media.map(t => ({ ...t })) : [];
      const collections = topicInfo.media_tags ? topicInfo.media_tags.map(t => ({ ...t, name: t.label })) : [];
      const sourcesAndCollections = sources.concat(collections);
      initialValues = {
        buttonLabel: formatMessage(messages.save),
        ...topicInfo,
        sourcesAndCollections,
      };
    }
    if (this.state.editConfirmationOpen) {
      const dialogActions = [
        <AppButton
          label={formatMessage(messages.cancel)}
          primary
          onTouchTap={this.handleCancelSave}
        />,
        <AppButton
          label={formatMessage(messages.confirm)}
          primary
          onTouchTap={this.handleConfirmSave}
        />,
      ];
      dialogContent = (
        <Dialog
          title={formatMessage(localMessages.riskConfirmTitle)}
          actions={dialogActions}
          modal
          open={this.state.editConfirmationOpen}
          onRequestClose={this.handleConfirmCancel}
          className="app-dialog"
        >

<FormattedMessage {...localMessages.handleRiskDescription} />

        </Dialog>
      );
    }
    return (

        <BackLinkingControlBar message={messages.backToTopic} linkTo={`/topics/${topicId}/summary`} />
        <Grid>
          <Title render={formatMessage(localMessages.editTopicTitle)} />
          <Row>
            <Col lg={12}>

<FormattedMessage {...localMessages.editTopicTitle} />

<FormattedMessage {...localMessages.editTopicText} />

            </Col>
          </Row>
          {dialogContent}
          <Permissioned onlyTopic={PERMISSION_TOPIC_WRITE}>
            <TopicForm
              topicId={topicId}
              initialValues={initialValues}
              onSubmit={this.handleRequestSave}
              title={formatMessage(localMessages.editTopicCollectionsTitle)}
              intro={formatMessage(localMessages.editTopicCollectionsIntro)}
              mode={TOPIC_FORM_MODE_EDIT}
              destroyOnUnmount
            />
          </Permissioned>
        </Grid>

    );
  }
}

EditTopicContainer.propTypes = {
  // from context
  location: PropTypes.object.isRequired,
  intl: PropTypes.object.isRequired,
  params: PropTypes.object,
  // from state
  timespan: PropTypes.object,
  filters: PropTypes.object.isRequired,
  topicId: PropTypes.number,
  topicInfo: PropTypes.object,
  formData: PropTypes.object,
  // from dispatch/merge
  reallyHandleSave: PropTypes.func.isRequired,
};

const mapStateToProps = (state, ownProps) => ({
  filters: state.topics.selected.filters,
  topicId: parseInt(ownProps.params.topicId, 10),
  topicInfo: state.topics.selected.info,
  timespan: state.topics.selected.timespans.selected,
  snapshots: state.topics.selected.snapshots.list,
  user: state.user,
  formData: state.form.topicForm,
});

const mapDispatchToProps = (dispatch, ownProps) => ({
  reallyHandleSave: (values, topicInfo) => {
    const infoToSave = { ...values };   // clone it so we can edit as needed
    infoToSave.is_public = infoToSave.is_public ? 1 : 0;
    infoToSave.is_logogram = infoToSave.is_logogram ? 1 : 0;
    infoToSave.ch_monitor_id = values.ch_monitor_id === undefined ? '' : values.ch_monitor_id;
    if ('sourcesAndCollections' in values) {
      infoToSave['sources[]'] = values.sourcesAndCollections.filter(s => s.media_id).map(s => s.media_id);
      infoToSave['collections[]'] = values.sourcesAndCollections.filter(s => s.tags_id).map(s => s.tags_id);
    } else {
      infoToSave['sources[]'] = '';
      infoToSave['collections[]'] = '';
    }
    return dispatch(updateTopic(ownProps.params.topicId, infoToSave))
      .then((results) => {
        if (results.topics_id) {
          // let them know it worked
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.feedback) }));
          // if the dates changed tell them it needs a new snapshot
          if ((infoToSave.start_date !== topicInfo.start_date) || (infoToSave.end_date !== topicInfo.end_date)) {
            dispatch(setTopicNeedsNewSnapshot());
          }
          // update topic info and redirect back to topic summary
          dispatch(fetchTopicSummary(results.topics_id))
            .then(() => dispatch(push(`/topics/${results.topics_id}/summary`)));
        } else {
          dispatch(updateFeedback({ open: true, message: ownProps.intl.formatMessage(localMessages.failed) }));
        }
      }
    );
  },
});

export default
  injectIntl(
    connect(mapStateToProps, mapDispatchToProps)(
      EditTopicContainer
    )
  );
<|endoftext|>


<commit_before>/*
Copyright 2016 OpenMarket 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 MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';

export default class NetworkDropdown extends React.Component {
    constructor(props) {
        super(props);

        this.dropdownRootElement = null;
        this.ignoreEvent = null;

        this.onInputClick = this.onInputClick.bind(this);
        this.onRootClick = this.onRootClick.bind(this);
        this.onDocumentClick = this.onDocumentClick.bind(this);
        this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
        this.onInputKeyUp = this.onInputKeyUp.bind(this);
        this.collectRoot = this.collectRoot.bind(this);
        this.collectInputTextBox = this.collectInputTextBox.bind(this);

        this.inputTextBox = null;

        let defaultNetwork = null;
        if (
            this.props.config.serverConfig &&
            this.props.config.serverConfig[server] &&
            this.props.config.serverConfig[server].networks &&
            '_matrix' in this.props.config.serverConfig[server].networks
        ) {
            defaultNetwork = '_matrix';
        }

        this.state = {
            expanded: false,
            selectedServer: MatrixClientPeg.getHomeServerName(),
            selectedNetwork: defaultNetwork,
        };
    }

    componentWillMount() {
        // Listen for all clicks on the document so we can close the
        // menu when the user clicks somewhere else
        document.addEventListener('click', this.onDocumentClick, false);

        // fire this now so the defaults can be set up
        this.props.onOptionChange(this.state.selectedServer, this.state.selectedNetwork);
    }

    componentWillUnmount() {
        document.removeEventListener('click', this.onDocumentClick, false);
    }

    componentDidUpdate() {
        if (this.state.expanded && this.inputTextBox) {
            this.inputTextBox.focus();
        }
    }

    onDocumentClick(ev) {
        // Close the dropdown if the user clicks anywhere that isn't
        // within our root element
        if (ev !== this.ignoreEvent) {
            this.setState({
                expanded: false,
            });
        }
    }

    onRootClick(ev) {
        // This captures any clicks that happen within our elements,
        // such that we can then ignore them when they're seen by the
        // click listener on the document handler, ie. not close the
        // dropdown immediately after opening it.
        // NB. We can't just stopPropagation() because then the event
        // doesn't reach the React onClick().
        this.ignoreEvent = ev;
    }

    onInputClick(ev) {
        this.setState({
            expanded: !this.state.expanded,
        });
        ev.preventDefault();
    }

    onMenuOptionClick(server, network, ev) {
        this.setState({
            expanded: false,
            selectedServer: server,
            selectedNetwork: network,
        });
        this.props.onOptionChange(server, network);
    }

    onInputKeyUp(e) {
        if (e.key == 'Enter') {
            this.setState({
                expanded: false,
                selectedServer: e.target.value,
                selectedNetwork: null,
            });
            this.props.onOptionChange(e.target.value, null);
        }
    }

    collectRoot(e) {
        if (this.dropdownRootElement) {
            this.dropdownRootElement.removeEventListener('click', this.onRootClick, false);
        }
        if (e) {
            e.addEventListener('click', this.onRootClick, false);
        }
        this.dropdownRootElement = e;
    }

    collectInputTextBox(e) {
        this.inputTextBox = e;
    }

    _getMenuOptions() {
        const options = [];

        let servers = [];
        if (this.props.config.servers) {
            servers = servers.concat(this.props.config.servers);
        }

        if (servers.indexOf(MatrixClientPeg.getHomeServerName()) == -1) {
            servers.unshift(MatrixClientPeg.getHomeServerName());
        }

        for (const server of servers) {
            options.push(this._makeMenuOption(server, null));
            if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
                for (const network of this.props.config.serverConfig[server].networks) {
                    options.push(this._makeMenuOption(server, network));
                }
            }
        }

        return options;
    }

    _makeMenuOption(server, network, wire_onclick) {
        if (wire_onclick === undefined) wire_onclick = true;
        let icon;
        let name;
        let span_class;

        if (network === null) {
            name = server;
            span_class = 'mx_NetworkDropdown_menu_all';
        } else if (network == '_matrix') {
            name = 'Matrix';
            icon = ;
            span_class = 'mx_NetworkDropdown_menu_network';
        } else {
            name = this.props.config.networkNames[network];
            icon = ;
            span_class = 'mx_NetworkDropdown_menu_network';
        }

        const click_handler = wire_onclick ? this.onMenuOptionClick.bind(this, server, network) : null;

        let key = server;
        if (network !== null) {
            key += '_' + network;
        }

        return

            {icon}
            {name}

;
    }

    render() {
        let current_value;

        let menu;
        if (this.state.expanded) {
            const menu_options = this._getMenuOptions();
            menu =

                {menu_options}

;
            current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
                ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
            />
        } else {
            current_value = this._makeMenuOption(
                this.state.selectedServer, this.state.selectedNetwork, false
            );
        }

        return

                {current_value}

                {menu}

;
    }
}

NetworkDropdown.propTypes = {
    onOptionChange: React.PropTypes.func.isRequired,
    config: React.PropTypes.object,
};

NetworkDropdown.defaultProps = {
    config: {
        networks: [],
    }
};

<commit_msg>Oops, fix undefined variable<commit_after>/*
Copyright 2016 OpenMarket 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 MatrixClientPeg from 'matrix-react-sdk/lib/MatrixClientPeg';

export default class NetworkDropdown extends React.Component {
    constructor(props) {
        super(props);

        this.dropdownRootElement = null;
        this.ignoreEvent = null;

        this.onInputClick = this.onInputClick.bind(this);
        this.onRootClick = this.onRootClick.bind(this);
        this.onDocumentClick = this.onDocumentClick.bind(this);
        this.onMenuOptionClick = this.onMenuOptionClick.bind(this);
        this.onInputKeyUp = this.onInputKeyUp.bind(this);
        this.collectRoot = this.collectRoot.bind(this);
        this.collectInputTextBox = this.collectInputTextBox.bind(this);

        this.inputTextBox = null;

        const server = MatrixClientPeg.getHomeServerName();
        let defaultNetwork = null;
        if (
            this.props.config.serverConfig &&
            this.props.config.serverConfig[server] &&
            this.props.config.serverConfig[server].networks &&
            '_matrix' in this.props.config.serverConfig[server].networks
        ) {
            defaultNetwork = '_matrix';
        }

        this.state = {
            expanded: false,
            selectedServer: server,
            selectedNetwork: defaultNetwork,
        };
    }

    componentWillMount() {
        // Listen for all clicks on the document so we can close the
        // menu when the user clicks somewhere else
        document.addEventListener('click', this.onDocumentClick, false);

        // fire this now so the defaults can be set up
        this.props.onOptionChange(this.state.selectedServer, this.state.selectedNetwork);
    }

    componentWillUnmount() {
        document.removeEventListener('click', this.onDocumentClick, false);
    }

    componentDidUpdate() {
        if (this.state.expanded && this.inputTextBox) {
            this.inputTextBox.focus();
        }
    }

    onDocumentClick(ev) {
        // Close the dropdown if the user clicks anywhere that isn't
        // within our root element
        if (ev !== this.ignoreEvent) {
            this.setState({
                expanded: false,
            });
        }
    }

    onRootClick(ev) {
        // This captures any clicks that happen within our elements,
        // such that we can then ignore them when they're seen by the
        // click listener on the document handler, ie. not close the
        // dropdown immediately after opening it.
        // NB. We can't just stopPropagation() because then the event
        // doesn't reach the React onClick().
        this.ignoreEvent = ev;
    }

    onInputClick(ev) {
        this.setState({
            expanded: !this.state.expanded,
        });
        ev.preventDefault();
    }

    onMenuOptionClick(server, network, ev) {
        this.setState({
            expanded: false,
            selectedServer: server,
            selectedNetwork: network,
        });
        this.props.onOptionChange(server, network);
    }

    onInputKeyUp(e) {
        if (e.key == 'Enter') {
            this.setState({
                expanded: false,
                selectedServer: e.target.value,
                selectedNetwork: null,
            });
            this.props.onOptionChange(e.target.value, null);
        }
    }

    collectRoot(e) {
        if (this.dropdownRootElement) {
            this.dropdownRootElement.removeEventListener('click', this.onRootClick, false);
        }
        if (e) {
            e.addEventListener('click', this.onRootClick, false);
        }
        this.dropdownRootElement = e;
    }

    collectInputTextBox(e) {
        this.inputTextBox = e;
    }

    _getMenuOptions() {
        const options = [];

        let servers = [];
        if (this.props.config.servers) {
            servers = servers.concat(this.props.config.servers);
        }

        if (servers.indexOf(MatrixClientPeg.getHomeServerName()) == -1) {
            servers.unshift(MatrixClientPeg.getHomeServerName());
        }

        for (const server of servers) {
            options.push(this._makeMenuOption(server, null));
            if (this.props.config.serverConfig && this.props.config.serverConfig[server] && this.props.config.serverConfig[server].networks) {
                for (const network of this.props.config.serverConfig[server].networks) {
                    options.push(this._makeMenuOption(server, network));
                }
            }
        }

        return options;
    }

    _makeMenuOption(server, network, wire_onclick) {
        if (wire_onclick === undefined) wire_onclick = true;
        let icon;
        let name;
        let span_class;

        if (network === null) {
            name = server;
            span_class = 'mx_NetworkDropdown_menu_all';
        } else if (network == '_matrix') {
            name = 'Matrix';
            icon = ;
            span_class = 'mx_NetworkDropdown_menu_network';
        } else {
            name = this.props.config.networkNames[network];
            icon = ;
            span_class = 'mx_NetworkDropdown_menu_network';
        }

        const click_handler = wire_onclick ? this.onMenuOptionClick.bind(this, server, network) : null;

        let key = server;
        if (network !== null) {
            key += '_' + network;
        }

        return

            {icon}
            {name}

;
    }

    render() {
        let current_value;

        let menu;
        if (this.state.expanded) {
            const menu_options = this._getMenuOptions();
            menu =

                {menu_options}

;
            current_value = <input type="text" className="mx_NetworkDropdown_networkoption"
                ref={this.collectInputTextBox} onKeyUp={this.onInputKeyUp}
            />
        } else {
            current_value = this._makeMenuOption(
                this.state.selectedServer, this.state.selectedNetwork, false
            );
        }

        return

                {current_value}

                {menu}

;
    }
}

NetworkDropdown.propTypes = {
    onOptionChange: React.PropTypes.func.isRequired,
    config: React.PropTypes.object,
};

NetworkDropdown.defaultProps = {
    config: {
        networks: [],
    }
};

<|endoftext|>


<commit_before>/*
Copyright 2018 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.
*/

const React = require('react');
const ReactDOM = require('react-dom');

// Shamelessly ripped off Modal.js.  There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
// pass in a custom control as the actual body.

const ContainerId = "mx_PersistedElement";

function getOrCreateContainer() {
    let container = document.getElementById(ContainerId);

    if (!container) {
        container = document.createElement("div");
        container.id = ContainerId;
        document.body.appendChild(container);
    }

    return container;
}

// Greater than that of the ContextualMenu
const PE_Z_INDEX = 3000;

/*
 * Class of component that renders its children in a separate ReactDOM virtual tree
 * in a container element appended to document.body.
 *
 * This prevents the children from being unmounted when the parent of PersistedElement
 * unmounts, allowing them to persist.
 *
 * When PE is unmounted, it hides the children using CSS. When mounted or updated, the
 * children are made visible and are positioned into a div that is given the same
 * bounding rect as the parent of PE.
 */
export default class PersistedElement extends React.Component {
    constructor() {
        super();
        this.collectChildContainer = this.collectChildContainer.bind(this);
        this.collectChild = this.collectChild.bind(this);
    }

    collectChildContainer(ref) {
        this.childContainer = ref;
    }

    collectChild(ref) {
        this.child = ref;
        this.updateChild();
    }

    componentDidMount() {
        this.updateChild();
    }

    componentDidUpdate() {
        this.updateChild();
    }

    componentWillUnmount() {
        this.updateChildVisibility(this.child, false);
    }

    updateChild() {
        this.updateChildPosition(this.child, this.childContainer);
        this.updateChildVisibility(this.child, true);
    }

    updateChildVisibility(child, visible) {
        if (!child) return;
        child.style.display = visible ? 'block' : 'none';
    }

    updateChildPosition(child, parent) {
        if (!child || !parent) return;

        const parentRect = parent.getBoundingClientRect();
        Object.assign(child.style, {
            position: 'absolute',
            top: parentRect.top + 'px',
            left: parentRect.left + 'px',
            width: parentRect.width + 'px',
            height: parentRect.height + 'px',
            zIndex: PE_Z_INDEX,
        });
    }

    render() {
        const content =

            {this.props.children}

;

        ReactDOM.render(content, getOrCreateContainer());

        return

;
    }
}

<commit_msg>Fix blank sticker picker<commit_after>/*
Copyright 2018 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.
*/

const React = require('react');
const ReactDOM = require('react-dom');

// Shamelessly ripped off Modal.js.  There's probably a better way
// of doing reusable widgets like dialog boxes & menus where we go and
// pass in a custom control as the actual body.

const ContainerId = "mx_PersistedElement";

function getOrCreateContainer() {
    let container = document.getElementById(ContainerId);

    if (!container) {
        container = document.createElement("div");
        container.id = ContainerId;
        document.body.appendChild(container);
    }

    return container;
}

// Greater than that of the ContextualMenu
const PE_Z_INDEX = 5000;

/*
 * Class of component that renders its children in a separate ReactDOM virtual tree
 * in a container element appended to document.body.
 *
 * This prevents the children from being unmounted when the parent of PersistedElement
 * unmounts, allowing them to persist.
 *
 * When PE is unmounted, it hides the children using CSS. When mounted or updated, the
 * children are made visible and are positioned into a div that is given the same
 * bounding rect as the parent of PE.
 */
export default class PersistedElement extends React.Component {
    constructor() {
        super();
        this.collectChildContainer = this.collectChildContainer.bind(this);
        this.collectChild = this.collectChild.bind(this);
    }

    collectChildContainer(ref) {
        this.childContainer = ref;
    }

    collectChild(ref) {
        this.child = ref;
        this.updateChild();
    }

    componentDidMount() {
        this.updateChild();
    }

    componentDidUpdate() {
        this.updateChild();
    }

    componentWillUnmount() {
        this.updateChildVisibility(this.child, false);
    }

    updateChild() {
        this.updateChildPosition(this.child, this.childContainer);
        this.updateChildVisibility(this.child, true);
    }

    updateChildVisibility(child, visible) {
        if (!child) return;
        child.style.display = visible ? 'block' : 'none';
    }

    updateChildPosition(child, parent) {
        if (!child || !parent) return;

        const parentRect = parent.getBoundingClientRect();
        Object.assign(child.style, {
            position: 'absolute',
            top: parentRect.top + 'px',
            left: parentRect.left + 'px',
            width: parentRect.width + 'px',
            height: parentRect.height + 'px',
            zIndex: PE_Z_INDEX,
        });
    }

    render() {
        const content =

            {this.props.children}

;

        ReactDOM.render(content, getOrCreateContainer());

        return

;
    }
}

<|endoftext|>


<commit_before>/* @flow */
import React, { PropTypes, Component } from 'react';
import { Button, Input, Row, Col } from 'react-bootstrap';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { generateValidation } from '@mcalthrop/redux-form-validation';
import TextInput from 'components/FormFields/TextInput';
import HorizontalRadioGroup from 'components/FormFields/HorizontalRadioGroup';
import DropDown from 'components/FormFields/DropDown';
import FormErrorMessages from 'components/FormFields/FormErrorMessages';
import validations from './ProfileEditForm.validations';
import { reduxForm } from 'redux-form';
import { messages } from './ProfileEditForm.i18n';
import styles from './ProfileEditForm.scss';
import { autobind } from 'core-decorators';

const MALE: string = 'male';
const FEMALE: string = 'female';

class ProfileEditForm extends React.Component {
  static propTypes = {
    fields: PropTypes.object.isRequired,
    intl: intlShape.isRequired,
    values: PropTypes.object.isRequired,
    pristine: PropTypes.bool.isRequired,
    invalid: PropTypes.bool.isRequired,
    resetForm: PropTypes.func.isRequired,
    user: PropTypes.object.isRequired,
    handleUpdate: PropTypes.func.isRequired,
  };

  @autobind
  onUpdateClick() {
    this.props.handleUpdate(Object.assign({}, this.props.user, this.props.values));
  }

  @autobind
  onResetClick() {
    this.props.resetForm();
  }

  isUpdateButtonDisabled(): boolean {
    return this.props.pristine || this.props.invalid;
  }

  isResetButtonDisabled(): boolean {
    return this.props.pristine;
  }

  render(): Component {
    const {
      fields: {
        givenName,
        familyName,
        nickname,
        email,
        emailVerified,
        age,
        gender,
        locale,
        notes,
        },
      } = this.props;
    const { formatMessage } = this.props.intl;
    const genderValues = [
      {
        label: formatMessage(messages.gender.male.label),
        value: MALE,
      },
      {
        label: formatMessage(messages.gender.female.label),
        value: FEMALE,
      },
    ];

    // in a real app, the locales would be populated via service call:
    const locales: string[] = ['en-GB', 'en-AU', 'es-ES', 'es-CR', 'es-NI'];

    return (

        <form onSubmit={this.onUpdateClick}>
          <Row>
            <Col sm={2}>

            </Col>
            <Col sm={5}>
              <TextInput field={givenName} placeholder={formatMessage(messages.givenName.placeholder)}>
                <FormErrorMessages field={givenName} minLength={validations.givenName.minLength} />
              </TextInput>
              <TextInput field={familyName} placeholder={formatMessage(messages.familyName.placeholder)}>
                <FormErrorMessages field={familyName} minLength={validations.familyName.minLength} />
              </TextInput>
              <TextInput field={nickname} placeholder={formatMessage(messages.nickname.placeholder)}>
                <FormErrorMessages field={nickname} maxLength={validations.nickname.maxLength} />
              </TextInput>
              <TextInput field={email} type="email" placeholder={formatMessage(messages.email.placeholder)}>
                <FormErrorMessages field={email} />
              </TextInput>
              <Input type="checkbox" label={formatMessage(messages.emailVerified.label)} {...emailVerified} />
            </Col>
            <Col sm={5}>
              <TextInput field={age} type="number" placeholder={formatMessage(messages.age.placeholder)}>
                <FormErrorMessages field={age} min={validations.age.min} max={validations.age.max} />
              </TextInput>
              <HorizontalRadioGroup field={gender} values={genderValues} />
              <DropDown label={formatMessage(messages.locale.label)} field={locale} values={locales} />
              <Input type="textarea" label={formatMessage(messages.notes.label)} {...notes} />
            </Col>
          </Row>
          <Row>
            <Col sm={2} />
            <Col sm={10}>
              <Button
                bsStyle="primary"
                onClick={this.onUpdateClick}
                disabled={this.isUpdateButtonDisabled()}
              >
                <FormattedMessage {...messages.save.label} />
              </Button>
              {
                // Need to include the preceding non-breaking space, because when React renders the HTML,
                // there is no gap in the markup between the buttons - which results in the buttons being
                // flush up against each other.
              }
              <Button
                bsStyle="default"
                onClick={this.onResetClick}
                disabled={this.isResetButtonDisabled()}
              >
                <FormattedMessage {...messages.reset.label} />
              </Button>
            </Col>
          </Row>
        </form>

    );
  }
}

const reduxFormConfig: Object = {
  form: 'editProfile',
  ...generateValidation(validations),
};

const mapStateToProps = (state, props) => ({ initialValues: props.user });

export default reduxForm(reduxFormConfig, mapStateToProps)(injectIntl(ProfileEditForm));
<commit_msg>Adjust layout of Age and Gender<commit_after>/* @flow */
import React, { PropTypes, Component } from 'react';
import { Button, Input, Row, Col } from 'react-bootstrap';
import { FormattedMessage, intlShape, injectIntl } from 'react-intl';
import { generateValidation } from '@mcalthrop/redux-form-validation';
import TextInput from 'components/FormFields/TextInput';
import HorizontalRadioGroup from 'components/FormFields/HorizontalRadioGroup';
import DropDown from 'components/FormFields/DropDown';
import FormErrorMessages from 'components/FormFields/FormErrorMessages';
import validations from './ProfileEditForm.validations';
import { reduxForm } from 'redux-form';
import { messages } from './ProfileEditForm.i18n';
import styles from './ProfileEditForm.scss';
import { autobind } from 'core-decorators';

const MALE: string = 'male';
const FEMALE: string = 'female';

class ProfileEditForm extends React.Component {
  static propTypes = {
    fields: PropTypes.object.isRequired,
    intl: intlShape.isRequired,
    values: PropTypes.object.isRequired,
    pristine: PropTypes.bool.isRequired,
    invalid: PropTypes.bool.isRequired,
    resetForm: PropTypes.func.isRequired,
    user: PropTypes.object.isRequired,
    handleUpdate: PropTypes.func.isRequired,
  };

  @autobind
  onUpdateClick() {
    this.props.handleUpdate(Object.assign({}, this.props.user, this.props.values));
  }

  @autobind
  onResetClick() {
    this.props.resetForm();
  }

  isUpdateButtonDisabled(): boolean {
    return this.props.pristine || this.props.invalid;
  }

  isResetButtonDisabled(): boolean {
    return this.props.pristine;
  }

  render(): Component {
    const {
      fields: {
        givenName,
        familyName,
        nickname,
        email,
        emailVerified,
        age,
        gender,
        locale,
        notes,
        },
      } = this.props;
    const { formatMessage } = this.props.intl;
    const genderValues = [
      {
        label: formatMessage(messages.gender.male.label),
        value: MALE,
      },
      {
        label: formatMessage(messages.gender.female.label),
        value: FEMALE,
      },
    ];

    // in a real app, the locales would be populated via service call:
    const locales: string[] = ['en-GB', 'en-AU', 'es-ES', 'es-CR', 'es-NI'];

    return (

        <form onSubmit={this.onUpdateClick}>
          <Row>
            <Col sm={2}>

            </Col>
            <Col sm={5}>
              <TextInput field={givenName} placeholder={formatMessage(messages.givenName.placeholder)}>
                <FormErrorMessages field={givenName} minLength={validations.givenName.minLength} />
              </TextInput>
              <TextInput field={familyName} placeholder={formatMessage(messages.familyName.placeholder)}>
                <FormErrorMessages field={familyName} minLength={validations.familyName.minLength} />
              </TextInput>
              <TextInput field={nickname} placeholder={formatMessage(messages.nickname.placeholder)}>
                <FormErrorMessages field={nickname} maxLength={validations.nickname.maxLength} />
              </TextInput>
              <TextInput field={email} type="email" placeholder={formatMessage(messages.email.placeholder)}>
                <FormErrorMessages field={email} />
              </TextInput>
              <Input type="checkbox" label={formatMessage(messages.emailVerified.label)} {...emailVerified} />
            </Col>
            <Col sm={5}>
              <Row>
                <Col sm={3}>
                  <TextInput field={age} type="number" placeholder={formatMessage(messages.age.placeholder)}>
                    <FormErrorMessages field={age} min={validations.age.min} max={validations.age.max}/>
                  </TextInput>
                </Col>
                <Col sm={9}>
                  <HorizontalRadioGroup field={gender} values={genderValues} />
                </Col>
              </Row>
              <DropDown label={formatMessage(messages.locale.label)} field={locale} values={locales} />
              <Input type="textarea" label={formatMessage(messages.notes.label)} {...notes} />
            </Col>
          </Row>
          <Row>
            <Col sm={2} />
            <Col sm={10}>
              <Button
                bsStyle="primary"
                onClick={this.onUpdateClick}
                disabled={this.isUpdateButtonDisabled()}
              >
                <FormattedMessage {...messages.save.label} />
              </Button>
              {
                // Need to include the preceding non-breaking space, because when React renders the HTML,
                // there is no gap in the markup between the buttons - which results in the buttons being
                // flush up against each other.
              }
              <Button
                bsStyle="default"
                onClick={this.onResetClick}
                disabled={this.isResetButtonDisabled()}
              >
                <FormattedMessage {...messages.reset.label} />
              </Button>
            </Col>
          </Row>
        </form>

    );
  }
}

const reduxFormConfig: Object = {
  form: 'editProfile',
  ...generateValidation(validations),
};

const mapStateToProps = (state, props) => ({ initialValues: props.user });

export default reduxForm(reduxFormConfig, mapStateToProps)(injectIntl(ProfileEditForm));
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  Text,
  View,
  StyleSheet,
  Dimensions,
} from 'react-native';
import Carousel from 'react-native-looped-carousel';
import Modal from 'react-native-modalbox';
import RNFetchBlob from 'react-native-fetch-blob';
import Toast from 'react-native-simple-toast';
import * as Progress from 'react-native-progress';
import { unzip } from 'react-native-zip-archive';
import RNFS from 'react-native-fs';

import AsyncStorageHelper from '../../../utils/AsyncStorageHelper';

const randomColor = require('randomcolor'); // import the script

const DIR = RNFetchBlob.fs.dirs;
const LEETCODE_PATH = '/growth-leetcode-api-master';
const LeetCodeUrl = 'https://github.com/phodal/growth-leetcode-api/archive/master.zip';
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
  wrapper: {
    paddingTop: 50,
    flex: 1,
  },
  text: {
    paddingBottom: 50,
  },
  modal: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 300,
    width: 300,
  },
});

function getRandomColor() {
  return randomColor({
    luminosity: 'light',
    hue: 'random',
  });
}

export default class LeetCodeView extends Component {
  static componentName = 'LeetCodeView';
  static modal = null;

  static unzipFiles(res) {
    unzip(res.path(), `${DIR.DocumentDir}`).then(() => {
      AsyncStorageHelper.set('leetcode.unzip', 'true');
    }).catch((err) => {
      console.log(`解压失败 ${err}`);
      Toast.show(`解压失败 ${err}`);
    });
  }

  constructor(props) {
    super(props);

    this.state = {
      progress: 0.001,
      questions: null,
      size: { width, height },
      hasDownloaded: false,
    };
  }

  componentWillMount() {
    AsyncStorageHelper.get('leetcode.index', (err, result) => {
      let index = 0;
      if (result !== null) {
        index = parseInt(result, 10);
      }

      this.setState({
        index,
      });
    });

    AsyncStorageHelper.get('leetcode.downloaded', (err, result) => {
      if (result === 'true') {
        this.setState({
          hasDownloaded: true,
        });
      }
    });
  }

  componentDidMount() {
    const that = this;
    AsyncStorageHelper.get('leetcode.downloaded', (err, result) => {
      if (result !== 'true') {
        this.modal.open();
        this.fetchFile(LeetCodeUrl, 'leetcode');
      } else {
        AsyncStorageHelper.get('leetcode.questions', (error, questions) => {
          if (questions) {
            that.setState({
              questions: JSON.parse(questions),
            });
          } else {
            that.loadQuestionsToDB();
          }
        });
      }
    });
  }

  onLayoutDidChange = (e) => {
    const layout = e.nativeEvent.layout;
    this.setState({ size: { width: layout.width, height: layout.height } });
  };

  loadQuestionsToDB() {
    const leetCodeDir = DIR.DocumentDir.concat(`${LEETCODE_PATH}/api.json`);
    console.log(leetCodeDir);

    RNFS.readFile(leetCodeDir, 'utf8')
    .then((json) => {
      this.setState({
        questions: JSON.parse(json),
      });

      AsyncStorageHelper.set('leetcode.questions', json);
    }).catch((error) => {
      Toast.show(error);
      AsyncStorageHelper.set('leetcode.downloaded', 'false');
    });
  }

  fetchFile(url, fileName) {
    RNFetchBlob
    .config({
      path: `${DIR.DocumentDir}/${fileName}.zip`,
    })
    .fetch('GET', url, {})
    .progress((received, total) => {
      const progressWithoutRound = received / total;

      let progress = Math.round(progressWithoutRound * 1e2) / 1e2;
      if (progress > 1) {
        progress = 1;
      }
      this.setState({ progress });
    })
    .then((res) => {
      const now = new Date();
      console.log(`${now} 下载成功`);
      console.log(res.path());

      LeetCodeView.unzipFiles(res);
      this.loadQuestionsToDB();

      AsyncStorageHelper.set('leetcode.downloaded', 'true');
      this.setState({ hasDownloaded: true });
    })
    .catch((err) => {
      Toast.show(`下载失败 ${err}`);
    });
  }

  render() {
    const hasDownloaded = this.state.hasDownloaded;
    const questions = this.state.questions;

    if (!hasDownloaded || !questions) {
      return (
        <Modal
          style={[styles.modal]}
          position={'center'}
          ref={(modal) => { this.modal = modal; }}
        >
          <Text style={styles.text}>正在为您下载</Text>
          <Progress.Circle
            style={styles.progress}
            progress={this.state.progress}
            indeterminate
            size={80}
          />
        </Modal>
      );
    }

    return (
      <View style={{ flex: 1 }} onLayout={this.onLayoutDidChange}>
        <Carousel
          delay={2000}
          style={this.state.size}
          autoplay={false}
          pageInfo={false}
          onAnimateNextPage={p => console.log(p)}
        >
          <View style={[{ backgroundColor: getRandomColor() }, this.state.size]}>
            <Text>1</Text>
          </View>
          <View style={[{ backgroundColor: getRandomColor() }, this.state.size]}>
            <Text>2</Text>
          </View>
          <View style={[{ backgroundColor: getRandomColor() }, this.state.size]}>
            <Text>3</Text>
          </View>
        </Carousel>
      </View>
    );
  }
}
<commit_msg>[LeetCode] fix load db issue<commit_after>import React, { Component } from 'react';
import {
  Text,
  View,
  StyleSheet,
  Dimensions,
} from 'react-native';
import Carousel from 'react-native-looped-carousel';
import Modal from 'react-native-modalbox';
import RNFetchBlob from 'react-native-fetch-blob';
import Toast from 'react-native-simple-toast';
import * as Progress from 'react-native-progress';
import { unzip } from 'react-native-zip-archive';
import RNFS from 'react-native-fs';

import AsyncStorageHelper from '../../../utils/AsyncStorageHelper';

const randomColor = require('randomcolor'); // import the script

const DIR = RNFetchBlob.fs.dirs;
const LEETCODE_PATH = '/growth-leetcode-api-master';
const LeetCodeUrl = 'https://github.com/phodal/growth-leetcode-api/archive/master.zip';
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
  wrapper: {
    paddingTop: 50,
    flex: 1,
  },
  text: {
    paddingBottom: 50,
  },
  modal: {
    justifyContent: 'center',
    alignItems: 'center',
    height: 300,
    width: 300,
  },
});

function getRandomColor() {
  return randomColor({
    luminosity: 'light',
    hue: 'random',
  });
}

export default class LeetCodeView extends Component {
  static componentName = 'LeetCodeView';
  static modal = null;

  constructor(props) {
    super(props);

    this.state = {
      progress: 0.001,
      questions: null,
      size: { width, height },
      hasDownloaded: false,
    };
  }

  componentWillMount() {
    AsyncStorageHelper.get('leetcode.index', (err, result) => {
      let index = 0;
      if (result !== null) {
        index = parseInt(result, 10);
      }

      this.setState({
        index,
      });
    });

    AsyncStorageHelper.get('leetcode.downloaded', (err, result) => {
      if (result === 'true') {
        this.setState({
          hasDownloaded: true,
        });
      }
    });
  }

  componentDidMount() {
    const that = this;
    AsyncStorageHelper.get('leetcode.downloaded', (err, result) => {
      if (result !== 'true') {
        this.modal.open();
        this.fetchFile(LeetCodeUrl, 'leetcode');
      } else {
        AsyncStorageHelper.get('leetcode.questions', (error, questions) => {
          if (questions) {
            that.setState({
              questions: JSON.parse(questions),
            });
          } else {
            that.loadQuestionsToDB();
          }
        });
      }
    });
  }

  onLayoutDidChange = (e) => {
    const layout = e.nativeEvent.layout;
    this.setState({ size: { width: layout.width, height: layout.height } });
  };

  loadQuestionsToDB() {
    const leetCodeDir = DIR.DocumentDir.concat(`${LEETCODE_PATH}/api.json`);
    console.log(leetCodeDir);

    RNFS.readFile(leetCodeDir, 'utf8')
    .then((json) => {
      this.setState({
        questions: JSON.parse(json),
      });

      AsyncStorageHelper.set('leetcode.questions', json);
    }).catch((error) => {
      Toast.show(error);
      AsyncStorageHelper.set('leetcode.downloaded', 'false');
    });
  }

  fetchFile(url, fileName) {
    RNFetchBlob
    .config({
      path: `${DIR.DocumentDir}/${fileName}.zip`,
    })
    .fetch('GET', url, {})
    .progress((received, total) => {
      const progressWithoutRound = received / total;

      let progress = Math.round(progressWithoutRound * 1e2) / 1e2;
      if (progress > 1) {
        progress = 1;
      }
      this.setState({ progress });
    })
    .then((res) => {
      const now = new Date();
      console.log(`${now} 下载成功`);
      console.log(res.path());

      this.unzipFiles(res);

      AsyncStorageHelper.set('leetcode.downloaded', 'true');
      this.setState({ hasDownloaded: true });
    })
    .catch((err) => {
      Toast.show(`下载失败 ${err}`);
    });
  }

  unzipFiles(res) {
    unzip(res.path(), `${DIR.DocumentDir}`).then(() => {
      this.loadQuestionsToDB();
      AsyncStorageHelper.set('leetcode.unzip', 'true');
    }).catch((err) => {
      console.log(`解压失败 ${err}`);
      Toast.show(`解压失败 ${err}`);
    });
  }

  render() {
    const hasDownloaded = this.state.hasDownloaded;
    const questions = this.state.questions;

    if (!hasDownloaded || !questions) {
      return (
        <Modal
          style={[styles.modal]}
          position={'center'}
          ref={(modal) => { this.modal = modal; }}
        >
          <Text style={styles.text}>正在为您下载</Text>
          <Progress.Circle
            style={styles.progress}
            progress={this.state.progress}
            indeterminate
            size={80}
          />
        </Modal>
      );
    }

    return (
      <View style={{ flex: 1 }} onLayout={this.onLayoutDidChange}>
        <Carousel
          delay={2000}
          style={this.state.size}
          autoplay={false}
          pageInfo={false}
          onAnimateNextPage={p => console.log(p)}
        >
          <View style={[{ backgroundColor: getRandomColor() }, this.state.size]}>
            <Text>1</Text>
          </View>
          <View style={[{ backgroundColor: getRandomColor() }, this.state.size]}>
            <Text>2</Text>
          </View>
          <View style={[{ backgroundColor: getRandomColor() }, this.state.size]}>
            <Text>3</Text>
          </View>
        </Carousel>
      </View>
    );
  }
}
<|endoftext|>