Skip to content
Snippets Groups Projects
ReviewPage.jsx 12.5 KiB
Newer Older
import React from 'react'
Audrey Hamelers's avatar
Audrey Hamelers committed
import { Query, Mutation } from 'react-apollo'
import styled from 'styled-components'
Audrey Hamelers's avatar
Audrey Hamelers committed
import ReactHtmlParser from 'react-html-parser'
import { th, lighten, darken } from '@pubsweet/ui-toolkit'
import { Action, Icon, Button, H1, H2 } from '@pubsweet/ui'
import {
Audrey Hamelers's avatar
Audrey Hamelers committed
  Loading,
  LoadingIcon,
Audrey Hamelers's avatar
Audrey Hamelers committed
  Buttons,
  CloseButton,
  PreviewPage,
  PreviewPanel,
  EditPanel,
  PanelHeader,
  PanelContent,
  Toggle as Toggles,
} from '../ui'
import UploadFiles, {
  SubmissionTypes,
  ReviewTypes,
  AllTypes,
} from '../upload-files'
import ManuscriptPreview from '../ManuscriptPreview'
import PDFViewer from '../pdf-viewer'
ahamelers's avatar
ahamelers committed
import { GET_MANUSCRIPT } from '../operations'
Audrey Hamelers's avatar
Audrey Hamelers committed
import SubmissionHeader from '../SubmissionHeader'
ahamelers's avatar
ahamelers committed
import HTMLPreview from './HTMLPreview'
import ReviewForm from './ReviewForm'
import Annotator from './Annotator'
import ReviewInstructions from './ReviewInstructions'
import { CONVERT_XML, GET_REVIEWS } from './operations'

const PreviewPageDiv = styled(PreviewPage)`
ahamelers's avatar
ahamelers committed
  & > div > div {
    overflow: auto;
  }
Audrey Hamelers's avatar
Audrey Hamelers committed
  & > div > div > div:last-child {
    height: calc(100% - (${th('gridUnit')} * 19));
    position: relative;
ahamelers's avatar
ahamelers committed
  }
  .show-mobile {
    display: none;
  }
ahamelers's avatar
ahamelers committed
  .pad {
    padding: 0 calc(2 * ${th('gridUnit')});
  }
  @media screen and (max-width: 870px) {
    .show-mobile {
ahamelers's avatar
ahamelers committed
      display: inline-block;
    }
    .hide-mobile {
      display: none;
    }
Audrey Hamelers's avatar
Audrey Hamelers committed
  }
`
const PreviewPanelHeader = styled(PanelHeader)`
  height: calc(${th('gridUnit')} * 10);
ahamelers's avatar
ahamelers committed

  @media screen and (max-width: 870px) {
    height: auto;
  }
Audrey Hamelers's avatar
Audrey Hamelers committed
`
const Toggle = styled(Toggles)`
Audrey Hamelers's avatar
Audrey Hamelers committed
  height: calc(${th('gridUnit')} * 7);
ahamelers's avatar
ahamelers committed
  &.hide-mobile button {
    margin-left: calc(${th('gridUnit')} * 3);
  }
  @media screen and (max-width: 870px) {
    height: auto;
    margin-bottom: calc(${th('gridUnit')} * 3);
  }
const PreviewError = styled.div`
  border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorError')};
  background-color: ${lighten('colorError', 40)};
  padding: ${th('gridUnit')} calc(${th('gridUnit')} * 2);
  margin: ${th('gridUnit')} 0;
const BannerDiv = styled.div`
  position: absolute;
  top: 0;
  right: calc(${th('gridUnit')} * 4);
  left: calc(${th('gridUnit')} * 2);
  z-index: 3;
  padding: calc(${th('gridUnit')} * 3) calc(${th('gridUnit')} * 2);
  background-color: ${darken('colorBackgroundHue', 7)};
  border: ${th('borderWidth')} ${th('borderStyle')} ${th('colorBorder')};
  display: flex;
  align-items: start;
  justify-content: space-between;
  * {
    font-size: ${th('fontSizeBaseSmall')};
  }
  & > div {
    display: flex;
    align-items: flex-start;
  }
`
const AnnotatePDF = Annotator(PDFViewer)
const AnnotateHTML = Annotator(HTMLPreview)

const Banner = ({ close, open, ...props }) => (
  <BannerDiv {...props}>
    <div>
      <Icon size={2}>info</Icon>
      <div>
        {`Highlight text and click the pencil icon button to create an annotation and report errors.`}
        <Action onClick={open}>
          Click here to view detailed instructions.
        </Action>
      </div>
    </div>
    <CloseButton onClick={close} size={3} />
  </BannerDiv>
)

Audrey Hamelers's avatar
Audrey Hamelers committed
class Review extends React.Component {
  state = {
    pane: this.props.currentUser.admin ? 'files' : 'web',
    instruct: true,
    banner: false,
    showManuscript: false,
    showAll: false,
Audrey Hamelers's avatar
Audrey Hamelers committed
  }
  componentDidMount() {
    const hidePopup = document.cookie.split('; ').reduce((r, v) => {
      const parts = v.split('=')
      return parts[0] === 'hide-review-popup' ? decodeURIComponent(parts[1]) : r
    }, '')
    if (
      hidePopup &&
      ['xml-qa', 'xml-review'].includes(this.props.manuscript.status)
    ) {
      this.setState({ instruct: false, banner: true })
Audrey Hamelers's avatar
Audrey Hamelers committed
  render() {
    const { manuscript, currentUser, reviews } = this.props
    const review = reviews.find(r => !r.deleted) || null
Audrey Hamelers's avatar
Audrey Hamelers committed
    const { files: allfiles, status, teams } = manuscript
Audrey Hamelers's avatar
Audrey Hamelers committed
    const sourceFile = allfiles.find(f => f.type === 'manuscript')
Audrey Hamelers's avatar
Audrey Hamelers committed
    const files = allfiles.filter(
Audrey Hamelers's avatar
Audrey Hamelers committed
      f => !f.type || ReviewTypes.some(rev => rev.value === f.type),
Audrey Hamelers's avatar
Audrey Hamelers committed
    )
    const originalFiles = allfiles.filter(f =>
      SubmissionTypes.some(sub => sub.value === f.type),
Audrey Hamelers's avatar
Audrey Hamelers committed
    )
    const html = files.find(file => file.type === 'tempHTML')
Audrey Hamelers's avatar
Audrey Hamelers committed
    const pdf = files.find(f => f.type === 'pdf4load' || f.type === 'pdf4print')
    const xml = files.find(f => f.type === 'PMC') || null
ahamelers's avatar
ahamelers committed
    const reviewer = teams.find(t => t.role === 'reviewer').teamMembers[0]
    const { banner, pane, instruct, showManuscript, showAll } = this.state
ahamelers's avatar
ahamelers committed
    if (
      !currentUser.admin &&
ahamelers's avatar
ahamelers committed
      !(currentUser.external && manuscript.status === 'xml-qa') &&
ahamelers's avatar
ahamelers committed
      !(
        currentUser.id === reviewer.user.id &&
        manuscript.status === 'xml-review'
      )
    ) {
      this.props.history.push('/')
      return null
    }
Audrey Hamelers's avatar
Audrey Hamelers committed
    return (
      <PreviewPageDiv>
        {instruct && (
          <ReviewInstructions
            close={() => this.setState({ instruct: false })}
          />
        )}
ahamelers's avatar
ahamelers committed
        <PreviewPanel
          style={{
            flex: showManuscript && '1 1 750px',
            width: showManuscript && '50%',
          }}
        >
          <div style={{ maxWidth: showManuscript && '750px' }}>
Audrey Hamelers's avatar
Audrey Hamelers committed
            <PreviewPanelHeader>
              <H1>Review web versions of manuscript</H1>
            </PreviewPanelHeader>
            <Toggle>
Audrey Hamelers's avatar
Audrey Hamelers committed
              {currentUser.admin && (
Audrey Hamelers's avatar
Audrey Hamelers committed
                <Action
                  className={pane === 'files' ? 'current' : ''}
Audrey Hamelers's avatar
Audrey Hamelers committed
                  onClick={() => this.setState({ pane: 'files' })}
                >
Audrey Hamelers's avatar
Audrey Hamelers committed
                </Action>
Audrey Hamelers's avatar
Audrey Hamelers committed
              )}
Audrey Hamelers's avatar
Audrey Hamelers committed
              <Action
                className={pane === 'web' ? 'current' : ''}
ahamelers's avatar
ahamelers committed
                onClick={() => this.setState({ pane: 'web' })}
Audrey Hamelers's avatar
Audrey Hamelers committed
              >
Audrey Hamelers's avatar
Audrey Hamelers committed
              </Action>
              <Action
                className={pane === 'pdf' ? 'current' : ''}
Audrey Hamelers's avatar
Audrey Hamelers committed
                onClick={() => this.setState({ pane: 'pdf' })}
              >
Audrey Hamelers's avatar
Audrey Hamelers committed
              </Action>
              <Action
Audrey Hamelers's avatar
Audrey Hamelers committed
                className={
Audrey Hamelers's avatar
Audrey Hamelers committed
                  pane === 'original' ? 'current show-mobile' : 'show-mobile'
Audrey Hamelers's avatar
Audrey Hamelers committed
                }
                onClick={() => this.setState({ pane: 'original' })}
                primary={pane !== 'original'}
              >
                Submitted file
Audrey Hamelers's avatar
Audrey Hamelers committed
              </Action>
            </Toggle>
Audrey Hamelers's avatar
Audrey Hamelers committed
            <PanelContent>
              {pane === 'files' && (
Audrey Hamelers's avatar
Audrey Hamelers committed
                <Mutation
                  mutation={CONVERT_XML}
                  refetchQueries={() => [
                    {
                      query: GET_MANUSCRIPT,
                      variables: { id: manuscript.id },
                    },
                  ]}
                >
Audrey Hamelers's avatar
Audrey Hamelers committed
                  {(convertXML, { data }) => {
                    const generatePreviews = async () => {
                      await convertXML({
                        variables: { id: xml.id },
                      })
                    }
                    return (
                      <React.Fragment>
                        <Buttons left top>
                          <Button disabled={!xml} onClick={generatePreviews}>
                            Regenerate previews
                          </Button>
                          <Button
                            onClick={() => this.setState({ showAll: !showAll })}
                          >
                            {`${showAll ? 'Hide' : 'Show'} submission files`}
                          </Button>
                        </Buttons>
Audrey Hamelers's avatar
Audrey Hamelers committed
                        {manuscript.formState && (
Audrey Hamelers's avatar
Audrey Hamelers committed
                          <PreviewError>
Audrey Hamelers's avatar
Audrey Hamelers committed
                            {ReactHtmlParser(manuscript.formState)}
Audrey Hamelers's avatar
Audrey Hamelers committed
                          </PreviewError>
                        )}
                        <UploadFiles
                          checked
                          files={showAll ? allfiles : files}
                          manuscript={manuscript.id}
                          types={showAll ? AllTypes : ReviewTypes}
Audrey Hamelers's avatar
Audrey Hamelers committed
                        />
                        <Buttons left top>
                          <Button disabled={!xml} onClick={generatePreviews}>
                            Regenerate previews
                          </Button>
                          <Button
                            onClick={() => this.setState({ showAll: !showAll })}
                          >
                            {`${showAll ? 'Hide' : 'Show'} submission files`}
                          </Button>
                        </Buttons>
                      </React.Fragment>
                    )
                  }}
                </Mutation>
Audrey Hamelers's avatar
Audrey Hamelers committed
              )}
              {pane === 'pdf' && pdf && (
                <React.Fragment>
                  {status === 'tagging' ? (
                    <PDFViewer url={pdf.url} />
                  ) : (
                    <React.Fragment>
                      {banner && (
                        <Banner
                          close={() => this.setState({ banner: false })}
                          open={() => this.setState({ instruct: true })}
                          style={{ top: '1.5rem' }}
                        />
                      )}
                      <AnnotatePDF
                        file={pdf}
                        reload={this.props.reload}
                        revId={(review && review.id) || null}
                        userId={currentUser.id}
                      />
                    </React.Fragment>
                  )}
                </React.Fragment>
Audrey Hamelers's avatar
Audrey Hamelers committed
              )}
              {pane === 'web' && html && (
                <React.Fragment>
                  {status === 'tagging' ? (
                    <HTMLPreview url={html.url} />
                  ) : (
                    <React.Fragment>
                      {banner && (
                        <Banner
                          close={() => this.setState({ banner: false })}
                          open={() => this.setState({ instruct: true })}
                        />
                      )}
                      <AnnotateHTML
                        file={html}
                        reload={this.props.reload}
                        revId={(review && review.id) || null}
                        userId={currentUser.id}
                      />
                    </React.Fragment>
                  )}
                </React.Fragment>
Audrey Hamelers's avatar
Audrey Hamelers committed
              )}
              {pane === 'original' && sourceFile && (
                <ManuscriptPreview file={sourceFile} />
              )}
            </PanelContent>
          </div>
        </PreviewPanel>
ahamelers's avatar
ahamelers committed
        <EditPanel
          style={{
            flex: showManuscript && '1 1 750px',
            width: showManuscript && '50%',
          }}
        >
          <div style={{ maxWidth: showManuscript && '750px' }}>
Audrey Hamelers's avatar
Audrey Hamelers committed
            <PreviewPanelHeader>
              <H2>Compare &amp; approve</H2>
            </PreviewPanelHeader>
ahamelers's avatar
ahamelers committed
            <Toggle className="hide-mobile">
              <Button
                onClick={() =>
                  this.setState({ showManuscript: !showManuscript })
                }
              >
                {showManuscript
                  ? 'Back to approval form'
ahamelers's avatar
ahamelers committed
                  : 'Compare with submitted file'}
              </Button>
            </Toggle>
            <PanelContent className={showManuscript && 'pad'}>
              {showManuscript && sourceFile ? (
                <ManuscriptPreview file={sourceFile} />
              ) : (
                <ReviewForm
Audrey Hamelers's avatar
Audrey Hamelers committed
                  files={originalFiles}
ahamelers's avatar
ahamelers committed
                  manuscript={manuscript}
Audrey Hamelers's avatar
Audrey Hamelers committed
                  previews={!!html && !!pdf}
                  reviews={reviews}
ahamelers's avatar
ahamelers committed
                />
              )}
Audrey Hamelers's avatar
Audrey Hamelers committed
            </PanelContent>
Audrey Hamelers's avatar
Audrey Hamelers committed
          </div>
        </EditPanel>
      </PreviewPageDiv>
    )
  }
}

const ReviewWithHeader = SubmissionHeader(Review)

const ReviewPage = ({ match, ...props }) => (
  <Query
    fetchPolicy="cache-and-network"
    query={GET_MANUSCRIPT}
    variables={{ id: match.params.id }}
  >
    {({ data: { manuscript }, loading: loadingOne }) => (
      <Query query={GET_REVIEWS} variables={{ manuscriptId: match.params.id }}>
        {({ data, loading: loadingTwo, refetch }) => {
          if (loadingOne || !manuscript) {
            return (
              <Loading>
                <LoadingIcon />
              </Loading>
            )
          }
          return (
            <ReviewWithHeader
              manuscript={manuscript}
              match={match}
              reload={() => refetch()}
              reviews={(data && data.reviewsByMId) || []}
              {...props}
            />
          )
        }}
      </Query>
    )}
Audrey Hamelers's avatar
Audrey Hamelers committed
  </Query>
)

export default ReviewPage