diff --git a/packages/component-faraday-selectors/src/index.js b/packages/component-faraday-selectors/src/index.js index f1d9bcb1960a84d6656d68c25bf8616b2b4c02a8..0b9b1ab4d1a1b56a7e0753fb96eb1db46d2ae11b 100644 --- a/packages/component-faraday-selectors/src/index.js +++ b/packages/component-faraday-selectors/src/index.js @@ -1,4 +1,5 @@ import { get, last } from 'lodash' +import { selectCurrentUser } from 'xpub-selectors' export const isHEToManuscript = (state, collectionId) => { const currentUserId = get(state, 'currentUser.user.id') @@ -132,3 +133,15 @@ export const userNotConfirmed = ({ currentUser }) => get(currentUser, 'isAuthenticated') && !currentUserIs({ currentUser }, 'staff') && !get(currentUser, 'user.isConfirmed') + +export const currentUserIsReviewer = (state, fragmentId) => { + const currentUser = selectCurrentUser(state) + const invitations = get(state, `fragments.${fragmentId}.invitations`, []) + return !!invitations.find( + i => + i.userId === currentUser.id && + i.role === 'reviewer' && + i.hasAnswer && + i.isAccepted, + ) +} diff --git a/packages/component-faraday-ui/src/DateParser.js b/packages/component-faraday-ui/src/DateParser.js new file mode 100644 index 0000000000000000000000000000000000000000..cd35989c68f1947f7593e0176d9104dd4fa6ca5f --- /dev/null +++ b/packages/component-faraday-ui/src/DateParser.js @@ -0,0 +1,12 @@ +import React, { Fragment } from 'react' +import moment from 'moment' +import { Text } from 'pubsweet-component-faraday-ui' + +const DateParser = ({ label, date, format = 'DD.MM.YYYY' }) => ( + <Fragment> + {label && <Text>{label} </Text>} + {date && <Text mr={1}>{moment(date).format(format)}</Text>} + </Fragment> +) + +export default DateParser diff --git a/packages/component-faraday-ui/src/DateParser.md b/packages/component-faraday-ui/src/DateParser.md new file mode 100644 index 0000000000000000000000000000000000000000..a734dede59a3f900b073d89a555f6373e3c11ab9 --- /dev/null +++ b/packages/component-faraday-ui/src/DateParser.md @@ -0,0 +1,21 @@ +Date parser without label + +```js +const date = 1534942072364 +;<DateParser date={date} /> +``` + +Date parser with label + +```js +const date = 1534942072364; +<DateParser date={date} label='Updated on: '/> +``` + +Date parser with format + +```js +const date = 1534942072364 +const format = 'DD.MM.YY' +;<DateParser date={date} format={format} /> +``` diff --git a/packages/component-faraday-ui/src/DownloadZipFiles.js b/packages/component-faraday-ui/src/DownloadZipFiles.js new file mode 100644 index 0000000000000000000000000000000000000000..459f44b422ffc4a6a80e17089166f932bdd12a3e --- /dev/null +++ b/packages/component-faraday-ui/src/DownloadZipFiles.js @@ -0,0 +1,107 @@ +import React, { Fragment } from 'react' +import qs from 'querystring' +import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { Spinner } from '@pubsweet/ui' +import { compose, withHandlers, withState } from 'recompose' +import { + getUserToken, + currentUserIsReviewer, +} from 'pubsweet-component-faraday-selectors' + +const ZipFiles = ({ disabled, fetching, children, downloadFiles }) => ( + <Fragment onClick={!disabled ? downloadFiles : null}> + {fetching ? <Spinner /> : children} + </Fragment> +) + +const cache = {} + +const reviewerFiles = ['manuscripts', 'supplementary'] +const defaultFiles = [...reviewerFiles, 'coverLetter'] + +const DownloadZipFiles = compose( + connect((state, { collectionId }) => ({ + token: getUserToken(state), + isReviewer: currentUserIsReviewer(state, collectionId), + })), + withState('fetching', 'setFetching', false), + withHandlers({ + createAnchorElement: () => (file, filename) => { + const url = URL.createObjectURL(file) + const a = document.createElement('a') + + a.href = url + a.download = filename + document.body.appendChild(a) + + return { + a, + url, + } + }, + removeAnchorElement: () => (a, url) => { + document.body.removeChild(a) + URL.revokeObjectURL(url) + }, + }), + withHandlers({ + downloadFiles: ({ + token, + isReviewer, + fragmentId, + setFetching, + archiveName, + createAnchorElement, + removeAnchorElement, + }) => () => { + const getUrl = `${ + window.location.origin + }/api/files/${fragmentId}?${qs.stringify({ + fileTypes: isReviewer ? reviewerFiles : defaultFiles, + })}` + if (cache[fragmentId]) { + const fileName = archiveName || `${fragmentId}-archive.zip` + + const { a, url } = createAnchorElement(cache[fragmentId], fileName) + a.click() + removeAnchorElement(a, url) + } else { + setFetching(fetching => true) + const xhr = new XMLHttpRequest() + xhr.onreadystatechange = function onXhrStateChange() { + if (this.readyState === 4) { + setFetching(fetching => false) + if (this.status >= 200 && this.status < 300) { + const fileName = archiveName || `${fragmentId}-archive.zip` + const f = new File([this.response], fileName, { + type: 'application/zip', + }) + cache[fragmentId] = f + + const { a, url } = createAnchorElement(f, fileName) + a.click() + removeAnchorElement(a, url) + } + } + } + xhr.open('GET', getUrl) + xhr.responseType = 'blob' + xhr.setRequestHeader('Authorization', `Bearer ${token}`) + xhr.send() + } + }, + }), +)(ZipFiles) + +DownloadZipFiles.propTypes = { + disabled: PropTypes.bool, + archiveName: PropTypes.string, + fragmentId: PropTypes.string.isRequired, +} + +DownloadZipFiles.defaultProps = { + disabled: false, +} + +export default DownloadZipFiles diff --git a/packages/component-faraday-ui/src/DownloadZipFiles.md b/packages/component-faraday-ui/src/DownloadZipFiles.md new file mode 100644 index 0000000000000000000000000000000000000000..82263d4563040a93ce1a5330497927de6dbd5bb9 --- /dev/null +++ b/packages/component-faraday-ui/src/DownloadZipFiles.md @@ -0,0 +1,14 @@ +Download manuscript package as zip + +```js +const collection = {id: '', customId: ''}; +const fragment = {id: ''}; + +<DownloadZipFiles + archiveName={`ID-${collection.customId}`} + collectionId={collection.id} + fragmentId={fragment.id} +> + <div>Download</div> +</DownloadZipFiles> +``` diff --git a/packages/component-faraday-ui/src/ManuscriptDetailsTop.js b/packages/component-faraday-ui/src/ManuscriptDetailsTop.js new file mode 100644 index 0000000000000000000000000000000000000000..2ddbca724b9f74267e28eb4efc4d8cd99d308c6e --- /dev/null +++ b/packages/component-faraday-ui/src/ManuscriptDetailsTop.js @@ -0,0 +1,32 @@ +import React from 'react' +import { + Row, + ActionLink, + DateParser, + IconButton, + DownloadZipFiles, + ManuscriptVersion, +} from 'pubsweet-component-faraday-ui' + +const ManuscriptDetailsTop = ({ history, collection = {}, fragment = {} }) => ( + <Row alignItems="center" mb={1}> + <Row alignItems="center" justify="flex-start"> + <ActionLink icon="arrow-left" onClick={() => history.push('/')}> + Dashboard + </ActionLink> + </Row> + <Row alignItems="center" justify="flex-end"> + <DownloadZipFiles + archiveName={`ID-${collection.customId}`} + collectionId={collection.id} + fragmentId={fragment.id} + > + <IconButton icon="download" /> + </DownloadZipFiles> + <DateParser date={fragment.submitted} label="Updated on" /> + <ManuscriptVersion collection={collection} fragment={fragment} /> + </Row> + </Row> +) + +export default ManuscriptDetailsTop diff --git a/packages/component-faraday-ui/src/ManuscriptDetailsTop.md b/packages/component-faraday-ui/src/ManuscriptDetailsTop.md new file mode 100644 index 0000000000000000000000000000000000000000..f4a1a46f7c94ed7eaec8a46e95e7af124af4860c --- /dev/null +++ b/packages/component-faraday-ui/src/ManuscriptDetailsTop.md @@ -0,0 +1,15 @@ +Manuscript Details top section + +```js +const history = { + push: () => alert('go back') +}; +const fragment={}; +const collection={}; + +<ManuscriptDetailsTop + collection={collection} + fragment={fragment} + history={history} +/> +``` diff --git a/packages/component-faraday-ui/src/ManuscriptVersion.js b/packages/component-faraday-ui/src/ManuscriptVersion.js new file mode 100644 index 0000000000000000000000000000000000000000..f5d8d5493279292c9dc0b0565f3586d233e820d1 --- /dev/null +++ b/packages/component-faraday-ui/src/ManuscriptVersion.js @@ -0,0 +1,39 @@ +import React from 'react' +import { get } from 'lodash' +import { Menu } from '@pubsweet/ui' +import { withRouter } from 'react-router-dom' +import { compose, withHandlers, withProps } from 'recompose' + +const ManuscriptVersion = ({ + history, + fragments, + fragment = {}, + collection = {}, + parseVersionOptions, +}) => + !!fragments.length && ( + <Menu + inline + onChange={v => + history.push(`/projects/${collection.id}/versions/${v}/details`) + } + options={parseVersionOptions(fragments)} + value={get(fragment, 'id')} + /> + ) + +export default compose( + withRouter, + withProps(({ collection = {} }) => ({ + fragments: get(collection, 'fragments', []), + })), + withHandlers({ + parseVersionOptions: ({ fragments }) => () => + fragments + .map((f, index) => ({ + value: f, + label: `Version ${index + 1}`, + })) + .reverse(), + }), +)(ManuscriptVersion) diff --git a/packages/component-faraday-ui/src/ManuscriptVersion.md b/packages/component-faraday-ui/src/ManuscriptVersion.md new file mode 100644 index 0000000000000000000000000000000000000000..d740afec5d1308b0bdb3482d46a8dd7e4f7880ad --- /dev/null +++ b/packages/component-faraday-ui/src/ManuscriptVersion.md @@ -0,0 +1,13 @@ +Manuscript version component + +```js + +const history = { + push: () => alert('go to version') +}; + +const fragment={}; +const collection={}; + +<ManuscriptVersion collection={collection} fragment={fragment} /> +``` diff --git a/packages/component-faraday-ui/src/index.js b/packages/component-faraday-ui/src/index.js index 0808cec2411a0e20a4a40fad255d5f21813cdc3e..5265ed1692701c22ca6862324403566bf11ebf1b 100644 --- a/packages/component-faraday-ui/src/index.js +++ b/packages/component-faraday-ui/src/index.js @@ -21,6 +21,12 @@ export { default as Tag } from './Tag' export { default as Text } from './Text' export { default as WizardAuthors } from './WizardAuthors' export { default as WizardFiles } from './WizardFiles' +export { default as DateParser } from './DateParser' +export { default as DownloadZipFiles } from './DownloadZipFiles' + +// Manuscript Details +export { default as ManuscriptVersion } from './ManuscriptVersion' +export { default as ManuscriptDetailsTop } from './ManuscriptDetailsTop' export * from './styledHelpers' diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.js b/packages/component-manuscript/src/components/ManuscriptLayout.js index c0ed8a1f1635315f2e1f5aa3ea048fec535ab306..f9a91d8c5239cdc03c6f55d7215f66c7611b0e5f 100644 --- a/packages/component-manuscript/src/components/ManuscriptLayout.js +++ b/packages/component-manuscript/src/components/ManuscriptLayout.js @@ -1,27 +1,7 @@ import React, { Fragment } from 'react' import { isEmpty } from 'lodash' - -import { - Root, - Header, - SideBar, - Container, - LeftDetails, - BreadCrumbs, - ManuscriptId, - RightDetails, -} from '../atoms' -import { - SideBarRoles, - SideBarActions, - SubmitRevision, - ManuscriptHeader, - ReviewsAndReports, - ManuscriptDetails, - ManuscriptVersion, - EditorialComments, - ResponseToReviewers, -} from './' +import styled from 'styled-components' +import { Text, ManuscriptDetailsTop, Row } from 'pubsweet-component-faraday-ui' const ManuscriptLayout = ({ journal, @@ -37,55 +17,24 @@ const ManuscriptLayout = ({ <Root> {!isEmpty(project) && !isEmpty(version) ? ( <Fragment> - <Container flex={3}> - <Header> - <LeftDetails> - <BreadCrumbs> - <span onClick={() => history.push('/')}>Dashboard</span> - <span>Manuscript Details</span> - </BreadCrumbs> - <ManuscriptId>{`- ID ${project.customId}`}</ManuscriptId> - </LeftDetails> - <RightDetails> - <ManuscriptVersion project={project} version={version} /> - </RightDetails> - </Header> - <ManuscriptHeader - journal={journal} - project={project} - version={version} - /> - <ManuscriptDetails - fragment={version} - startExpanded={isEmpty(version.revision)} - /> - {editorialRecommendations.length > 0 && ( - <EditorialComments - editorInChief={editorInChief} - project={project} - recommendations={editorialRecommendations} - /> - )} - {hasResponseToReviewers && <ResponseToReviewers version={version} />} - <ReviewsAndReports project={project} version={version} /> - {canMakeRevision && ( - <SubmitRevision project={project} version={version} /> - )} - </Container> - <SideBar flex={1}> - <SideBarActions project={project} version={version} /> - <SideBarRoles - currentUser={currentUser} - editorInChief={editorInChief} - project={project} - version={version} - /> - </SideBar> + <ManuscriptDetailsTop + collection={project} + fragment={version} + history={history} + /> + <Row justify="flex-start">Aici header</Row> </Fragment> ) : ( - <div>Loading...</div> + <Text>Loading...</Text> )} </Root> ) export default ManuscriptLayout + +// #region styles +const Root = styled.div` + overflow-y: auto; + min-height: 50vh; +` +// #endregion diff --git a/packages/component-manuscript/src/components/ManuscriptLayout.old.js b/packages/component-manuscript/src/components/ManuscriptLayout.old.js new file mode 100644 index 0000000000000000000000000000000000000000..c0ed8a1f1635315f2e1f5aa3ea048fec535ab306 --- /dev/null +++ b/packages/component-manuscript/src/components/ManuscriptLayout.old.js @@ -0,0 +1,91 @@ +import React, { Fragment } from 'react' +import { isEmpty } from 'lodash' + +import { + Root, + Header, + SideBar, + Container, + LeftDetails, + BreadCrumbs, + ManuscriptId, + RightDetails, +} from '../atoms' +import { + SideBarRoles, + SideBarActions, + SubmitRevision, + ManuscriptHeader, + ReviewsAndReports, + ManuscriptDetails, + ManuscriptVersion, + EditorialComments, + ResponseToReviewers, +} from './' + +const ManuscriptLayout = ({ + journal, + history, + currentUser, + editorInChief, + canMakeRevision, + editorialRecommendations, + hasResponseToReviewers, + project = {}, + version = {}, +}) => ( + <Root> + {!isEmpty(project) && !isEmpty(version) ? ( + <Fragment> + <Container flex={3}> + <Header> + <LeftDetails> + <BreadCrumbs> + <span onClick={() => history.push('/')}>Dashboard</span> + <span>Manuscript Details</span> + </BreadCrumbs> + <ManuscriptId>{`- ID ${project.customId}`}</ManuscriptId> + </LeftDetails> + <RightDetails> + <ManuscriptVersion project={project} version={version} /> + </RightDetails> + </Header> + <ManuscriptHeader + journal={journal} + project={project} + version={version} + /> + <ManuscriptDetails + fragment={version} + startExpanded={isEmpty(version.revision)} + /> + {editorialRecommendations.length > 0 && ( + <EditorialComments + editorInChief={editorInChief} + project={project} + recommendations={editorialRecommendations} + /> + )} + {hasResponseToReviewers && <ResponseToReviewers version={version} />} + <ReviewsAndReports project={project} version={version} /> + {canMakeRevision && ( + <SubmitRevision project={project} version={version} /> + )} + </Container> + <SideBar flex={1}> + <SideBarActions project={project} version={version} /> + <SideBarRoles + currentUser={currentUser} + editorInChief={editorInChief} + project={project} + version={version} + /> + </SideBar> + </Fragment> + ) : ( + <div>Loading...</div> + )} + </Root> +) + +export default ManuscriptLayout diff --git a/packages/styleguide/src/webpack-config.js b/packages/styleguide/src/webpack-config.js index 5b92100e766e6cb19b30a1fbacccef855fa64771..06e77cbf45e00fa48a6975029552b49f4e6d35e8 100644 --- a/packages/styleguide/src/webpack-config.js +++ b/packages/styleguide/src/webpack-config.js @@ -12,6 +12,8 @@ module.exports = dir => { /@pubsweet\/[^/]+\/src/, /component-faraday-ui\/src/, /component-modal\/src/, + /component-faraday-selectors\/src/, + /components-faraday\/src/, ] return { devtool: 'cheap-module-source-map',