diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3d0d7d7fe19f77b57bfcd26fd8179d5ae69820e8..f4e0a163dce1a9f40e1caf582907cebd36733c40 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,7 +11,7 @@ stages: - demo build: - image: docker:latest + image: docker:stable stage: build script: - docker version diff --git a/packages/component-manuscript/src/atoms/index.js b/packages/component-manuscript/src/atoms/index.js index 190da931750dcaef873126324b201affb1aee83f..ca2cc52d61272c7ded2a6a651140939f5185e463 100644 --- a/packages/component-manuscript/src/atoms/index.js +++ b/packages/component-manuscript/src/atoms/index.js @@ -30,7 +30,7 @@ const Container = styled.div` const SideBar = styled.div` flex: ${({ flex }) => flex || 1}; background-color: ${th('colorBackground')}; - padding: 0 ${th('subGridUnit')}; + padding: ${th('subGridUnit')}; ` const Header = styled.div` diff --git a/packages/component-manuscript/src/components/Authors.js b/packages/component-manuscript/src/components/Authors.js new file mode 100644 index 0000000000000000000000000000000000000000..afcf825b1883a752195249558f7be15e6884855e --- /dev/null +++ b/packages/component-manuscript/src/components/Authors.js @@ -0,0 +1,67 @@ +import React from 'react' +import { th } from '@pubsweet/ui' +import styled from 'styled-components' + +const TR = ({ name, email, affiliation }) => ( + <Row> + <td>{name}</td> + <td>{email}</td> + <td>{affiliation}</td> + </Row> +) + +const Authors = ({ authors }) => ( + <Table> + <thead> + <tr> + <td>Full Name</td> + <td>Email</td> + <td>Affiliation</td> + </tr> + </thead> + <tbody> + {authors.map( + ({ firstName = '', lastName = '', email = '', affiliation = '' }) => ( + <TR + affiliation={affiliation} + email={email} + key={email} + name={`${firstName} ${lastName}`} + /> + ), + )} + </tbody> + </Table> +) + +export default Authors + +// #region styled-components +const Row = styled.tr` + border-bottom: ${th('borderDefault')}; + color: ${th('colorPrimary')}; + font-family: ${th('fontReading')}; + font-size: ${th('fontSizeBaseSmall')}; + height: 40px; + text-align: left; + &:hover { + background-color: ${th('backgroundColorReverse')}; + } +` + +const Table = styled.table` + border-spacing: 0; + border-collapse: collapse; + width: 100%; + + & thead tr { + border-bottom: ${th('borderDefault')}; + color: ${th('colorPrimary')}; + font-family: ${th('fontReading')}; + font-size: ${th('fontSizeBaseSmall')}; + font-weight: bold; + height: 40px; + text-align: left; + } +` +// #endregion diff --git a/packages/component-manuscript/src/components/Expandable.js b/packages/component-manuscript/src/components/Expandable.js new file mode 100644 index 0000000000000000000000000000000000000000..f4602d7da1f3d0999d6b360a2ad90d1b1a175723 --- /dev/null +++ b/packages/component-manuscript/src/components/Expandable.js @@ -0,0 +1,68 @@ +import React from 'react' +import styled from 'styled-components' +import { th, Icon } from '@pubsweet/ui' +import { compose, withState, withHandlers } from 'recompose' + +const Expandable = ({ expanded, label, children, toggle }) => ( + <Root expanded={expanded}> + <Header onClick={toggle}> + <Chevron expanded={expanded}> + <Icon primary size={3}> + chevron_up + </Icon> + </Chevron> + <SectionLabel>{label}</SectionLabel> + </Header> + {expanded && <ChildrenContainer>{children}</ChildrenContainer>} + </Root> +) + +export default compose( + withState('expanded', 'setExpanded', ({ startExpanded }) => startExpanded), + withHandlers({ + toggle: ({ setExpanded }) => () => { + setExpanded(e => !e) + }, + }), +)(Expandable) + +// #region styled-components +const Chevron = styled.div` + align-items: center; + height: calc(${th('subGridUnit')} * 5); + display: flex; + justify-content: center; + transform: ${({ expanded }) => `rotateZ(${expanded ? 0 : 180}deg)`}; + transition: transform 0.2s; + width: calc(${th('subGridUnit')} * 5); +` + +const SectionLabel = styled.span` + color: ${th('colorPrimary')}; + font-family: ${th('fontHeading')}; + font-size: ${th('fontSizeBase')}; + text-transform: uppercase; +` + +const ChildrenContainer = styled.div` + cursor: default; + padding: calc(${th('subGridUnit')} * 3); + padding-top: 0; +` + +const Header = styled.div` + align-items: center; + border-bottom: ${th('borderDefault')}; + margin-bottom: calc(${th('subGridUnit')} * 3); + cursor: pointer; + display: flex; + justify-content: flex-start; +` + +const Root = styled.div` + cursor: pointer; + display: flex; + flex-direction: column; + transition: all 0.3s; +` +// #endregion diff --git a/packages/component-manuscript/src/components/Files.js b/packages/component-manuscript/src/components/Files.js new file mode 100644 index 0000000000000000000000000000000000000000..cd0aa6f75f382618e3689d15498202dd337cdbe2 --- /dev/null +++ b/packages/component-manuscript/src/components/Files.js @@ -0,0 +1,165 @@ +import React, { Fragment } from 'react' +import { last } from 'lodash' +import styled, { css } from 'styled-components' +import { th, Icon } from '@pubsweet/ui' + +const parseFileSize = (size = 0) => { + const kbSize = Math.floor(size / 1000) + const mbSize = Math.floor(kbSize / 1000) + const gbSize = Math.floor(mbSize / 1000) + + if (gbSize) { + return `${gbSize} GB` + } else if (mbSize) { + return `${mbSize} MB` + } else if (kbSize) { + return `${kbSize} kB` + } + return `${size} bytes` +} + +const hasPreview = (name = '') => { + const extension = last(name.split('.')) + return ['pdf', 'png', 'jpg'].includes(extension) +} + +const File = ({ name = 'filename', size, id, previewFile, downloadFile }) => ( + <FileRoot> + {hasPreview(name) && ( + <IconButton onClick={previewFile(id)}> + <Icon primary size={3}> + eye + </Icon> + </IconButton> + )} + <IconButton onClick={downloadFile(id, name)}> + <Icon primary size={3}> + download + </Icon> + </IconButton> + <FileName>{name}</FileName> + <FileSize>{parseFileSize(size)}</FileSize> + </FileRoot> +) + +const Files = ({ + previewFile, + downloadFile, + files: { manuscripts = [], coverLetter = [], supplementary = [] }, +}) => ( + <Root> + {!!manuscripts.length && ( + <Fragment> + <Header> + <span>Main manuscript</span> + <div /> + </Header> + {manuscripts.map(file => ( + <File + key={file.id} + {...file} + downloadFile={downloadFile} + previewFile={previewFile} + /> + ))} + </Fragment> + )} + {!!supplementary.length && ( + <Fragment> + <Header> + <span>Supplemetary files</span> + <div /> + </Header> + {supplementary.map(file => ( + <File + key={file.id} + {...file} + downloadFile={downloadFile} + previewFile={previewFile} + /> + ))} + </Fragment> + )} + {!!coverLetter.length && ( + <Fragment> + <Header> + <span>Cover letter</span> + <div /> + </Header> + {coverLetter.map(file => ( + <File + key={file.id} + {...file} + downloadFile={downloadFile} + previewFile={previewFile} + /> + ))} + </Fragment> + )} + </Root> +) + +export default Files + +// #region styled-component +const defaultText = css` + color: ${th('colorPrimary')}; + font-family: ${th('fontHeading')}; + font-size: ${th('fontSizeBaseSmall')}; +` + +const FileName = styled.span` + ${defaultText}; + margin: 0 ${th('subGridUnit')}; +` +const FileSize = FileName.extend` + margin-left: ${th('subGridUnit')}; +` + +const IconButton = styled.div` + align-items: center; + cursor: pointer; + display: flex; + justify-content: center; + margin: 0 ${th('subGridUnit')}; + &:hover { + opacity: 0.7; + } +` + +const FileRoot = styled.div` + align-items: center; + border: ${th('borderDefault')}; + display: flex; + flex-direction: row; + margin-bottom: ${th('subGridUnit')}; + padding: ${th('subGridUnit')}; +` + +const Header = styled.div` + align-self: stretch; + align-items: center; + display: flex; + flex-direction: row; + + & span { + ${defaultText}; + margin-right: ${th('subGridUnit')}; + margin-top: ${th('subGridUnit')}; + text-transform: uppercase; + } + + & div { + background: ${th('colorPrimary')}; + flex: 1; + height: 1px; + } +` + +const Root = styled.div` + align-items: flex-start; + display: flex; + flex-direction: column; + justify-content: flex-start; +` +// #endregion diff --git a/packages/component-manuscript/src/components/ManuscriptDetails.js b/packages/component-manuscript/src/components/ManuscriptDetails.js new file mode 100644 index 0000000000000000000000000000000000000000..f10c096528d8e492c142e793f1f72fa8ebe846f8 --- /dev/null +++ b/packages/component-manuscript/src/components/ManuscriptDetails.js @@ -0,0 +1,59 @@ +import React from 'react' +import { isEmpty } from 'lodash' +import { th } from '@pubsweet/ui' +import styled from 'styled-components' + +import { Authors, Expandable, Files } from './' + +const ManuscriptDetails = ({ + previewFile, + downloadFile, + collection: { authors = [] }, + fragment: { conflicts = {}, files = {}, metadata: { abstract = '' } }, +}) => ( + <Root> + <Expandable label="Details" startExpanded> + {!!abstract && ( + <Expandable label="abstract" startExpanded> + <Text dangerouslySetInnerHTML={{ __html: abstract }} /> + </Expandable> + )} + {conflicts.hasConflicts === 'yes' && ( + <Expandable label="Conflict of interest"> + <Text>{conflicts.message}</Text> + </Expandable> + )} + {!!authors.length && ( + <Expandable label="authors"> + <Authors authors={authors} /> + </Expandable> + )} + {!isEmpty(files) && ( + <Expandable label="files"> + <Files + downloadFile={downloadFile} + files={files} + previewFile={previewFile} + /> + </Expandable> + )} + </Expandable> + </Root> +) + +export default ManuscriptDetails + +// #region styled-components +const Text = styled.span` + color: ${th('colorPrimary')}; + font-family: ${th('fontReading')}; + font-size: ${th('fontSizeBaseSmall')}; +` + +const Root = styled.div` + background-color: ${th('colorBackground')}; + border: ${th('borderDefault')}; + margin-top: calc(${th('subGridUnit')} * 2); + transition: height 0.3s; +` +// #endregion diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index e0bf2d3911b519621facfe53f1c2dbb3b2224853..cacf5cc52eb98ad9144e34967cca8434bfa09aae 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -12,6 +12,7 @@ import { LeftDetails, RightDetails, } from '../atoms' +import { ManuscriptDetails, SideBarActions, SideBarRoles } from './' const ManuscriptLayout = ({ currentUser, @@ -19,6 +20,8 @@ const ManuscriptLayout = ({ project, version, journal, + previewFile, + downloadFile, }) => ( <Root> <Container flex={3}> @@ -35,8 +38,17 @@ const ManuscriptLayout = ({ </RightDetails> </Header> <ManuscriptHeader journal={journal} project={project} version={version} /> + <ManuscriptDetails + collection={project} + downloadFile={downloadFile} + fragment={version} + previewFile={previewFile} + /> </Container> - <SideBar flex={1}>Sidebar</SideBar> + <SideBar flex={1}> + <SideBarActions project={project} version={version} /> + <SideBarRoles project={project} version={version} /> + </SideBar> </Root> ) diff --git a/packages/component-manuscript/src/components/ManuscriptPage.js b/packages/component-manuscript/src/components/ManuscriptPage.js index 0179406874f89e00b929d56de0f74446ddc48cbe..834f2bfd90a089862f75d55546cdb12a50de33c7 100644 --- a/packages/component-manuscript/src/components/ManuscriptPage.js +++ b/packages/component-manuscript/src/components/ManuscriptPage.js @@ -9,10 +9,11 @@ import { } from 'xpub-selectors' import { replace } from 'react-router-redux' import { compose, lifecycle, withHandlers } from 'recompose' +import { reviewerDecision } from 'pubsweet-components-faraday/src/redux/reviewers' +import { getSignedUrl } from 'pubsweet-components-faraday/src/redux/files' import ManuscriptLayout from './ManuscriptLayout' import { parseSearchParams, redirectToError } from './utils' -import { reviewerDecision } from '../../../components-faraday/src/redux/reviewers' export default compose( withJournal, @@ -29,7 +30,12 @@ export default compose( project: selectCollection(state, match.params.project), version: selectFragment(state, match.params.version), }), - { reviewerDecision, replace, updateVersion: actions.updateFragment }, + { + reviewerDecision, + replace, + updateVersion: actions.updateFragment, + getSignedUrl, + }, ), lifecycle({ componentDidMount() { @@ -50,5 +56,24 @@ export default compose( id: version.id, ...data, }), + previewFile: ({ getSignedUrl }) => fileId => e => { + e.preventDefault() + getSignedUrl(fileId).then(({ signedUrl }) => { + const windowReference = window.open() + windowReference.location = signedUrl + }) + }, + downloadFile: ({ getSignedUrl }) => (fileId, fileName) => e => { + e.preventDefault() + getSignedUrl(fileId).then(({ signedUrl }) => { + const a = document.createElement('a') + a.href = `${signedUrl}` + a.download = fileName + a.target = '_blank' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + }) + }, }), )(ManuscriptLayout) diff --git a/packages/component-manuscript/src/components/SideBarActions.js b/packages/component-manuscript/src/components/SideBarActions.js new file mode 100644 index 0000000000000000000000000000000000000000..82ba580f1fc89c75e7577db903c7077174df6fab --- /dev/null +++ b/packages/component-manuscript/src/components/SideBarActions.js @@ -0,0 +1,50 @@ +import React from 'react' +import { th, Button, Icon } from '@pubsweet/ui' +import styled, { css } from 'styled-components' + +import ZipFiles from 'pubsweet-components-faraday/src/components/Dashboard/ZipFiles' + +const SideBarActions = ({ project, version }) => ( + <Root> + <ActionButton> Make Decision </ActionButton> + <ZipFiles archiveName={`ID-${project.customId}`} fragmentId={version.id}> + <ClickableIcon> + <Icon>download</Icon> + </ClickableIcon> + </ZipFiles> + </Root> +) + +export default SideBarActions + +// #region styled-components +const defaultText = css` + color: ${th('colorText')}; + font-family: ${th('fontReading')}; + font-size: ${th('fontSizeBaseSmall')}; +` + +const Root = styled.div` + border-bottom: ${th('borderDefault')}; + display: flex; + justify-content: space-between; + padding: ${th('subGridUnit')}; +` +const ActionButton = styled(Button)` + ${defaultText}; + align-items: center; + background-color: ${th('colorPrimary')}; + color: ${th('colorTextReverse')}; + display: flex; + padding: 4px 8px; + text-align: center; + height: calc(${th('subGridUnit')}*5); + text-transform: uppercase; +` +const ClickableIcon = styled.div` + margin: 0 ${th('subGridUnit')}; + &:hover { + opacity: 0.7; + } +` +// #endregion diff --git a/packages/component-manuscript/src/components/SideBarRoles.js b/packages/component-manuscript/src/components/SideBarRoles.js new file mode 100644 index 0000000000000000000000000000000000000000..8331028975e493f91ff4f7bb518c32232e680822 --- /dev/null +++ b/packages/component-manuscript/src/components/SideBarRoles.js @@ -0,0 +1,65 @@ +import React from 'react' +import { th, Button } from '@pubsweet/ui' +import { get } from 'lodash' +import styled, { css } from 'styled-components' + +const getHE = project => { + const heName = get(project, 'handlingEditor.name') + + if (heName) { + return <Name> {heName} </Name> + } + return <ActionButton>Assign</ActionButton> +} + +const SideBarRoles = ({ project }) => ( + <Root> + <Row> + <Text>Editor in Chief</Text> + <Name>John Snow</Name> + </Row> + <Row> + <Text>Handling Editor</Text> + {getHE(project)} + </Row> + </Root> +) + +export default SideBarRoles + +// #region styled-components +const defaultText = css` + color: ${th('colorText')}; + font-family: ${th('fontReading')}; + font-size: ${th('fontSizeBaseSmall')}; +` +const Root = styled.div` + display: flex; + flex-direction: column; + padding: calc(${th('subGridUnit')}*3) ${th('subGridUnit')}; +` +const Text = styled.div` + ${defaultText}; + text-transform: uppercase; +` + +const Name = styled.div` + ${defaultText}; + text-decoration: underline; +` + +const Row = styled.div` + display: flex; + justify-content: space-between; +` +const ActionButton = styled(Button)` + ${defaultText}; + align-items: center; + background-color: ${th('colorBackground')}; + padding: 4px 8px; + text-align: center; + height: calc(${th('subGridUnit')}*5); + text-transform: uppercase; + margin: 0; +` +// #endregion diff --git a/packages/component-manuscript/src/components/index.js b/packages/component-manuscript/src/components/index.js index e2e5b2d910d00c3a5fa12f0d7ebaf5363ea8d28f..ca25d8bba550a2c83a8a8c318b89c08c289e54f0 100644 --- a/packages/component-manuscript/src/components/index.js +++ b/packages/component-manuscript/src/components/index.js @@ -1 +1,7 @@ +export { default as Files } from './Files' +export { default as Authors } from './Authors' +export { default as Expandable } from './Expandable' export { default as ManuscriptPage } from './ManuscriptPage' +export { default as ManuscriptDetails } from './ManuscriptDetails' +export { default as SideBarActions } from './SideBarActions' +export { default as SideBarRoles } from './SideBarRoles' diff --git a/packages/components-faraday/src/redux/index.js b/packages/components-faraday/src/redux/index.js index cf03e87f32321c663eebead2e2354f09687aa323..ddaa6385d75eba8dbcc2f2abfe25f757f6021591 100644 --- a/packages/components-faraday/src/redux/index.js +++ b/packages/components-faraday/src/redux/index.js @@ -1,2 +1,4 @@ export { default as authors } from './authors' export { default as editors } from './editors' +export { default as files } from './files' +export { default as reviewers } from './reviewers'