<commit_before>// @todo: merge this with ReplyFeedback
import { useState } from 'react';
import { makeStyles, withStyles } from '@material-ui/core/styles';
import {
  Tabs,
  Tab,
  Box,
  Popover,
  Typography,
  SvgIcon,
} from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import gql from 'graphql-tag';
import { useMutation } from '@apollo/react-hooks';
import { t } from 'ttag';
import { format, formatDistanceToNow } from 'lib/dateWithLocale';
import isValid from 'date-fns/isValid';
import { TYPE_NAME } from '../constants/replyType';
import Avatar from 'components/AppLayout/Widgets/Avatar';
import TextExpansion from './TextExpansion';
import Feedback from './Feedback';
import cx from 'clsx';

const ThumbUpIcon = props => (
  <SvgIcon {...props} viewBox="0 0 17 15">
    <path d="M1 13.8672H3.65455V6.51612H1V13.8672ZM15.6 7.1287C15.6 6.45486 15.0027 5.90353 14.2727 5.90353H10.0852L10.7156 3.10401L10.7355 2.90798C10.7355 2.65682 10.6227 2.42403 10.4435 2.25863L9.74009 1.61542L5.37336 5.65237C5.12782 5.8729 4.98182 6.17919 4.98182 6.51612V12.642C4.98182 13.3158 5.57909 13.8672 6.30909 13.8672H12.2818C12.8326 13.8672 13.3038 13.5609 13.5029 13.1198L15.5071 8.80107C15.5668 8.66017 15.6 8.51315 15.6 8.35388V7.1287Z" />
  </SvgIcon>
);

const ThumbDownIcon = props => (
  <SvgIcon {...props} viewBox="0 0 17 15">
    <path d="M10.2909 1.46155H4.31818C3.76736 1.46155 3.29618 1.76784 3.09709 2.2089L1.09291 6.52765C1.03318 6.66854 1 6.81556 1 6.97483V8.20001C1 8.87386 1.59727 9.42518 2.32727 9.42518H6.51482L5.88436 12.2247L5.86445 12.4207C5.86445 12.6719 5.97727 12.9047 6.15646 13.0701L6.85991 13.7133L11.2333 9.67635C11.4722 9.45581 11.6182 9.14952 11.6182 8.8126V2.68672C11.6182 2.01288 11.0209 1.46155 10.2909 1.46155ZM12.9455 1.46155V8.8126H15.6V1.46155H12.9455Z" />
  </SvgIcon>
);

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    flex: '1 0 auto',
  },
  replyType: {
    color: ({ replyType }) => {
      switch (replyType) {
        case 'OPINIONATED':
          return '#2079F0';
        case 'NOT_RUMOR':
          return '#00B172';
        case 'RUMOR':
          return '#FB5959';
        default:
          return theme.palette.common.black;
      }
    },
  },
  status: {
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    flexWrap: 'wrap',
  },
  actions: {
    display: 'flex',
    order: 2,
    [theme.breakpoints.up('md')]: {
      order: 1,
    },
  },
  createdAt: {
    color: theme.palette.secondary[200],
    flex: '1 0 100%',
    order: 1,
    padding: '8px 0',
    [theme.breakpoints.up('md')]: {
      flex: '0 1 auto',
      order: 2,
    },
  },
  vote: {
    display: 'flex',
    alignItems: 'center',
    borderRadius: 45,
    padding: '1px 8px',
    marginRight: 3,
    outline: 'none',
    cursor: 'pointer',
    border: `1px solid ${theme.palette.secondary[100]}`,
    color: theme.palette.secondary[100],
    background: theme.palette.common.white,
    [theme.breakpoints.up('md')]: {
      padding: '4px 18px',
      marginRight: 10,
    },
  },
  voted: {
    border: `1px solid ${theme.palette.primary[500]}`,
    color: theme.palette.primary[500],
  },
  buttonGroup: {
    display: 'flex',
    alignItems: 'center',
    whiteSpace: 'nowrap',
    '& $vote': {
      marginRight: 0,
      display: 'inline-flex',
      color: theme.palette.secondary[200],
      background: theme.palette.secondary[50],
      '& img': {
        paddingLeft: 6,
      },
      '&:first-child': {
        borderTopRightRadius: 0,
        borderBottomRightRadius: 0,
        borderRight: 0,
        paddingRight: 0,
      },
      '&:last-child': {
        borderTopLeftRadius: 0,
        borderBottomLeftRadius: 0,
      },
      '&:not(:first-child):not(:last-child)': {
        borderRadius: 0,
        borderLeft: 0,
        borderRight: 0,
      },
    },
  },
  popover: {
    position: 'relative',
    width: 420,
    maxWidth: '90vw',
    padding: 32,
  },
  closeButton: {
    background: theme.palette.common.white,
    cursor: 'pointer',
    position: 'absolute',
    right: 6,
    top: 10,
    border: 'none',
    outline: 'none',
    color: theme.palette.secondary[100],
  },
  feedbacks: {
    marginTop: 16,
    maxHeight: 300,
    overflow: 'auto',
  },
  popupTitle: {
    fontSize: 18,
    marginBottom: 24,
  },
  textarea: {
    padding: 15,
    width: '100%',
    borderRadius: 8,
    border: `1px solid ${theme.palette.secondary[100]}`,
    outline: 'none',
    '&:focus': {
      border: `1px solid ${theme.palette.primary[500]}`,
    },
  },
  textCenter: { textAlign: 'center' },
  sendButton: {
    outline: 'none',
    border: 'none',
    marginTop: 10,
    padding: '10px 25px',
    background: theme.palette.primary[500],
    color: theme.palette.common.white,
    cursor: 'pointer',
    borderRadius: 30,
    '&:disabled': {
      opacity: 0.7,
      cursor: 'not-allowed',
    },
  },
}));

const CustomTab = withStyles({
  root: {
    position: 'relative',
    minHeight: 0,
  },
  wrapper: {
    '& > svg': {
      position: 'absolute',
      left: 0,
    },
  },
})(Tab);

const ArticleReplyFeedbackForUser = gql`
  fragment ArticleReplyFeedbackForUser on ArticleReply {
    articleId
    replyId
    ownVote
  }
`;

const ArticleReplyFeedbackData = gql`
  fragment ArticleReplyFeedbackData on ArticleReply {
    ...ArticleReplyFeedbackForUser
    positiveFeedbackCount
    negativeFeedbackCount
    ownVote
    feedbacks {
      ...Feedback
    }
  }
  ${Feedback.fragments.Feedback}
  ${ArticleReplyFeedbackForUser}
`;

const CREATE_REPLY_FEEDBACK = gql`
  mutation CreateOrUpdateArticleReplyFeedback(
    $articleId: String!
    $replyId: String!
    $vote: FeedbackVote!
    $comment: String
  ) {
    CreateOrUpdateArticleReplyFeedback(
      articleId: $articleId
      replyId: $replyId
      vote: $vote
      comment: $comment
    ) {
      ...ArticleReplyFeedbackData
    }
  }
  ${ArticleReplyFeedbackData}
`;
/**
 *
 * @param {ReplyItem} props.reply - see ReplyItem in GraphQL fragment
 * @param {boolean} showUser
 */
function ReplyItem({
  articleId,
  replyId,
  reply,
  feedbacks,
  positiveFeedbackCount,
  negativeFeedbackCount,
  ownVote,
}) {
  const [createReplyFeedback, { loading: updatingReplyFeedback }] = useMutation(
    CREATE_REPLY_FEEDBACK,
    {
      onCompleted() {
        closeVotePopover();
        setReason('');
      },
    }
  );

  const [vote, setVote] = useState(null);
  const [reason, setReason] = useState('');
  const [reasonsPopoverAnchorEl, setReasonsPopoverAnchorEl] = useState(null);
  const [votePopoverAnchorEl, setVotePopoverAnchorEl] = useState(null);
  const [tab, setTab] = useState(0);
  const { user, text, type: replyType } = reply;
  const createdAt = new Date(reply.createdAt);
  const timeAgoStr = formatDistanceToNow(createdAt);

  const openReasonsPopover = event => {
    setReasonsPopoverAnchorEl(event.currentTarget);
  };

  const closeReasonsPopover = () => {
    setReasonsPopoverAnchorEl(null);
  };

  const openVotePopover = (event, value) => {
    setVotePopoverAnchorEl(event.currentTarget);
    setVote(value);
  };

  const closeVotePopover = () => {
    setVotePopoverAnchorEl(null);
    setVote(null);
  };

  const classes = useStyles({ replyType });
  const userName = user?.name ?? t`Anonymoys`;

  return (

      <Box p="24px" display={['none', 'none', 'block']}>
        <Avatar user={user} size={72} />
        {/*

{TYPE_ICON[replyType]}

        */}
      </Box>
      <Box pr="14px" display={['block', 'block', 'none']}>
        <Avatar user={user} size={30} />
        {/*

{TYPE_ICON[replyType]}

        */}
      </Box>
      <Box py="12px" flexGrow={1}>

{t`${userName} consider this ${TYPE_NAME[replyType]}`}

        <TextExpansion content={text} />

            <button
              className={cx(
                classes.vote,
                ownVote === 'UPVOTE' && classes.voted
              )}
              type="button"
              onClick={e => openVotePopover(e, 'UPVOTE')}
            >
              <ThumbUpIcon />
            </button>
            <button
              className={cx(
                classes.vote,
                ownVote === 'DOWNVOTE' && classes.voted
              )}
              type="button"
              onClick={e => openVotePopover(e, 'DOWNVOTE')}
            >
              <ThumbDownIcon />
            </button>

              <button className={classes.vote} type="button">
                {positiveFeedbackCount}
                <ThumbUpIcon style={{ fontSize: 16, margin: '0 2px' }} />
              </button>
              <button className={classes.vote} type="button">
                {negativeFeedbackCount}
                <ThumbDownIcon style={{ fontSize: 16, margin: '0 2px' }} />
              </button>
              <button
                className={classes.vote}
                type="button"
                onClick={openReasonsPopover}
              >
                {t`See Reasons`}
              </button>

          {isValid(createdAt) && (
            {t`${timeAgoStr} ago`}
          )}

      </Box>
      <Popover
        id={`reply-reasons-${reply.id}`}
        open={!!reasonsPopoverAnchorEl}
        anchorEl={reasonsPopoverAnchorEl}
        onClose={closeReasonsPopover}
        anchorOrigin={{
          vertical: 'top',
          horizontal: 'center',
        }}
        classes={{ paper: classes.popover }}
      >
        <button
          type="button"
          className={classes.closeButton}
          onClick={closeReasonsPopover}
        >
          <CloseIcon />
        </button>
        {text}
        <Tabs
          value={tab}
          onChange={(e, value) => setTab(value)}
          indicatorColor="primary"
          textColor="primary"
          variant="fullWidth"
        >
          <CustomTab
            icon={<ThumbUpIcon />}
            label={t`Helpful ${positiveFeedbackCount}`}
          />
          <CustomTab
            icon={<ThumbDownIcon />}
            label={t`Not Helpful ${negativeFeedbackCount}`}
          />
        </Tabs>
        <Box
          display={tab === 0 ? 'block' : 'none'}
          className={classes.feedbacks}
        >
          {feedbacks
            .filter(({ vote, user }) => vote === 'UPVOTE' && user)
            .map(feedback => (
              <Feedback key={feedback.id} {...feedback} />
            ))}
        </Box>
        <Box
          display={tab === 1 ? 'block' : 'none'}
          className={classes.feedbacks}
        >
          {feedbacks
            .filter(({ vote, user }) => vote === 'DOWNVOTE' && user)
            .map(feedback => (
              <Feedback key={feedback.id} {...feedback} />
            ))}
        </Box>
      </Popover>
      <Popover
        id={`feedback-reasons-${reply.id}`}
        open={!!votePopoverAnchorEl}
        anchorEl={votePopoverAnchorEl}
        onClose={closeVotePopover}
        anchorOrigin={{
          vertical: 'top',
          horizontal: 'center',
        }}
        classes={{ paper: classes.popover }}
      >
        <button
          type="button"
          className={classes.closeButton}
          onClick={closeVotePopover}
        >
          <CloseIcon />
        </button>
        <Typography
          className={classes.popupTitle}
        >{t`The reason why you upvote/downvote:`}</Typography>
        <textarea
          className={classes.textarea}
          value={reason}
          onChange={e => setReason(e.target.value)}
          rows={10}
        />

          <button
            type="button"
            className={classes.sendButton}
            disabled={updatingReplyFeedback}
            onClick={() => {
              createReplyFeedback({
                variables: { articleId, replyId, vote, comment: reason },
              });
            }}
          >{t`Send`}</button>

      </Popover>

  );
}

ReplyItem.fragments = {
  ArticleReplyFeedbackData,
  ReplyItem: gql`
    fragment ReplyItem on Reply {
      id
      text
      type
      createdAt
      user {
        id
        name
        avatarUrl
      }
    }
  `,
};

export default ReplyItem;
<commit_msg>Fix typo<commit_after>// @todo: merge this with ReplyFeedback
import { useState } from 'react';
import { makeStyles, withStyles } from '@material-ui/core/styles';
import {
  Tabs,
  Tab,
  Box,
  Popover,
  Typography,
  SvgIcon,
} from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import gql from 'graphql-tag';
import { useMutation } from '@apollo/react-hooks';
import { t } from 'ttag';
import { format, formatDistanceToNow } from 'lib/dateWithLocale';
import isValid from 'date-fns/isValid';
import { TYPE_NAME } from '../constants/replyType';
import Avatar from 'components/AppLayout/Widgets/Avatar';
import TextExpansion from './TextExpansion';
import Feedback from './Feedback';
import cx from 'clsx';

const ThumbUpIcon = props => (
  <SvgIcon {...props} viewBox="0 0 17 15">
    <path d="M1 13.8672H3.65455V6.51612H1V13.8672ZM15.6 7.1287C15.6 6.45486 15.0027 5.90353 14.2727 5.90353H10.0852L10.7156 3.10401L10.7355 2.90798C10.7355 2.65682 10.6227 2.42403 10.4435 2.25863L9.74009 1.61542L5.37336 5.65237C5.12782 5.8729 4.98182 6.17919 4.98182 6.51612V12.642C4.98182 13.3158 5.57909 13.8672 6.30909 13.8672H12.2818C12.8326 13.8672 13.3038 13.5609 13.5029 13.1198L15.5071 8.80107C15.5668 8.66017 15.6 8.51315 15.6 8.35388V7.1287Z" />
  </SvgIcon>
);

const ThumbDownIcon = props => (
  <SvgIcon {...props} viewBox="0 0 17 15">
    <path d="M10.2909 1.46155H4.31818C3.76736 1.46155 3.29618 1.76784 3.09709 2.2089L1.09291 6.52765C1.03318 6.66854 1 6.81556 1 6.97483V8.20001C1 8.87386 1.59727 9.42518 2.32727 9.42518H6.51482L5.88436 12.2247L5.86445 12.4207C5.86445 12.6719 5.97727 12.9047 6.15646 13.0701L6.85991 13.7133L11.2333 9.67635C11.4722 9.45581 11.6182 9.14952 11.6182 8.8126V2.68672C11.6182 2.01288 11.0209 1.46155 10.2909 1.46155ZM12.9455 1.46155V8.8126H15.6V1.46155H12.9455Z" />
  </SvgIcon>
);

const useStyles = makeStyles(theme => ({
  root: {
    display: 'flex',
    flex: '1 0 auto',
  },
  replyType: {
    color: ({ replyType }) => {
      switch (replyType) {
        case 'OPINIONATED':
          return '#2079F0';
        case 'NOT_RUMOR':
          return '#00B172';
        case 'RUMOR':
          return '#FB5959';
        default:
          return theme.palette.common.black;
      }
    },
  },
  status: {
    display: 'flex',
    justifyContent: 'space-between',
    alignItems: 'center',
    flexWrap: 'wrap',
  },
  actions: {
    display: 'flex',
    order: 2,
    [theme.breakpoints.up('md')]: {
      order: 1,
    },
  },
  createdAt: {
    color: theme.palette.secondary[200],
    flex: '1 0 100%',
    order: 1,
    padding: '8px 0',
    [theme.breakpoints.up('md')]: {
      flex: '0 1 auto',
      order: 2,
    },
  },
  vote: {
    display: 'flex',
    alignItems: 'center',
    borderRadius: 45,
    padding: '1px 8px',
    marginRight: 3,
    outline: 'none',
    cursor: 'pointer',
    border: `1px solid ${theme.palette.secondary[100]}`,
    color: theme.palette.secondary[100],
    background: theme.palette.common.white,
    [theme.breakpoints.up('md')]: {
      padding: '4px 18px',
      marginRight: 10,
    },
  },
  voted: {
    border: `1px solid ${theme.palette.primary[500]}`,
    color: theme.palette.primary[500],
  },
  buttonGroup: {
    display: 'flex',
    alignItems: 'center',
    whiteSpace: 'nowrap',
    '& $vote': {
      marginRight: 0,
      display: 'inline-flex',
      color: theme.palette.secondary[200],
      background: theme.palette.secondary[50],
      '& img': {
        paddingLeft: 6,
      },
      '&:first-child': {
        borderTopRightRadius: 0,
        borderBottomRightRadius: 0,
        borderRight: 0,
        paddingRight: 0,
      },
      '&:last-child': {
        borderTopLeftRadius: 0,
        borderBottomLeftRadius: 0,
      },
      '&:not(:first-child):not(:last-child)': {
        borderRadius: 0,
        borderLeft: 0,
        borderRight: 0,
      },
    },
  },
  popover: {
    position: 'relative',
    width: 420,
    maxWidth: '90vw',
    padding: 32,
  },
  closeButton: {
    background: theme.palette.common.white,
    cursor: 'pointer',
    position: 'absolute',
    right: 6,
    top: 10,
    border: 'none',
    outline: 'none',
    color: theme.palette.secondary[100],
  },
  feedbacks: {
    marginTop: 16,
    maxHeight: 300,
    overflow: 'auto',
  },
  popupTitle: {
    fontSize: 18,
    marginBottom: 24,
  },
  textarea: {
    padding: 15,
    width: '100%',
    borderRadius: 8,
    border: `1px solid ${theme.palette.secondary[100]}`,
    outline: 'none',
    '&:focus': {
      border: `1px solid ${theme.palette.primary[500]}`,
    },
  },
  textCenter: { textAlign: 'center' },
  sendButton: {
    outline: 'none',
    border: 'none',
    marginTop: 10,
    padding: '10px 25px',
    background: theme.palette.primary[500],
    color: theme.palette.common.white,
    cursor: 'pointer',
    borderRadius: 30,
    '&:disabled': {
      opacity: 0.7,
      cursor: 'not-allowed',
    },
  },
}));

const CustomTab = withStyles({
  root: {
    position: 'relative',
    minHeight: 0,
  },
  wrapper: {
    '& > svg': {
      position: 'absolute',
      left: 0,
    },
  },
})(Tab);

const ArticleReplyFeedbackForUser = gql`
  fragment ArticleReplyFeedbackForUser on ArticleReply {
    articleId
    replyId
    ownVote
  }
`;

const ArticleReplyFeedbackData = gql`
  fragment ArticleReplyFeedbackData on ArticleReply {
    ...ArticleReplyFeedbackForUser
    positiveFeedbackCount
    negativeFeedbackCount
    ownVote
    feedbacks {
      ...Feedback
    }
  }
  ${Feedback.fragments.Feedback}
  ${ArticleReplyFeedbackForUser}
`;

const CREATE_REPLY_FEEDBACK = gql`
  mutation CreateOrUpdateArticleReplyFeedback(
    $articleId: String!
    $replyId: String!
    $vote: FeedbackVote!
    $comment: String
  ) {
    CreateOrUpdateArticleReplyFeedback(
      articleId: $articleId
      replyId: $replyId
      vote: $vote
      comment: $comment
    ) {
      ...ArticleReplyFeedbackData
    }
  }
  ${ArticleReplyFeedbackData}
`;
/**
 *
 * @param {ReplyItem} props.reply - see ReplyItem in GraphQL fragment
 * @param {boolean} showUser
 */
function ReplyItem({
  articleId,
  replyId,
  reply,
  feedbacks,
  positiveFeedbackCount,
  negativeFeedbackCount,
  ownVote,
}) {
  const [createReplyFeedback, { loading: updatingReplyFeedback }] = useMutation(
    CREATE_REPLY_FEEDBACK,
    {
      onCompleted() {
        closeVotePopover();
        setReason('');
      },
    }
  );

  const [vote, setVote] = useState(null);
  const [reason, setReason] = useState('');
  const [reasonsPopoverAnchorEl, setReasonsPopoverAnchorEl] = useState(null);
  const [votePopoverAnchorEl, setVotePopoverAnchorEl] = useState(null);
  const [tab, setTab] = useState(0);
  const { user, text, type: replyType } = reply;
  const createdAt = new Date(reply.createdAt);
  const timeAgoStr = formatDistanceToNow(createdAt);

  const openReasonsPopover = event => {
    setReasonsPopoverAnchorEl(event.currentTarget);
  };

  const closeReasonsPopover = () => {
    setReasonsPopoverAnchorEl(null);
  };

  const openVotePopover = (event, value) => {
    setVotePopoverAnchorEl(event.currentTarget);
    setVote(value);
  };

  const closeVotePopover = () => {
    setVotePopoverAnchorEl(null);
    setVote(null);
  };

  const classes = useStyles({ replyType });
  const userName = user?.name ?? t`Anonymous`;

  return (

      <Box p="24px" display={['none', 'none', 'block']}>
        <Avatar user={user} size={72} />
        {/*

{TYPE_ICON[replyType]}

        */}
      </Box>
      <Box pr="14px" display={['block', 'block', 'none']}>
        <Avatar user={user} size={30} />
        {/*

{TYPE_ICON[replyType]}

        */}
      </Box>
      <Box py="12px" flexGrow={1}>

{t`${userName} consider this ${TYPE_NAME[replyType]}`}

        <TextExpansion content={text} />

            <button
              className={cx(
                classes.vote,
                ownVote === 'UPVOTE' && classes.voted
              )}
              type="button"
              onClick={e => openVotePopover(e, 'UPVOTE')}
            >
              <ThumbUpIcon />
            </button>
            <button
              className={cx(
                classes.vote,
                ownVote === 'DOWNVOTE' && classes.voted
              )}
              type="button"
              onClick={e => openVotePopover(e, 'DOWNVOTE')}
            >
              <ThumbDownIcon />
            </button>

              <button className={classes.vote} type="button">
                {positiveFeedbackCount}
                <ThumbUpIcon style={{ fontSize: 16, margin: '0 2px' }} />
              </button>
              <button className={classes.vote} type="button">
                {negativeFeedbackCount}
                <ThumbDownIcon style={{ fontSize: 16, margin: '0 2px' }} />
              </button>
              <button
                className={classes.vote}
                type="button"
                onClick={openReasonsPopover}
              >
                {t`See Reasons`}
              </button>

          {isValid(createdAt) && (
            {t`${timeAgoStr} ago`}
          )}

      </Box>
      <Popover
        id={`reply-reasons-${reply.id}`}
        open={!!reasonsPopoverAnchorEl}
        anchorEl={reasonsPopoverAnchorEl}
        onClose={closeReasonsPopover}
        anchorOrigin={{
          vertical: 'top',
          horizontal: 'center',
        }}
        classes={{ paper: classes.popover }}
      >
        <button
          type="button"
          className={classes.closeButton}
          onClick={closeReasonsPopover}
        >
          <CloseIcon />
        </button>
        {text}
        <Tabs
          value={tab}
          onChange={(e, value) => setTab(value)}
          indicatorColor="primary"
          textColor="primary"
          variant="fullWidth"
        >
          <CustomTab
            icon={<ThumbUpIcon />}
            label={t`Helpful ${positiveFeedbackCount}`}
          />
          <CustomTab
            icon={<ThumbDownIcon />}
            label={t`Not Helpful ${negativeFeedbackCount}`}
          />
        </Tabs>
        <Box
          display={tab === 0 ? 'block' : 'none'}
          className={classes.feedbacks}
        >
          {feedbacks
            .filter(({ vote, user }) => vote === 'UPVOTE' && user)
            .map(feedback => (
              <Feedback key={feedback.id} {...feedback} />
            ))}
        </Box>
        <Box
          display={tab === 1 ? 'block' : 'none'}
          className={classes.feedbacks}
        >
          {feedbacks
            .filter(({ vote, user }) => vote === 'DOWNVOTE' && user)
            .map(feedback => (
              <Feedback key={feedback.id} {...feedback} />
            ))}
        </Box>
      </Popover>
      <Popover
        id={`feedback-reasons-${reply.id}`}
        open={!!votePopoverAnchorEl}
        anchorEl={votePopoverAnchorEl}
        onClose={closeVotePopover}
        anchorOrigin={{
          vertical: 'top',
          horizontal: 'center',
        }}
        classes={{ paper: classes.popover }}
      >
        <button
          type="button"
          className={classes.closeButton}
          onClick={closeVotePopover}
        >
          <CloseIcon />
        </button>
        <Typography
          className={classes.popupTitle}
        >{t`The reason why you upvote/downvote:`}</Typography>
        <textarea
          className={classes.textarea}
          value={reason}
          onChange={e => setReason(e.target.value)}
          rows={10}
        />

          <button
            type="button"
            className={classes.sendButton}
            disabled={updatingReplyFeedback}
            onClick={() => {
              createReplyFeedback({
                variables: { articleId, replyId, vote, comment: reason },
              });
            }}
          >{t`Send`}</button>

      </Popover>

  );
}

ReplyItem.fragments = {
  ArticleReplyFeedbackData,
  ReplyItem: gql`
    fragment ReplyItem on Reply {
      id
      text
      type
      createdAt
      user {
        id
        name
        avatarUrl
      }
    }
  `,
};

export default ReplyItem;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import Title from '../components/title2.jsx';

class SearchBar extends Component {
  constructor(props) {
    super(props);

    this.state = {
      searchType: 'constellation',
      searchInput: '',
    };

    this.handleTextChange = this.handleTextChange.bind(this);
    this.handleRadioChange = this.handleRadioChange.bind(this);
  }
	handleTextChange(event) {
    this.setState({
      searchInput: event.target.value,
    });
  }

  handleRadioChange(event) {
    this.setState({
      searchType: event.target.value,
    });
  }
	render() {
    return (

        <Title origin={this.props.origin} direction={this.props.direction}></Title>
        <form>
          <input
            id="searchInput"
            type="text"
            placeholder="Search..."
            value={this.state.searchInput}
            onChange={this.handleTextChange}
          />

              <input
                type = "radio"
                name="searchType"
                value="constellation"
                onClick={this.handleRadioChange}
			          onChange={this.handleRadioChange}
                checked={this.state.searchType === 'constellation'}/>Constellations

              <input
                type = "radio"
                name="searchType"
                value="galaxy"
                onClick={this.handleRadioChange}
			          onChange={this.handleRadioChange}
                checked={this.state.searchType === 'galaxy'}/>Galaxies

              <input
                type = "radio"
                name="searchType"
                value="universe"
								onClick={this.handleRadioChange}
			          onChange={this.handleRadioChange}
                checked={this.state.searchType === 'universe'}/>Universe

        </form>

    );
  }
}
export default SearchBar;
<commit_msg>Delete SearchBar.js<commit_after><|endoftext|>


<commit_before>var verify = require("../utils/verify");
var KNOWN_MODULE_TYPES = {
  "core": true,
  "core-extension": true,
  "extension": true
};

/**
 * @class Component
 * Represents a component to be built.
 *
 * @param {!Object} configuration The configuration of the component.
 *  @property {!Object} grunt Instance of grunt to use in the component.
 *  @property {!String} name  The name of the component.
 *  @property {?String} colour  The colour of the component.
 *
 *  @property {?Object} targets Target definition object.
 *    @property {?Array.<String>} deps List of project dependencies.
 *
 *  @property {?String} module-type Type of component being defined:
 *    * core: module is always enabled and has no init function [default].
 *    * core-extension: module is always enabled and has an init function.
 *    * extension: module has an init function and can be disabled.
 */
var Component = module.exports = function Component(configuration) {
  // Verify configuration.
  verify.notNullObject(configuration, "Invalid component configuration");
  verify.notNullObject(configuration.grunt, "Grunt instance not valid");
  verify.notEmptyString(configuration.name, "Missing component name");
  verify.notNullObjectIfDefined(
      configuration.targets,
      "If defined, the target property must be an object."
  );
  verify.optionalColour(
      configuration.colour,
      "If defined, the colour property must be an hex colour prefixed by an #."
  );
  verify.optionalNotEmptyString(
      configuration["module-type"],
      "If defined, the module-type property must be a string."
  );
  verify.optionalArray(
      configuration.inject,
      "Invalid injection list. Array of string required.",
      verify.notEmptyString
  );

  // Store it in the new instance.
  this._colour = configuration.colour;
  this._grunt  = configuration.grunt;
  this._inject = configuration.inject || [];
  this._name   = configuration.name;
  this._module_type = configuration["module-type"] || "core";
  this._enabled = true;
  this._targets = {};

  // Validate mode.
  if (!KNOWN_MODULE_TYPES[this._module_type]) {
    throw new Error("Unrecognised module type '" + this._module_type + "'");
  }

  // Process targets.
  if (configuration.targets) {
    this._process_targets(configuration);
  }
};

/**
 * Parses a dependency name for a target.
 * @param {!String} name   The dependency name to parse.
 * @param {!String} target The target that the name is resolved for.
 * @returns {!Object} A {name: !String, target: !String} object.
 */
Component.parseDependencyName = function parseDependencyName(name, target) {
  verify.notEmptyString(name, "The dependency name must be a string");
  verify.notEmptyString(
      target, "The name of the current target must be a string"
  );
  var parts = name.split(".");

  // Target missing from name.
  if (parts.length === 1) {
    return {
      name: name,
      target: target
    };
  }

  // Both target and name are specified.
  if (parts.length === 2) {
    return {
      name: parts[1],
      target: parts[0]
    };
  }

  throw new Error("Cannot parse malformed dependency '" + name + "'");
};

/**
 * Checks the given list of dependencies for conflicts and duplicates.
 * @param {!Array} dependencies
 * @returns {!Array}
 */
Component.checkDependenciesList = function checkDependenciesList(
    dependencies
) {
  verify.array(dependencies, "An array of dependencies is required");

  // Check for ambiguous dependencies.
  var components = {};
  dependencies.forEach(function(dep) {
    if (dep.name in components && components[dep.name] !== dep.target) {
      throw new Error("Ambiguous dependency for component '" + dep.name + "'");
    }
    components[dep.name] = dep.target;
  });

  // Rebuild result array.
  var added  = {};
  return dependencies.filter(function(dep) {
    var ignore = dep.name in added;
    added[dep.name] = true;
    return !ignore;
  });
};

/**
 * Processes a component configuration to extract targets information.
 * @param {!Object} config The configuration to process.
 */
Component.prototype._process_targets = function _process_targets(config) {
  var targets = Object.keys(config.targets);
  var nameParser = function(target) {
    return function(name) {
      return Component.parseDependencyName(name, target);
    };
  };

  for (var idx=0; idx < targets.length; idx++) {
    var name   = targets[idx];
    var target = config.targets[name];

    // Verify target structure.
    verify.notNullObject(target, "Target definitions must be objects.");
    verify.optionalArray(
        target.deps,
        "If defined, the deps property of target '" + name +
        "' must be an array"
    );

    // Process dependneces.
    var global_deps = config.deps || [];
    var target_deps = target.deps || [];
    var deps = [];
    deps.push.apply(deps, global_deps);
    deps.push.apply(deps, target_deps);
    deps = deps.map(nameParser(name));
    deps = Component.checkDependenciesList(deps);

    // Build result.
    this._targets[name] = {
      deps: deps
    };
  }
};

/**
 * Adds a dependency to all the targets of the component.
 * @param {!String} name The name of the dependency to add.
 */
Component.prototype.addDependency = function addDependency(name) {
  var _this   = this;
  var targets = Object.keys(this._targets);

  targets.forEach(function(target) {
    var deps = [];
    var parsed_name = Component.parseDependencyName(name, target);

    deps.push.apply(deps, _this._targets[target].deps);
    deps.push(parsed_name);

    deps = Component.checkDependenciesList(deps);
    _this._targets[target].deps = deps;
  });
};

/** @returns {bool} True if the component can be disabled. */
Component.prototype.canDisable = function canDisable() {
  return this._module_type === "extension";
};

/** @returns {!String} The colour of the component. */
Component.prototype.colour = function colour() {
  if (!this._colour) {
    this._colour = "#" + Math.floor(Math.random() * 16777215).toString(16);
  }
  return this._colour;
};

/**
 * Returns the list of dependencies for a target.
 * @param {!Strng} name The target to look for.
 * @returns {!Array} List of objects describing targets.
 */
Component.prototype.dependencies = function dependencies(name) {
  verify.notEmptyString(name, "Target name must be a string");
  if (!(name in this._targets)) {
    throw new Error("Missing target '" + name + "' for '" + this._name + "'");
  }

  var target = this._targets[name];
  return target.deps;
};

/**
 * Disables an optional component.
 * Calls to this method have no effect if the component cannot be diasabled.
 * @returns {!Boolean} true if the component was disabled by this call.
 */
Component.prototype.disable = function disable() {
  var enabled = this._enabled;
  this._enabled = false;
  return enabled;
};

/**
 * @returns {!Boolean} true if the component is currently enabled.
 */
Component.prototype.enabled = function enabled() {
  return (
      this._module_type === "core" ||
      this._module_type === "core-extension" ||
      this._enabled
  );
};

/**
 * Returns the path to run the clean task against
 * @param {!String} target The target to get the path for.
 * @returns {!String|!Array.<!String>} Path or paths to clear.
 */
Component.prototype.getCleanPath = function getCleanPath(target) {
  throw new Error("Method not implemented");
};

/**
 * Returns a list of C/C++ header files exposed by this component.
 * @param {!String} target The target to get the headers for.
 * @returns {!Array.<!String>} Headers file exposed.
 */
Component.prototype.getCppHeaders = function getCppHeaders(target) {
  return [];
};

/**
 * Returns a list of C/C++ dynamic libraries needed by this component.
 * @param {!String} target The target to get the dynamic libraries for.
 * @returns {!Array.<!String>} Static libraries exposed.
 */
Component.prototype.getDynamicLibs = function getDynamicLibs(target) {
  return [];
};

/** @returns {!String} The type of component. */
Component.prototype.getModuleType = function getModuleType() {
  return this._module_type;
};

/**
 * Returns a list of partial grunt file mappings.
 *
 * These mappings are used by NodeJS components to copy the needed files
 * into their destination directoryes.
 * As such, the mappings will have a source but should not have a dest
 * attribute, which will be set by the component that includes this.
 *
 * @param {!String} target The target to get the deps for.
 * @returns {!Array} An array of grunt file mappings without a dest attribute.
 */
Component.prototype.getNodeJsDependencies = function getNodeJsDependencies(
    target
) {
  return [];
};

/**
 * Returns the path to the output to the item build by this component.
 *
 * @param {!String} target The target to get the output path for.
 * @returns {!String|!Object}
 *     Path or to the item build by this target or object describing
 *     the files built by this target.
 *
 *     If this method returns an object,
 *     the object has the following properties:
 *
 *       * cwd: the `cwd` option to `grunt.file.copy`.
 *       * expand: the `expand` option to `grunt.file.copy`.
 *       * src: the `src` option to `grunt.file.copy`, this is mandatory.
 */
Component.prototype.getOutput = function getOutput(target) {
  throw new Error("Method not implemented");
};

/**
 * Returns a list of C/C++ static libraries exposed by this component.
 * @param {!String} target The target to get the static libraries for.
 * @returns {!Array.<!String>} Static libraries exposed.
 */
Component.prototype.getStaticLibs = function getStaticLibs(target) {
  return [];
};

/**
 * Returns a graph description for the component.
 *
 * The graph desctiption has the following format:
 *   * nodes: map from node ID to node description
 *     * label:  string describing the label.
 *     * colour: string with the colour of the node.
 *   * edges: list of directed edges mapping node ids.
 *
 * @param {!Object} components The components registry.
 * @param {!String} target     The build target to generate the graph for.
 * @returns {!Object} Direct dependencies graph for this component.
 */
Component.prototype.graph = function graph(components, target) {
  var deps  = this.dependencies(target);
  var graph = {
    nodes: {},
    edges: []
  };

  // Conver deps in components.
  deps = deps.map(function(dep) {
    return {
      instance: components.get(dep.name),
      target:   dep.target
    }
  });

  // Build the nodes map.
  graph.nodes[this.name()] = {
    colour: this.colour(),
    label:  this.name()
  };

  deps.forEach(function(dep) {
    var name = dep.instance.name();
    graph.nodes[name] = {
      colour: dep.instance.colour(),
      label:  name
    };
  });

  // Build the edges list.
  var _this = this;
  deps.forEach(function(dep) {
    graph.edges.push({
      colour: _this.colour(),
      from: dep.instance.name(),
      to:   _this.name()
    });
  });

  // Done.
  return graph;
};

/**
 * Handles the configuration and enqueuing of analysis tasks.
 * @param {!Components} components Collection of components in the system.
 */
Component.prototype.handleAnalysis = function handleAnalysis(components) {
  throw new Error("Method not implemented");
};

/**
 * Handles the configuration and enqueuing of a target.
 * @param {!String} target The target to handle.
 * @param {!Components} components Collection of components in the system.
 */
Component.prototype.handleTarget = function handleTarget(target, components) {
  throw new Error("Method not implemented");
};

/** @returns {bool} True if the component has an init function. */
Component.prototype.hasModuleInit = function hasModuleInit() {
  return (
      this._module_type === "core-extension" ||
      this._module_type === "extension"
  );
};

/**
 * Checks if a target is available.
 * @param {!String} target The target to check
 * @returns {!Boolean} True if the target is configured.
 */
Component.prototype.hasTarget = function hasTarget(target) {
  verify.notEmptyString(target, "Target to check for must be a string");
  return target in this._targets;
};

/** @returns {!Array.<String>} the list of components to be injected into. */
Component.prototype.listInjections = function listInjections() {
  return this._inject;
};

/** @returns {!String} The name of the component. */
Component.prototype.name = function name() {
  return this._name;
};

/** @returns {!Array} The list of targets defined for the component. */
Component.prototype.targets = function targets() {
  return Object.keys(this._targets).sort();
};
<commit_msg>Revert "Revert back to . as separator."<commit_after>var verify = require("../utils/verify");
var KNOWN_MODULE_TYPES = {
  "core": true,
  "core-extension": true,
  "extension": true
};

/**
 * @class Component
 * Represents a component to be built.
 *
 * @param {!Object} configuration The configuration of the component.
 *  @property {!Object} grunt Instance of grunt to use in the component.
 *  @property {!String} name  The name of the component.
 *  @property {?String} colour  The colour of the component.
 *
 *  @property {?Object} targets Target definition object.
 *    @property {?Array.<String>} deps List of project dependencies.
 *
 *  @property {?String} module-type Type of component being defined:
 *    * core: module is always enabled and has no init function [default].
 *    * core-extension: module is always enabled and has an init function.
 *    * extension: module has an init function and can be disabled.
 */
var Component = module.exports = function Component(configuration) {
  // Verify configuration.
  verify.notNullObject(configuration, "Invalid component configuration");
  verify.notNullObject(configuration.grunt, "Grunt instance not valid");
  verify.notEmptyString(configuration.name, "Missing component name");
  verify.notNullObjectIfDefined(
      configuration.targets,
      "If defined, the target property must be an object."
  );
  verify.optionalColour(
      configuration.colour,
      "If defined, the colour property must be an hex colour prefixed by an #."
  );
  verify.optionalNotEmptyString(
      configuration["module-type"],
      "If defined, the module-type property must be a string."
  );
  verify.optionalArray(
      configuration.inject,
      "Invalid injection list. Array of string required.",
      verify.notEmptyString
  );

  // Store it in the new instance.
  this._colour = configuration.colour;
  this._grunt  = configuration.grunt;
  this._inject = configuration.inject || [];
  this._name   = configuration.name;
  this._module_type = configuration["module-type"] || "core";
  this._enabled = true;
  this._targets = {};

  // Validate mode.
  if (!KNOWN_MODULE_TYPES[this._module_type]) {
    throw new Error("Unrecognised module type '" + this._module_type + "'");
  }

  // Process targets.
  if (configuration.targets) {
    this._process_targets(configuration);
  }
};

/**
 * Parses a dependency name for a target.
 * @param {!String} name   The dependency name to parse.
 * @param {!String} target The target that the name is resolved for.
 * @returns {!Object} A {name: !String, target: !String} object.
 */
Component.parseDependencyName = function parseDependencyName(name, target) {
  verify.notEmptyString(name, "The dependency name must be a string");
  verify.notEmptyString(
      target, "The name of the current target must be a string"
  );
  var parts = name.split("@");

  // Target missing from name.
  if (parts.length === 1) {
    return {
      name: name,
      target: target
    };
  }

  // Both target and name are specified.
  if (parts.length === 2) {
    return {
      name: parts[1],
      target: parts[0]
    };
  }

  throw new Error("Cannot parse malformed dependency '" + name + "'");
};

/**
 * Checks the given list of dependencies for conflicts and duplicates.
 * @param {!Array} dependencies
 * @returns {!Array}
 */
Component.checkDependenciesList = function checkDependenciesList(
    dependencies
) {
  verify.array(dependencies, "An array of dependencies is required");

  // Check for ambiguous dependencies.
  var components = {};
  dependencies.forEach(function(dep) {
    if (dep.name in components && components[dep.name] !== dep.target) {
      throw new Error("Ambiguous dependency for component '" + dep.name + "'");
    }
    components[dep.name] = dep.target;
  });

  // Rebuild result array.
  var added  = {};
  return dependencies.filter(function(dep) {
    var ignore = dep.name in added;
    added[dep.name] = true;
    return !ignore;
  });
};

/**
 * Processes a component configuration to extract targets information.
 * @param {!Object} config The configuration to process.
 */
Component.prototype._process_targets = function _process_targets(config) {
  var targets = Object.keys(config.targets);
  var nameParser = function(target) {
    return function(name) {
      return Component.parseDependencyName(name, target);
    };
  };

  for (var idx=0; idx < targets.length; idx++) {
    var name   = targets[idx];
    var target = config.targets[name];

    // Verify target structure.
    verify.notNullObject(target, "Target definitions must be objects.");
    verify.optionalArray(
        target.deps,
        "If defined, the deps property of target '" + name +
        "' must be an array"
    );

    // Process dependneces.
    var global_deps = config.deps || [];
    var target_deps = target.deps || [];
    var deps = [];
    deps.push.apply(deps, global_deps);
    deps.push.apply(deps, target_deps);
    deps = deps.map(nameParser(name));
    deps = Component.checkDependenciesList(deps);

    // Build result.
    this._targets[name] = {
      deps: deps
    };
  }
};

/**
 * Adds a dependency to all the targets of the component.
 * @param {!String} name The name of the dependency to add.
 */
Component.prototype.addDependency = function addDependency(name) {
  var _this   = this;
  var targets = Object.keys(this._targets);

  targets.forEach(function(target) {
    var deps = [];
    var parsed_name = Component.parseDependencyName(name, target);

    deps.push.apply(deps, _this._targets[target].deps);
    deps.push(parsed_name);

    deps = Component.checkDependenciesList(deps);
    _this._targets[target].deps = deps;
  });
};

/** @returns {bool} True if the component can be disabled. */
Component.prototype.canDisable = function canDisable() {
  return this._module_type === "extension";
};

/** @returns {!String} The colour of the component. */
Component.prototype.colour = function colour() {
  if (!this._colour) {
    this._colour = "#" + Math.floor(Math.random() * 16777215).toString(16);
  }
  return this._colour;
};

/**
 * Returns the list of dependencies for a target.
 * @param {!Strng} name The target to look for.
 * @returns {!Array} List of objects describing targets.
 */
Component.prototype.dependencies = function dependencies(name) {
  verify.notEmptyString(name, "Target name must be a string");
  if (!(name in this._targets)) {
    throw new Error("Missing target '" + name + "' for '" + this._name + "'");
  }

  var target = this._targets[name];
  return target.deps;
};

/**
 * Disables an optional component.
 * Calls to this method have no effect if the component cannot be diasabled.
 * @returns {!Boolean} true if the component was disabled by this call.
 */
Component.prototype.disable = function disable() {
  var enabled = this._enabled;
  this._enabled = false;
  return enabled;
};

/**
 * @returns {!Boolean} true if the component is currently enabled.
 */
Component.prototype.enabled = function enabled() {
  return (
      this._module_type === "core" ||
      this._module_type === "core-extension" ||
      this._enabled
  );
};

/**
 * Returns the path to run the clean task against
 * @param {!String} target The target to get the path for.
 * @returns {!String|!Array.<!String>} Path or paths to clear.
 */
Component.prototype.getCleanPath = function getCleanPath(target) {
  throw new Error("Method not implemented");
};

/**
 * Returns a list of C/C++ header files exposed by this component.
 * @param {!String} target The target to get the headers for.
 * @returns {!Array.<!String>} Headers file exposed.
 */
Component.prototype.getCppHeaders = function getCppHeaders(target) {
  return [];
};

/**
 * Returns a list of C/C++ dynamic libraries needed by this component.
 * @param {!String} target The target to get the dynamic libraries for.
 * @returns {!Array.<!String>} Static libraries exposed.
 */
Component.prototype.getDynamicLibs = function getDynamicLibs(target) {
  return [];
};

/** @returns {!String} The type of component. */
Component.prototype.getModuleType = function getModuleType() {
  return this._module_type;
};

/**
 * Returns a list of partial grunt file mappings.
 *
 * These mappings are used by NodeJS components to copy the needed files
 * into their destination directoryes.
 * As such, the mappings will have a source but should not have a dest
 * attribute, which will be set by the component that includes this.
 *
 * @param {!String} target The target to get the deps for.
 * @returns {!Array} An array of grunt file mappings without a dest attribute.
 */
Component.prototype.getNodeJsDependencies = function getNodeJsDependencies(
    target
) {
  return [];
};

/**
 * Returns the path to the output to the item build by this component.
 *
 * @param {!String} target The target to get the output path for.
 * @returns {!String|!Object}
 *     Path or to the item build by this target or object describing
 *     the files built by this target.
 *
 *     If this method returns an object,
 *     the object has the following properties:
 *
 *       * cwd: the `cwd` option to `grunt.file.copy`.
 *       * expand: the `expand` option to `grunt.file.copy`.
 *       * src: the `src` option to `grunt.file.copy`, this is mandatory.
 */
Component.prototype.getOutput = function getOutput(target) {
  throw new Error("Method not implemented");
};

/**
 * Returns a list of C/C++ static libraries exposed by this component.
 * @param {!String} target The target to get the static libraries for.
 * @returns {!Array.<!String>} Static libraries exposed.
 */
Component.prototype.getStaticLibs = function getStaticLibs(target) {
  return [];
};

/**
 * Returns a graph description for the component.
 *
 * The graph desctiption has the following format:
 *   * nodes: map from node ID to node description
 *     * label:  string describing the label.
 *     * colour: string with the colour of the node.
 *   * edges: list of directed edges mapping node ids.
 *
 * @param {!Object} components The components registry.
 * @param {!String} target     The build target to generate the graph for.
 * @returns {!Object} Direct dependencies graph for this component.
 */
Component.prototype.graph = function graph(components, target) {
  var deps  = this.dependencies(target);
  var graph = {
    nodes: {},
    edges: []
  };

  // Conver deps in components.
  deps = deps.map(function(dep) {
    return {
      instance: components.get(dep.name),
      target:   dep.target
    }
  });

  // Build the nodes map.
  graph.nodes[this.name()] = {
    colour: this.colour(),
    label:  this.name()
  };

  deps.forEach(function(dep) {
    var name = dep.instance.name();
    graph.nodes[name] = {
      colour: dep.instance.colour(),
      label:  name
    };
  });

  // Build the edges list.
  var _this = this;
  deps.forEach(function(dep) {
    graph.edges.push({
      colour: _this.colour(),
      from: dep.instance.name(),
      to:   _this.name()
    });
  });

  // Done.
  return graph;
};

/**
 * Handles the configuration and enqueuing of analysis tasks.
 * @param {!Components} components Collection of components in the system.
 */
Component.prototype.handleAnalysis = function handleAnalysis(components) {
  throw new Error("Method not implemented");
};

/**
 * Handles the configuration and enqueuing of a target.
 * @param {!String} target The target to handle.
 * @param {!Components} components Collection of components in the system.
 */
Component.prototype.handleTarget = function handleTarget(target, components) {
  throw new Error("Method not implemented");
};

/** @returns {bool} True if the component has an init function. */
Component.prototype.hasModuleInit = function hasModuleInit() {
  return (
      this._module_type === "core-extension" ||
      this._module_type === "extension"
  );
};

/**
 * Checks if a target is available.
 * @param {!String} target The target to check
 * @returns {!Boolean} True if the target is configured.
 */
Component.prototype.hasTarget = function hasTarget(target) {
  verify.notEmptyString(target, "Target to check for must be a string");
  return target in this._targets;
};

/** @returns {!Array.<String>} the list of components to be injected into. */
Component.prototype.listInjections = function listInjections() {
  return this._inject;
};

/** @returns {!String} The name of the component. */
Component.prototype.name = function name() {
  return this._name;
};

/** @returns {!Array} The list of targets defined for the component. */
Component.prototype.targets = function targets() {
  return Object.keys(this._targets).sort();
};
<|endoftext|>


<commit_before>const defaultColorList = {
  3: ['#2b83ba', '#ffffbf', '#d7191c'],
  4: ['#2b83ba', '#abdda4', '#fdae61', '#d7191c'],
  5: ['#2b83ba', '#abdda4', '#ffffbf', '#fdae61', '#d7191c'],
  6: ['#3288bd', '#99d594', '#e6f598', '#fee08b', '#fc8d59', '#d53e4f'],
  7: ['#3288bd', '#99d594', '#e6f598', '#ffffbf', '#fee08b', '#fc8d59', '#d53e4f'],
  8: ['#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#fee08b', '#fdae61', '#f46d43', '#d53e4f'],
  9: ['#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#ffffbf', '#fee08b', '#fdae61', '#f46d43', '#d53e4f'],
  10: ['#5e4fa2', '#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#fee08b', '#fdae61', '#f46d43', '#d53e4f', '#9e0142'],
  11: ['#5e4fa2', '#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#ffffbf',
    '#fee08b', '#fdae61', '#f46d43', '#d53e4f', '#9e0142'],
};
// Default Color List for gradient view
// const defaultColorList = ["#d7191c",
// "#fdae61",
// "#ffffbf",
// "#abdda4",
// "#2b83ba"];
// Regular Expression for testing valid color values
const cssHexRegExp = new RegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$');
function HeatmapControl(options) {
  this.name = 'HeatmapControl';
  /*
        list:[
            {
                id: ,
                title: ,
                icon: ,
                content:string
                isExpand:,

            }
        ]
    */
  this.setting = {
    fields: null,
    mode: null,
    currentField: null,
    onChange: null,
    onOpacityChange: null,
  };

  extend(this.setting, options);

  // attach container element
  this.elt = document.getElementById(this.setting.id);
  if (!this.elt && this.setting.element) {
    this.elt = this.setting.element;
  }

  if (!this.elt) {
    // create main
    this.elt = document.createElement('div');
  }

  // create UI
  this.__refresh();
}
/**
 * @private
 * __refresh refresh UI part according to this.setting
 */
HeatmapControl.prototype.__refresh = function() {
  empty(this.elt);
  this.elt.classList.add('hmc-container');
  this.rangeSliders = {};
  const template = `

         Gradient <input type='checkbox' value='gradient' ${
          this.setting.mode == 'gradient' ? 'checked' : ''
} />

        <select></select>

        Properties:

        Opacity:

         Color <input id='heatMapColor' type='color' value= ${
          this.setting.mode === 'gradient' ?
            '#1034A6' :
            $CAMIC.viewer.heatmap._color
} />

        # of Intervals <input id='legendIntervals' type='number' class='range-enforced' value=${
          this.setting.mode === 'gradient' ? $CAMIC.viewer.heatmap._colors.length : 5
} min='2' max='10'/>

    `;
  this.elt.innerHTML = template;
  const checkbox = this.elt.querySelector('.mode-panel input[type=checkbox]');
  checkbox.addEventListener('change', this._modeChanged.bind(this));

  const color = this.elt.querySelector('.color-panel input[type=color]');
  color.addEventListener('input', this._colorChanged.bind(this));

  this.rangeSliders = {};
  createSelect(this.elt.querySelector('.sel-field-panel select'), this.setting.fields, this.setting.currentField);
  this.elt.querySelector('.sel-field-panel select').addEventListener('change', this._selChanged.bind(this));
  if (this.setting.mode=='binal') this.elt.querySelector('.sel-field-panel').style.display='none';

  const fieldsPanel = this.elt.querySelector('.fields-panel');
  this.setting.fields.forEach((f)=>{
    this.rangeSliders[f.name] = createField(fieldsPanel, f, this.__change.bind(this));
  }, this);
  this.opacitySliders = {};
  const opacitiesPanel = this.elt.querySelector('.opacity-panel');
  this.setting.opacities.forEach((f)=>{
    this.opacitySliders[f.name] = createOpacities(opacitiesPanel, f, this.__opacityChange.bind(this));
  }, this);

  const colorsLegendPanel = this.elt.querySelector('.colors-legend-panel');
  // min max logic for valid number of intervals
  $(colorsLegendPanel).find('#legendIntervals').on('change', function(e) {
    const min=parseFloat($(this).attr('min'));
    const max=parseFloat($(this).attr('max'));
    const curr=parseFloat($(this).val());
    if (curr > max) {
      $(this).val(max); var changed=true;
    }
    if (curr < min) {
      $(this).val(min); var changed=true;
    }
    if (changed) {
      $warning = $(colorsLegendPanel).find('.warning');
      $warning.text('Only values in ' + min + ' through ' + max + ' allowed.');
      $warning.show();
      $warning.fadeOut(4500);
    }
  });

  const legendIntervalsInput = colorsLegendPanel.querySelector('#legendIntervals');
  // Setting default value of intervals
  legendIntervalsInput.value =
    this.setting.mode === 'gradient' ? $CAMIC.viewer.heatmap._colors.length : 5;
  const noOfIntervals = legendIntervalsInput.value;

  const legendsContainer = colorsLegendPanel.querySelector('.legends');
  createIntervalInputs(legendsContainer, noOfIntervals, this._legendColorsChanged.bind(this) );
  legendIntervalsInput.addEventListener('change', ()=>{
    createIntervalInputs(legendsContainer, legendIntervalsInput.value, this._legendColorsChanged.bind(this));
    this._legendColorsChanged();
  });

  this._modeChanged(false);
};

// called when model panel checkbox value changes
HeatmapControl.prototype._modeChanged = function(flag = true) {
  const mode = this.elt.querySelector(`.mode-panel input[type=checkbox]`).checked;

  if (!mode) {// binal
    this.elt.querySelector('.color-panel').style.display='';
    this.elt.querySelector('.colors-legend-panel').style.display='none';
    this.elt.querySelector('.sel-field-panel').style.display='none';
    this.setting.fields.forEach( (f)=> {
      // statements
      this.rangeSliders[f.name].slider.parentNode.style.display='';
      this.rangeSliders[f.name].disabled(false);
    }, this);
  } else { // gradient
    this.elt.querySelector('.color-panel').style.display='none';
    this.elt.querySelector('.colors-legend-panel').style.display='';
    this.elt.querySelector('.sel-field-panel').style.display='';
    const selectedField = this.elt.querySelector('.sel-field-panel select').value;
    this.rangeSliders[selectedField].slider.parentNode.style.display='';
    this.rangeSliders[selectedField].disabled(false);
    this.setting.fields.forEach( (f)=> {
      // statements
      if (f.name !== selectedField) {
        this.rangeSliders[f.name].slider.parentNode.style.display='none';
        this.rangeSliders[f.name].disabled(true);
      }
    }, this);
  }
  if (flag) this.__change.call(this);
};
// To validate and update heatmap color in binal mode
HeatmapControl.prototype._colorChanged = function() {
  const color = this.elt.querySelector('#heatMapColor').value;
  if (cssHexRegExp.test(color)) {
    this.__change.call(this);
  }
};
// To validate and update heatmap colors in gradient mode
HeatmapControl.prototype._legendColorsChanged = function() {
  let valid = true;
  const colorLegendPanel = this.elt.querySelector('.colors-legend-panel');
  $(colorLegendPanel.querySelector('.legends'))
      .children()
      .each(function(index, colorDiv) {
        if (cssHexRegExp.test(colorDiv.querySelector('input').value)===false) {
          valid = false;
          return;
        }
      });
  if (valid) this.__change.call(this);
};

// called when selected value changed
HeatmapControl.prototype._selChanged = function(e) {
  const selectedField = this.elt.querySelector('.sel-field-panel select').value;
  this.rangeSliders[selectedField].slider.parentNode.style.display = '';
  this.rangeSliders[selectedField].disabled(false);
  this.setting.fields.forEach( (f)=> {
    // statements
    if (f.name !== selectedField) {
      this.rangeSliders[f.name].slider.parentNode.style.display = 'none';
      this.rangeSliders[f.name].disabled(true);
    }
  }, this);
  this.resize.call(this);
  this.__change.call(this);
};

// call function on resizing slider
HeatmapControl.prototype.resize = function() {
  this.setting.fields.forEach( (f) => {
    // statements
    this.rangeSliders[f.name].onResize();
  }, this);

  this.setting.opacities.forEach( (f) => {
    // statements
    this.opacitySliders[f.name].onResize();
  }, this);
};

// called when any element changes
HeatmapControl.prototype.__change = function() {
  if (this.setting.onChange && typeof this.setting.onChange === 'function') {
    const mode = this.elt.querySelector(`.mode-panel input[type=checkbox]`).checked;
    const color = this.elt.querySelector('#heatMapColor').value;
    const colorLegendPanel = this.elt.querySelector('.colors-legend-panel');
    const colors = getColors(colorLegendPanel.querySelector('.legends'));
    const fields = [];
    const field = {};
    const data = {
      mode: mode?'gradient':'binal',
      color: color,
      colors: colors,
    };

    if (!mode) {
      this.setting.fields.forEach( (f)=> {
        fields.push({name: f.name, range: this.rangeSliders[f.name].getValue()});
      });
      data.fields = fields;
    } else {
      const selectedField = this.elt.querySelector('.sel-field-panel select').value;
      field.name = selectedField;
      field.range = this.rangeSliders[selectedField].getValue();
      data.field = field;
    }
    this.setting.onChange(data);
    this.resize.call(this);
  }
};

// should get all elements that have classname "fields-panel"
// check if it works as return is missing
HeatmapControl.prototype.getThreshold = function() {
  elt.querySelectorAll('.fields-panel > div').filter((elt)=>{});
};

// update changed value of opacity to setting
HeatmapControl.prototype.__opacityChange = function() {
  if (this.setting.onChange && typeof this.setting.onChange === 'function') {
    const data = {};
    this.setting.opacities.forEach( (f) => {
      data[f.name] = this.opacitySliders[f.name].getValue()/100;
    });
    this.setting.onOpacityChange(data);
  }
};

// createSelect         - creates options for each field
// createField          - creates fields and their sliders
// createOpacities      - creates opacity field and it's slider
// createIntervalInputs - creates HTML Color inputs for given noOfIntervals

function createSelect(sel, fields, currentField = null) {
  fields.forEach((field) => {
    const option = document.createElement('option');

    option.text = field.name;
    option.value = field.name;

    sel.add(option);
    if (currentField) sel.value = currentField;
  });
}
function createField(container, field, changeFunc) {
  const div = document.createElement('div');
  const label = document.createElement('label');
  label.textContent = field.name;
  const slider = document.createElement('input');
  slider.type = 'text';
  slider.name = field.name;
  // rangeSlider.
  div.appendChild(label);
  div.appendChild(slider);
  // field value catch
  field.value = field.value || field.range || [0.05, 0, 1];
  const rs = new rangeSlider({
    target: slider,
    values: {min: field.range[0]*100 >> 0, max: field.range[1]*100 >> 0},
    step: 1,
    range: true,
    tooltip: false,
    scale: false,
    labels: false,
    set: [field.value[0]*100 >> 0, field.value[1]*100 >> 0],
    onChange: changeFunc,
  });
  container.appendChild(div);
  return rs;
}
function createOpacities(container, field, changeFunc) {
  const div = document.createElement('div');
  const label = document.createElement('label');
  label.textContent = field.name;
  const slider = document.createElement('input');
  slider.type = 'text';
  slider.name = field.name;
  // rangeSlider.
  div.appendChild(label);
  div.appendChild(slider);
  const rs = new rangeSlider({
    target: slider,
    values: {min: 0, max: 100},
    step: 1,
    range: false,
    tooltip: false,
    scale: false,
    labels: false,
    set: [field.value*100],
    onChange: changeFunc,
  });
  container.appendChild(div);
  return rs;
}
function createIntervalInputs(container, noOfIntervals, changeFunc) {
  // Empty the container
  let colors=[];
  while ( container.firstChild ) container.removeChild( container.firstChild );
  for (let i = 1; i <= noOfIntervals; i++) {
    const div = document.createElement('div');
    div.className = 'color-input-container';
    const label = document.createElement('label');
    label.textContent = `Interval ${i} `;
    label.className = 'color-input';
    const color = document.createElement('input');
    color.type = 'color';
    if (i <= $CAMIC.viewer.heatmap._colors.length) {
      color.value = $CAMIC.viewer.heatmap._colors[i - 1];
    } else {
      color.value = defaultColorList[i];
    }
    color.oninput = changeFunc;
    // Input for color legends.
    div.appendChild(label);
    div.appendChild(color);
    colors.push(color.value);
    container.appendChild(div);
  }
  $CAMIC.viewer.heatmap.setColors(colors);
}
// returns selected colors for intervals
function getColors(container) {
  const rs = [];
  $(container).children().each(function(index, colorDiv) {
    rs.push(colorDiv.querySelector('input').value);
  });
  return rs;
}
// returns index of color in defaultColorList for given position and no of intervals
function getGradientColorIndex(position, noOfIntervals) {
  return parseInt((position * (10 / noOfIntervals)) - 1);
}
<commit_msg>Update heatmapcontrol.js<commit_after>const defaultColorList = {
  3: ['#2b83ba', '#ffffbf', '#d7191c'],
  4: ['#2b83ba', '#abdda4', '#fdae61', '#d7191c'],
  5: ['#2b83ba', '#abdda4', '#ffffbf', '#fdae61', '#d7191c'],
  6: ['#3288bd', '#99d594', '#e6f598', '#fee08b', '#fc8d59', '#d53e4f'],
  7: ['#3288bd', '#99d594', '#e6f598', '#ffffbf', '#fee08b', '#fc8d59', '#d53e4f'],
  8: ['#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#fee08b', '#fdae61', '#f46d43', '#d53e4f'],
  9: ['#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#ffffbf', '#fee08b', '#fdae61', '#f46d43', '#d53e4f'],
  10: ['#5e4fa2', '#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#fee08b', '#fdae61', '#f46d43', '#d53e4f', '#9e0142'],
  11: ['#5e4fa2', '#3288bd', '#66c2a5', '#abdda4', '#e6f598', '#ffffbf',
    '#fee08b', '#fdae61', '#f46d43', '#d53e4f', '#9e0142'],
};
// Default Color List for gradient view
// const defaultColorList = ["#d7191c",
// "#fdae61",
// "#ffffbf",
// "#abdda4",
// "#2b83ba"];
// Regular Expression for testing valid color values
const cssHexRegExp = new RegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$');
function HeatmapControl(options) {
  this.name = 'HeatmapControl';
  /*
        list:[
            {
                id: ,
                title: ,
                icon: ,
                content:string
                isExpand:,

            }
        ]
    */
  this.setting = {
    fields: null,
    mode: null,
    currentField: null,
    onChange: null,
    onOpacityChange: null,
  };

  extend(this.setting, options);

  // attach container element
  this.elt = document.getElementById(this.setting.id);
  if (!this.elt && this.setting.element) {
    this.elt = this.setting.element;
  }

  if (!this.elt) {
    // create main
    this.elt = document.createElement('div');
  }

  // create UI
  this.__refresh();
}
/**
 * @private
 * __refresh refresh UI part according to this.setting
 */
HeatmapControl.prototype.__refresh = function() {
  empty(this.elt);
  this.elt.classList.add('hmc-container');
  this.rangeSliders = {};
  const template = `

         Gradient <input type='checkbox' value='gradient' ${
          this.setting.mode == 'gradient' ? 'checked' : ''
} />

        <select></select>

        Properties:

        Opacity:

         Color <input id='heatMapColor' type='color' value= ${
          this.setting.mode === 'gradient' ?
            '#1034A6' :
            $CAMIC.viewer.heatmap._color
} />

        # of Intervals <input id='legendIntervals' type='number' class='range-enforced' value=${
          this.setting.mode === 'gradient' ? $CAMIC.viewer.heatmap._colors.length : 5
} min='2' max='10'/>

    `;
  this.elt.innerHTML = template;
  const checkbox = this.elt.querySelector('.mode-panel input[type=checkbox]');
  checkbox.addEventListener('change', this._modeChanged.bind(this));

  const color = this.elt.querySelector('.color-panel input[type=color]');
  color.addEventListener('input', this._colorChanged.bind(this));

  this.rangeSliders = {};
  createSelect(this.elt.querySelector('.sel-field-panel select'), this.setting.fields, this.setting.currentField);
  this.elt.querySelector('.sel-field-panel select').addEventListener('change', this._selChanged.bind(this));
  if (this.setting.mode=='binal') this.elt.querySelector('.sel-field-panel').style.display='none';

  const fieldsPanel = this.elt.querySelector('.fields-panel');
  this.setting.fields.forEach((f)=>{
    this.rangeSliders[f.name] = createField(fieldsPanel, f, this.__change.bind(this));
  }, this);
  this.opacitySliders = {};
  const opacitiesPanel = this.elt.querySelector('.opacity-panel');
  this.setting.opacities.forEach((f)=>{
    this.opacitySliders[f.name] = createOpacities(opacitiesPanel, f, this.__opacityChange.bind(this));
  }, this);

  const colorsLegendPanel = this.elt.querySelector('.colors-legend-panel');
  // min max logic for valid number of intervals
  $(colorsLegendPanel).find('#legendIntervals').on('change', function(e) {
    const min=parseFloat($(this).attr('min'));
    const max=parseFloat($(this).attr('max'));
    const curr=parseFloat($(this).val());
    if (curr > max) {
      $(this).val(max); var changed=true;
    }
    if (curr < min) {
      $(this).val(min); var changed=true;
    }
    if (changed) {
      $warning = $(colorsLegendPanel).find('.warning');
      $warning.text('Only values in ' + min + ' through ' + max + ' allowed.');
      $warning.show();
      $warning.fadeOut(4500);
    }
  });

  const legendIntervalsInput = colorsLegendPanel.querySelector('#legendIntervals');
  // Setting default value of intervals
  legendIntervalsInput.value =
    this.setting.mode === 'gradient' ? $CAMIC.viewer.heatmap._colors.length : 5;
  const noOfIntervals = legendIntervalsInput.value;

  const legendsContainer = colorsLegendPanel.querySelector('.legends');
  createIntervalInputs(legendsContainer, noOfIntervals, this._legendColorsChanged.bind(this) );
  legendIntervalsInput.addEventListener('change', ()=>{
    createIntervalInputs(legendsContainer, legendIntervalsInput.value, this._legendColorsChanged.bind(this));
    this._legendColorsChanged();
  });

  this._modeChanged(false);
};

// called when model panel checkbox value changes
HeatmapControl.prototype._modeChanged = function(flag = true) {
  const mode = this.elt.querySelector(`.mode-panel input[type=checkbox]`).checked;

  if (!mode) {// binal
    this.elt.querySelector('.color-panel').style.display='';
    this.elt.querySelector('.colors-legend-panel').style.display='none';
    this.elt.querySelector('.sel-field-panel').style.display='none';
    this.setting.fields.forEach( (f)=> {
      // statements
      this.rangeSliders[f.name].slider.parentNode.style.display='';
      this.rangeSliders[f.name].disabled(false);
    }, this);
  } else { // gradient
    this.elt.querySelector('.color-panel').style.display='none';
    this.elt.querySelector('.colors-legend-panel').style.display='';
    this.elt.querySelector('.sel-field-panel').style.display='';
    const selectedField = this.elt.querySelector('.sel-field-panel select').value;
    this.rangeSliders[selectedField].slider.parentNode.style.display='';
    this.rangeSliders[selectedField].disabled(false);
    this.setting.fields.forEach( (f)=> {
      // statements
      if (f.name !== selectedField) {
        this.rangeSliders[f.name].slider.parentNode.style.display='none';
        this.rangeSliders[f.name].disabled(true);
      }
    }, this);
  }
  if (flag) this.__change.call(this);
};
// To validate and update heatmap color in binal mode
HeatmapControl.prototype._colorChanged = function() {
  const color = this.elt.querySelector('#heatMapColor').value;
  if (cssHexRegExp.test(color)) {
    this.__change.call(this);
  }
};
// To validate and update heatmap colors in gradient mode
HeatmapControl.prototype._legendColorsChanged = function() {
  let valid = true;
  const colorLegendPanel = this.elt.querySelector('.colors-legend-panel');
  $(colorLegendPanel.querySelector('.legends'))
      .children()
      .each(function(index, colorDiv) {
        if (cssHexRegExp.test(colorDiv.querySelector('input').value)===false) {
          valid = false;
          return;
        }
      });
  if (valid) this.__change.call(this);
};

// called when selected value changed
HeatmapControl.prototype._selChanged = function(e) {
  const selectedField = this.elt.querySelector('.sel-field-panel select').value;
  this.rangeSliders[selectedField].slider.parentNode.style.display = '';
  this.rangeSliders[selectedField].disabled(false);
  this.setting.fields.forEach( (f)=> {
    // statements
    if (f.name !== selectedField) {
      this.rangeSliders[f.name].slider.parentNode.style.display = 'none';
      this.rangeSliders[f.name].disabled(true);
    }
  }, this);
  this.resize.call(this);
  this.__change.call(this);
};

// call function on resizing slider
HeatmapControl.prototype.resize = function() {
  this.setting.fields.forEach( (f) => {
    // statements
    this.rangeSliders[f.name].onResize();
  }, this);

  this.setting.opacities.forEach( (f) => {
    // statements
    this.opacitySliders[f.name].onResize();
  }, this);
};

// called when any element changes
HeatmapControl.prototype.__change = function() {
  if (this.setting.onChange && typeof this.setting.onChange === 'function') {
    const mode = this.elt.querySelector(`.mode-panel input[type=checkbox]`).checked;
    const color = this.elt.querySelector('#heatMapColor').value;
    const colorLegendPanel = this.elt.querySelector('.colors-legend-panel');
    const colors = getColors(colorLegendPanel.querySelector('.legends'));
    const fields = [];
    const field = {};
    const data = {
      mode: mode?'gradient':'binal',
      color: color,
      colors: colors,
    };

    if (!mode) {
      this.setting.fields.forEach( (f)=> {
        fields.push({name: f.name, range: this.rangeSliders[f.name].getValue()});
      });
      data.fields = fields;
    } else {
      const selectedField = this.elt.querySelector('.sel-field-panel select').value;
      field.name = selectedField;
      field.range = this.rangeSliders[selectedField].getValue();
      data.field = field;
    }
    this.setting.onChange(data);
    this.resize.call(this);
  }
};

// should get all elements that have classname "fields-panel"
// check if it works as return is missing
HeatmapControl.prototype.getThreshold = function() {
  elt.querySelectorAll('.fields-panel > div').filter((elt)=>{});
};

// update changed value of opacity to setting
HeatmapControl.prototype.__opacityChange = function() {
  if (this.setting.onChange && typeof this.setting.onChange === 'function') {
    const data = {};
    this.setting.opacities.forEach( (f) => {
      data[f.name] = this.opacitySliders[f.name].getValue()/100;
    });
    this.setting.onOpacityChange(data);
  }
};

// createSelect         - creates options for each field
function createSelect(sel, fields, currentField = null) {
  fields.forEach((field) => {
    const option = document.createElement('option');

    option.text = field.name;
    option.value = field.name;

    sel.add(option);
    if (currentField) sel.value = currentField;
  });
}
// createField          - creates fields and their sliders
function createField(container, field, changeFunc) {
  const div = document.createElement('div');
  const label = document.createElement('label');
  label.textContent = field.name;
  const slider = document.createElement('input');
  slider.type = 'text';
  slider.name = field.name;
  // rangeSlider.
  div.appendChild(label);
  div.appendChild(slider);
  // field value catch
  field.value = field.value || field.range || [0.05, 0, 1];
  const rs = new rangeSlider({
    target: slider,
    values: {min: field.range[0]*100 >> 0, max: field.range[1]*100 >> 0},
    step: 1,
    range: true,
    tooltip: false,
    scale: false,
    labels: false,
    set: [field.value[0]*100 >> 0, field.value[1]*100 >> 0],
    onChange: changeFunc,
  });
  container.appendChild(div);
  return rs;
}
// createOpacities      - creates opacity field and it's slider
function createOpacities(container, field, changeFunc) {
  const div = document.createElement('div');
  const label = document.createElement('label');
  label.textContent = field.name;
  const slider = document.createElement('input');
  slider.type = 'text';
  slider.name = field.name;
  // rangeSlider.
  div.appendChild(label);
  div.appendChild(slider);
  const rs = new rangeSlider({
    target: slider,
    values: {min: 0, max: 100},
    step: 1,
    range: false,
    tooltip: false,
    scale: false,
    labels: false,
    set: [field.value*100],
    onChange: changeFunc,
  });
  container.appendChild(div);
  return rs;
}
// createIntervalInputs - creates HTML Color inputs for given noOfIntervals
function createIntervalInputs(container, noOfIntervals, changeFunc) {
  // Empty the container
  let colors=[];
  while ( container.firstChild ) container.removeChild( container.firstChild );
  for (let i = 1; i <= noOfIntervals; i++) {
    const div = document.createElement('div');
    div.className = 'color-input-container';
    const label = document.createElement('label');
    label.textContent = `Interval ${i} `;
    label.className = 'color-input';
    const color = document.createElement('input');
    color.type = 'color';
    if (i <= $CAMIC.viewer.heatmap._colors.length) {
      color.value = $CAMIC.viewer.heatmap._colors[i - 1];
    } else {
      color.value = defaultColorList[i];
    }
    color.oninput = changeFunc;
    // Input for color legends.
    div.appendChild(label);
    div.appendChild(color);
    colors.push(color.value);
    container.appendChild(div);
  }
  $CAMIC.viewer.heatmap.setColors(colors);
}
// returns selected colors for intervals
function getColors(container) {
  const rs = [];
  $(container).children().each(function(index, colorDiv) {
    rs.push(colorDiv.querySelector('input').value);
  });
  return rs;
}
// returns index of color in defaultColorList for given position and no of intervals
function getGradientColorIndex(position, noOfIntervals) {
  return parseInt((position * (10 / noOfIntervals)) - 1);
}
<|endoftext|>


<commit_before>// Everything in this file uses child processes, because we're
// testing a command line utility.

var chain = require("slide").chain
var child_process = require("child_process")
var path = require("path")
  , testdir = __dirname
  , fs = require("graceful-fs")
  , npmpkg = path.dirname(testdir)
  , npmcli = path.resolve(npmpkg, "bin", "npm-cli.js")

var temp = process.env.TMPDIR
         || process.env.TMP
         || process.env.TEMP
         || ( process.platform === "win32"
            ? "c:\\windows\\temp"
            : "/tmp" )

temp = path.resolve(temp, "npm-test-" + process.pid)

var root = path.resolve(temp, "root")

var failures = 0
  , mkdir = require("mkdirp")
  , rimraf = require("rimraf")

var pathEnvSplit = process.platform === "win32" ? ";" : ":"
  , pathEnv = process.env.PATH.split(pathEnvSplit)
  , npmPath = process.platform === "win32" ? root : path.join(root, "bin")

pathEnv.unshift(npmPath, path.join(root, "node_modules", ".bin"))

// lastly, make sure that we get the same node that is being used to do
// run this script.  That's very important, especially when running this
// test file from in the node source folder.
pathEnv.unshift(path.dirname(process.execPath))

// the env for all the test installs etc.
var env = {}
Object.keys(process.env).forEach(function (i) {
  env[i] = process.env[i]
})
env.npm_config_prefix = root
env.npm_config_color = "always"
env.npm_config_global = "true"
// have to set this to false, or it'll try to test itself forever
env.npm_config_npat = "false"
env.PATH = pathEnv.join(pathEnvSplit)
env.NODE_PATH = path.join(root, "node_modules")

function cleanup (cb) {
  if (failures !== 0) return
  rimraf(root, function (er) {
    if (er) cb(er)
    mkdir(root, 0755, cb)
  })
}

function prefix (content, pref) {
  return pref + (content.trim().split(/\r?\n/).join("\n" + pref))
}

var execCount = 0
function exec (cmd, cwd, shouldFail, cb) {
  if (typeof shouldFail === "function") {
    cb = shouldFail, shouldFail = false
  }
  console.error("\n+"+cmd + (shouldFail ? " (expect failure)" : ""))

  // special: replace 'node' with the current execPath,
  // and 'npm' with the thing we installed.
  var cmdShow = cmd
  var npmReplace = path.resolve(npmPath, "npm")
  var nodeReplace = process.execPath
  if (process.platform === "win32") {
    npmReplace = '"' + npmReplace + '"'
    nodeReplace = '"' + nodeReplace + '"'
  }
  cmd = cmd.replace(/^npm /, npmReplace + " ")
  cmd = cmd.replace(/^node /, nodeReplace + " ")

  console.error("$$$$$$ cd %s; PATH=%s %s", cwd, env.PATH, cmd)

  child_process.exec(cmd, {cwd: cwd, env: env}, function (er, stdout, stderr) {
    console.error("$$$$$$ after command", cmd, cwd)
    if (stdout) {
      console.error(prefix(stdout, " 1> "))
    }
    if (stderr) {
      console.error(prefix(stderr, " 2> "))
    }

    execCount ++
    if (!shouldFail && !er || shouldFail && er) {
      // stdout = (""+stdout).trim()
      console.log("ok " + execCount + " " + cmdShow)
      return cb()
    } else {
      console.log("not ok " + execCount + " " + cmdShow)
      cb(new Error("failed "+cmdShow))
    }
  })
}

function execChain (cmds, cb) {
  chain(cmds.map(function (args) {
    return [exec].concat(args)
  }), cb)
}

function flatten (arr) {
  return arr.reduce(function (l, r) {
    return l.concat(r)
  }, [])
}

function setup (cb) {
  cleanup(function (er) {
    if (er) return cb(er)
    execChain([ [ "node \""+npmcli+"\" install \""+npmpkg+"\"", root ],
                [ "npm config set package-config:foo boo",  root ]
              ], cb)
  })
}

function main (cb) {
  console.log("# testing in %s", temp)
  console.log("# global prefix = %s", root)

  failures = 0

  process.chdir(testdir)
  var base = path.resolve(root, path.join("lib", "node_modules"))

  // get the list of packages
  var packages = fs.readdirSync(path.resolve(testdir, "packages"))
  packages = packages.filter(function (p) {
    return p && !p.match(/^\./)
  })

  installAllThenTestAll()

  function installAllThenTestAll () {
    var packagesToRm = packages.slice(0)
    if (process.platform !== "win32") {
      // Windows can't handle npm rm npm due to file-in-use issues.
      packagesToRm.push("npm")
    }

    chain(
        [ setup
        , [ exec, "npm install "+npmpkg, testdir ]
        , [ execChain, packages.map(function (p) {
              return [ "npm install packages/"+p, testdir ]
            }) ]
        , [ execChain, packages.map(function (p) {
              return [ "npm test -ddd", path.resolve(base, p) ]
            }) ]
        , [ execChain, packagesToRm.map(function (p) {
              return [ "npm rm "+p, root ]
            }) ]
        , installAndTestEach
        ]
      , cb
      )
  }

  function installAndTestEach (cb) {
    var thingsToChain = [
        setup
        , [ execChain, flatten(packages.map(function (p) {
              return [ [ "npm install packages/"+p, testdir ]
                     , [ "npm test", path.resolve(base, p) ]
                     , [ "npm rm "+p, root ] ]
            })) ]
      ]
    if (process.platform !== "win32") {
      // Windows can't handle npm rm npm due to file-in-use issues.
      thingsToChain.push([exec, "npm rm npm", testdir])
    }

    chain(thingsToChain, cb)
  }
}

main(function (er) {
  console.log("1.." + execCount)
  if (er) throw er
})
<commit_msg>legacy tests no longer poison user's cache<commit_after>// Everything in this file uses child processes, because we're
// testing a command line utility.

var chain = require("slide").chain
var child_process = require("child_process")
var path = require("path")
  , testdir = __dirname
  , fs = require("graceful-fs")
  , npmpkg = path.dirname(testdir)
  , npmcli = path.resolve(npmpkg, "bin", "npm-cli.js")

var temp = process.env.TMPDIR
         || process.env.TMP
         || process.env.TEMP
         || ( process.platform === "win32"
            ? "c:\\windows\\temp"
            : "/tmp" )

temp = path.resolve(temp, "npm-test-" + process.pid)

var root = path.resolve(temp, "root")
  , cache = path.resolve(temp, "npm_cache")

var failures = 0
  , mkdir = require("mkdirp")
  , rimraf = require("rimraf")

var pathEnvSplit = process.platform === "win32" ? ";" : ":"
  , pathEnv = process.env.PATH.split(pathEnvSplit)
  , npmPath = process.platform === "win32" ? root : path.join(root, "bin")

pathEnv.unshift(npmPath, path.join(root, "node_modules", ".bin"))

// lastly, make sure that we get the same node that is being used to do
// run this script.  That's very important, especially when running this
// test file from in the node source folder.
pathEnv.unshift(path.dirname(process.execPath))

// the env for all the test installs etc.
var env = {}
Object.keys(process.env).forEach(function (i) {
  env[i] = process.env[i]
})
env.npm_config_prefix = root
env.npm_config_color = "always"
env.npm_config_global = "true"
// have to set this to false, or it'll try to test itself forever
env.npm_config_npat = "false"
env.PATH = pathEnv.join(pathEnvSplit)
env.NODE_PATH = path.join(root, "node_modules")
env.npm_config_cache = cache

function cleanup (cb) {
  if (failures !== 0) return
  rimraf(root, function (er) {
    if (er) cb(er)
    mkdir(root, 0755, cb)
  })
}

function prefix (content, pref) {
  return pref + (content.trim().split(/\r?\n/).join("\n" + pref))
}

var execCount = 0
function exec (cmd, cwd, shouldFail, cb) {
  if (typeof shouldFail === "function") {
    cb = shouldFail, shouldFail = false
  }
  console.error("\n+"+cmd + (shouldFail ? " (expect failure)" : ""))

  // special: replace 'node' with the current execPath,
  // and 'npm' with the thing we installed.
  var cmdShow = cmd
  var npmReplace = path.resolve(npmPath, "npm")
  var nodeReplace = process.execPath
  if (process.platform === "win32") {
    npmReplace = '"' + npmReplace + '"'
    nodeReplace = '"' + nodeReplace + '"'
  }
  cmd = cmd.replace(/^npm /, npmReplace + " ")
  cmd = cmd.replace(/^node /, nodeReplace + " ")

  console.error("$$$$$$ cd %s; PATH=%s %s", cwd, env.PATH, cmd)

  child_process.exec(cmd, {cwd: cwd, env: env}, function (er, stdout, stderr) {
    console.error("$$$$$$ after command", cmd, cwd)
    if (stdout) {
      console.error(prefix(stdout, " 1> "))
    }
    if (stderr) {
      console.error(prefix(stderr, " 2> "))
    }

    execCount ++
    if (!shouldFail && !er || shouldFail && er) {
      // stdout = (""+stdout).trim()
      console.log("ok " + execCount + " " + cmdShow)
      return cb()
    } else {
      console.log("not ok " + execCount + " " + cmdShow)
      cb(new Error("failed "+cmdShow))
    }
  })
}

function execChain (cmds, cb) {
  chain(cmds.map(function (args) {
    return [exec].concat(args)
  }), cb)
}

function flatten (arr) {
  return arr.reduce(function (l, r) {
    return l.concat(r)
  }, [])
}

function setup (cb) {
  cleanup(function (er) {
    if (er) return cb(er)
    execChain([ [ "node \""+npmcli+"\" install \""+npmpkg+"\"", root ],
                [ "npm config set package-config:foo boo",  root ]
              ], cb)
  })
}

function main (cb) {
  console.log("# testing in %s", temp)
  console.log("# global prefix = %s", root)

  failures = 0

  process.chdir(testdir)
  var base = path.resolve(root, path.join("lib", "node_modules"))

  // get the list of packages
  var packages = fs.readdirSync(path.resolve(testdir, "packages"))
  packages = packages.filter(function (p) {
    return p && !p.match(/^\./)
  })

  installAllThenTestAll()

  function installAllThenTestAll () {
    var packagesToRm = packages.slice(0)
    if (process.platform !== "win32") {
      // Windows can't handle npm rm npm due to file-in-use issues.
      packagesToRm.push("npm")
    }

    chain(
        [ setup
        , [ exec, "npm install "+npmpkg, testdir ]
        , [ execChain, packages.map(function (p) {
              return [ "npm install packages/"+p, testdir ]
            }) ]
        , [ execChain, packages.map(function (p) {
              return [ "npm test -ddd", path.resolve(base, p) ]
            }) ]
        , [ execChain, packagesToRm.map(function (p) {
              return [ "npm rm "+p, root ]
            }) ]
        , installAndTestEach
        ]
      , cb
      )
  }

  function installAndTestEach (cb) {
    var thingsToChain = [
        setup
        , [ execChain, flatten(packages.map(function (p) {
              return [ [ "npm install packages/"+p, testdir ]
                     , [ "npm test", path.resolve(base, p) ]
                     , [ "npm rm "+p, root ] ]
            })) ]
      ]
    if (process.platform !== "win32") {
      // Windows can't handle npm rm npm due to file-in-use issues.
      thingsToChain.push([exec, "npm rm npm", testdir])
    }

    chain(thingsToChain, cb)
  }
}

main(function (er) {
  console.log("1.." + execCount)
  if (er) throw er
})
<|endoftext|>