Verified Commit 74714f7c authored by Sandro Lutz's avatar Sandro Lutz
Browse files

Implement studydocuments details

parent 2df5c8e0
......@@ -8028,9 +8028,9 @@
}
},
"filesize": {
"version": "3.5.11",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz",
"integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g=="
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-6.0.1.tgz",
"integrity": "sha512-u4AYWPgbI5GBhs6id1KdImZWn5yfyFrrQ8OWZdN7ZMfA8Bf4HcO0BGo9bmUIEV8yrp8I1xVfJ/dn90GtFNNJcg=="
},
"fill-range": {
"version": "4.0.0",
......@@ -15324,6 +15324,11 @@
"escape-string-regexp": "^1.0.5"
}
},
"filesize": {
"version": "3.5.11",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-3.5.11.tgz",
"integrity": "sha512-ZH7loueKBoDb7yG9esn1U+fgq7BzlzW6NRi5/rMdxIZ05dj7GFD/Xc5rq2CDt5Yq86CyfSYVyx4242QQNZbx1g=="
},
"inquirer": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz",
......
......@@ -16,7 +16,7 @@ const useStyles = makeStyles(
root: {
position: 'relative',
display: 'block',
paddingTop: '.75em',
paddingTop: '0',
},
title: {
position: 'sticky',
......
import React from 'react'
import PropTypes from 'prop-types'
import FileIcon from '~images/icons/file'
import PdfIcon from '~images/icons/pdf'
import ImageIcon from '~images/icons/image'
import ArchiveIcon from '~images/icons/archive'
import WordIcon from '~images/icons/word'
import ExcelIcon from '~images/icons/excel'
import PowerPointIcon from '~images/icons/powerpoint'
const getIconComponentByMimeType = mimeType => {
switch (mimeType) {
case 'application/pdf':
return PdfIcon
// Word Documents
case 'application/vnd.oasis.opendocument.text':
case 'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
case 'application/msword':
return WordIcon
// PowerPoint Documents
case 'application/vnd.oasis.opendocument.presentation':
case 'application/vnd.openxmlformats-officedocument.presentationml.presentation':
case 'application/vnd.ms-powerpoint':
return PowerPointIcon
// Excel Document
case 'application/vnd.oasis.opendocument.spreadsheet':
case 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet':
case 'application/vnd.ms-excel':
return ExcelIcon
// Archives
case 'application/x-tar':
case 'application/octet-stream':
case 'application/x-bzip':
case 'application/x-bzip2':
case 'application/x-rar-compressed':
case 'application/zip':
case 'application/x-7z-compressed':
return ArchiveIcon
// Images
case 'image/jpeg':
case 'image/gif':
case 'image/png':
case 'image/svg+xml':
return ImageIcon
default:
return FileIcon
}
}
const FileTypeIcon = ({ mimeType, ...props }) => {
const IconComponent = getIconComponentByMimeType(mimeType)
return <IconComponent {...props} />
}
FileTypeIcon.propTypes = {
/** MimeType to select corresponding icon */
mimeType: PropTypes.string,
}
export default FileTypeIcon
import React from 'react'
import React, { useState } from 'react'
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
import { useSelector, useDispatch } from 'react-redux'
import { makeStyles } from '@material-ui/styles'
import { Toolbar } from '@material-ui/core'
import { useIntl, FormattedMessage } from 'gatsby-plugin-intl'
import { Toolbar, Button } from '@material-ui/core'
import { useIntl, navigate, FormattedMessage } from 'gatsby-plugin-intl'
import fileSize from 'filesize'
import { apiUrl } from 'config'
import { deleteItem } from '~store/common/actions'
import { STUDYDOCUMENTS } from '~store/studydocuments/constants'
import CopyButton from '../general/copyButton'
import FileTypeIcon from '../general/fileTypeIcon'
const useStyles = makeStyles(
theme => ({
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-evenly',
alignContent: 'space-between',
alignItems: 'center',
textAlign: 'left',
width: '100%',
},
notification: {
width: 'calc(100% - 2em)',
margin: '1em',
},
notificationColored: {
color: theme.palette.secondary.main,
},
additionalFields: {
transition: 'width 1s ease',
'& > *': {
width: '100%',
margin: '.5em 0',
documents: {
display: 'none',
padding: '16px',
[theme.breakpoints.down('md')]: {
display: 'block',
},
},
additionalFieldsVisible: {
width: '350px',
padding: '1em',
position: 'relative',
documentLink: {
display: 'inline-block',
color: theme.palette.text.primary,
borderRadius: '4px',
padding: '4px',
'&:after': {
content: '" "',
display: 'block',
position: 'absolute',
'&:not(:last-child)': {
marginRight: '1em',
},
'&:hover': {
backgroundColor: theme.palette.common.grey,
width: '4px',
top: '1em',
bottom: 0,
right: 0,
borderRadius: '2px',
overflow: 'hidden',
[theme.breakpoints.down('md')]: {
width: 'calc(100% - 2em)',
height: '4px',
margin: '0 1em',
},
},
},
description: {
flexGrow: 1,
padding: '1em',
textAlign: 'left',
[theme.breakpoints.down('md')]: {
width: '100%',
'& span': {
display: 'inline-block',
position: 'relative',
bottom: '-2px',
},
},
divider: {
gridArea: 'divider',
documentIcon: {
verticalAlign: 'middle',
},
documentName: {
marginLeft: '.25em',
},
documentSize: {
opacity: '.5',
marginLeft: '1em',
},
toolbar: {
width: '100%',
......@@ -83,13 +71,16 @@ const useStyles = makeStyles(
flexGrow: 1,
},
}),
{ name: 'jobofferDetails' }
{ name: 'studydocumentsDetails' }
)
const StudydocumentDetails = ({ studydocumentId, ...props }) => {
const studydocument = useSelector(
state => state.studydocuments.items[studydocumentId]
)
const [confirmDeletion, setConfirmDeletion] = useState(false)
const [deletePending, setDeletePending] = useState(false)
const dispatch = useDispatch()
const classes = useStyles()
const intl = useIntl()
......@@ -97,21 +88,90 @@ const StudydocumentDetails = ({ studydocumentId, ...props }) => {
if (!studydocument || !studydocument.data) return null
const { data } = studydocument
const canEdit =
data._links &&
data._links.self &&
data._links.self.methods &&
data._links.self.methods.includes('PATCH')
const canDelete =
data._links &&
data._links.self &&
data._links.self.methods &&
data._links.self.methods.includes('DELETE')
return (
<div className={[classes.root].join(' ')} {...props}>
<div>This is just a placeholder!</div>
<div className={classes.documents}>
{data.files.map(({ file, name, content_type, length }) => {
const label =
name.length <= 18
? name
: `${name.substr(0, 10)}\u2026${name.substr(name.length - 8)}`
return (
<a
key={name}
className={classes.documentLink}
href={`${apiUrl}${file}`}
target="_blank"
rel="noopener noreferrer"
onClick={e => e.stopPropagation()}
>
<FileTypeIcon
className={classes.documentIcon}
mimeType={content_type}
/>
<span className={classes.documentName}>{label}</span>
<span className={classes.documentSize}>{fileSize(length)}</span>
</a>
)
})}
</div>
<Toolbar className={classes.toolbar} variant="dense">
{/* Buttons on the LEFT side */}
{canEdit && (
<Button onClick={() => navigate(`/studydocuments/${data._id}/edit`)}>
<FormattedMessage id="studydocuments.actions.edit" />
</Button>
)}
{canDelete && !confirmDeletion && (
<Button
disabled={deletePending}
onClick={() => setConfirmDeletion(true)}
>
<FormattedMessage id="studydocuments.actions.delete" />
</Button>
)}
{canDelete && confirmDeletion && (
<React.Fragment>
<Button onClick={() => setConfirmDeletion(false)}>
<FormattedMessage id="studydocuments.actions.cancel" />
</Button>
<Button
color="secondary"
variant="outlined"
onClick={() => {
setConfirmDeletion(false)
setDeletePending(true)
dispatch(
deleteItem(STUDYDOCUMENTS, { id: data._id, etag: data._etag })
)
.then(() => {
setDeletePending(false)
navigate('/studydocuments')
})
.catch(() => {
setDeletePending(false)
})
}}
>
<FormattedMessage id="studydocuments.actions.confirm" />
</Button>
</React.Fragment>
)}
<span className={classes.toolbarSeparator} />
{/* Buttons on the RIGHT side */}
{/* {data.pdf && (
<Button onClick={() => window.open(apiUrl + data.pdf.file, '_blank')}>
<FormattedMessage id="jobs.downloadAsPdf" />
</Button>
)} */}
<CopyButton
value={`${window.location.origin}/${intl.locale}/jobs/${data._id}`}
value={`${window.location.origin}/${intl.locale}/studydocuments/${data._id}`}
>
<FormattedMessage id="copyDirectLink" />
</CopyButton>
......
import React from 'react'
import PropTypes from 'prop-types'
import { useSelector } from 'react-redux'
import filesize from 'filesize'
import { Icon } from '@material-ui/core'
import { makeStyles } from '@material-ui/styles'
import Skeleton from '@material-ui/lab/Skeleton'
import { useIntl, FormattedMessage } from 'gatsby-plugin-intl'
import { apiUrl } from 'config'
import FileTypeIcon from '../general/fileTypeIcon'
const useStyles = makeStyles(
theme => ({
root: {
display: 'grid',
gridTemplateAreas: "'image content'",
gridTemplateColumns: '182px 1fr',
width: '100%',
minHeight: '100px',
textAlign: 'left',
padding: '1.25em',
[theme.breakpoints.down('sm')]: {
gridTemplateAreas: 'content',
gridTemplateColumns: '1fr',
},
'& > *': {
width: '100%',
},
},
image: {
gridArea: 'image',
title: {
display: 'block',
margin: '16px',
width: 'calc(100% - 32px)',
backgroundColor: 'transparent',
[theme.breakpoints.down('sm')]: {
fontSize: '1.17em',
margin: 0,
'& span': {
fontWeight: 'normal',
},
},
properties: {
width: '100%',
marginTop: '.25em',
'& > div': {
display: 'inline-block',
paddingBottom: '.25em',
'& span::after': {
content: "':'",
paddingRight: '.5em',
},
'&::after': {
content: "'/'",
color: theme.palette.secondary.main,
margin: '0 1em',
[theme.breakpoints.down('sm')]: {
content: "','",
color: '#000',
margin: '0 .5em 0 0',
},
},
'&:last-of-type::after': {
content: "''",
margin: 0,
},
},
},
documents: {
width: 'calc(100% + 8px)',
position: 'relative',
left: '-4px',
[theme.breakpoints.down('md')]: {
display: 'none',
},
},
content: {
gridArea: 'content',
textAlign: 'left',
padding: '1em',
documentLink: {
display: 'inline-block',
color: theme.palette.text.primary,
borderRadius: '4px',
padding: '4px',
'&:not(:last-child)': {
marginRight: '1em',
},
'&:hover': {
backgroundColor: theme.palette.common.grey,
},
'& span': {
display: 'inline-block',
position: 'relative',
bottom: '-2px',
},
},
title: {
padding: 0,
margin: 0,
marginBottom: '.25em',
documentIcon: {
verticalAlign: 'middle',
},
documentName: {
marginLeft: '.25em',
},
documentSize: {
opacity: '.5',
marginLeft: '1em',
},
}),
{ name: 'eventSummary' }
{ name: 'studydocumentsSummary' }
)
const StudydocumentSummary = ({ studydocumentId, ...props }) => {
......@@ -46,6 +108,7 @@ const StudydocumentSummary = ({ studydocumentId, ...props }) => {
state => state.studydocuments.items[studydocumentId]
)
const classes = useStyles()
const intl = useIntl()
if (!studydocument || !studydocument.data) {
// TODO: ....
......@@ -66,13 +129,65 @@ const StudydocumentSummary = ({ studydocumentId, ...props }) => {
}
const { data } = studydocument
// const title = translateMessage({ en: data.title_en, de: data.title_de }, intl)
const title =
data.type || data.lecture
? `${data.lecture ? data.lecture : ''} ${
data.type
? intl.formatMessage({ id: `studydocuments.name.${data.type}` })
: ''
}`
: null
return (
<div className={classes.root} {...props}>
<div className={classes.content}>
<h2 className={classes.title}>{data.title}</h2>
<div></div>
<h2 className={classes.title}>
{title}
<span>
{title ? '' : ''}
{data.title}
</span>
</h2>
<div className={classes.properties}>
{data.course_year && <div>{data.course_year}</div>}
{data.professor && <div>{data.professor}</div>}
{data.author && (
<div>
<span>
<FormattedMessage id="studydocuments.author" />
</span>
{data.author}
</div>
)}
{data.semester && (
<div>
<FormattedMessage id={`studydocuments.semester${data.semester}`} />
</div>
)}
</div>
<div className={classes.documents}>
{data.files.map(({ file, name, content_type, length }) => {
const label =
name.length <= 18
? name
: `${name.substr(0, 10)}\u2026${name.substr(name.length - 8)}`
return (
<a
key={name}
className={classes.documentLink}
href={`${apiUrl}${file}`}
target="_blank"
rel="noopener noreferrer"
onClick={e => e.stopPropagation()}
>
<FileTypeIcon
className={classes.documentIcon}
mimeType={content_type}
/>
<span className={classes.documentName}>{label}</span>
<span className={classes.documentSize}>{filesize(length)}</span>
</a>
)
})}
</div>
</div>
)
......
import React from 'react'
import createSvgIcon from '@material-ui/icons/utils/createSvgIcon'
export default createSvgIcon(
<React.Fragment>
<path d="M5.12,5L5.93,4H17.93L18.87,5M12,17.5L6.5,12H10V10H14V12H17.5L12,17.5M20.54,5.23L19.15,3.55C18.88,3.21 18.47,3 18,3H6C5.53,3 5.12,3.21 4.84,3.55L3.46,5.23C3.17,5.57 3,6 3,6.5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V6.5C21,6 20.83,5.57 20.54,5.23Z" />
</React.Fragment>,
'Archive'
)
import React from 'react'
import createSvgIcon from '@material-ui/icons/utils/createSvgIcon'
export default createSvgIcon(
<React.Fragment>
<path d="M16.2,17H14.2L12,13.2L9.8,17H7.8L11,12L7.8,7H9.8L12,10.8L14.2,7H16.2L13,12M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" />
</React.Fragment>,
'Excel'
)
import React from 'react'
import createSvgIcon from '@material-ui/icons/utils/createSvgIcon'
export default createSvgIcon(
<React.Fragment>
<path d="M14,17H7V15H14M17,13H7V11H17M17,9H7V7H17M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3Z" />
</React.Fragment>,
'File'
)
import React from 'react'
import createSvgIcon from '@material-ui/icons/utils/createSvgIcon'
export default createSvgIcon(
<React.Fragment>
<path d="M8.5,13.5L11,16.5L14.5,12L19,18H5M21,19V5C21,3.89 20.1,3 19,3H5A2,2 0 0,0 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19Z" />
</React.Fragment>,
'Image'
)
import React from 'react'
import createSvgIcon from '@material-ui/icons/utils/createSvgIcon'
export default createSvgIcon(
<React.Fragment>
<path d="M11.43,10.94C11.2,11.68 10.87,12.47 10.42,13.34C10.22,13.72 10,14.08 9.92,14.38L10.03,14.34V14.34C11.3,13.85 12.5,13.57 13.37,13.41C13.22,13.31 13.08,13.2 12.96,13.09C12.36,12.58 11.84,11.84 11.43,10.94M17.91,14.75C17.74,14.94 17.44,15.05 17,15.05C16.24,15.05 15,14.82 14,14.31C12.28,14.5 11,14.73 9.97,15.06C9.92,15.08 9.86,15.1 9.79,15.13C8.55,17.25 7.63,18.2 6.82,18.2C6.66,18.2 6.5,18.16 6.38,18.09L5.9,17.78L5.87,17.73C5.8,17.55 5.78,17.38 5.82,17.19C5.93,16.66 6.5,15.82 7.7,15.07C7.89,14.93 8.19,14.77 8.59,14.58C8.89,14.06 9.21,13.45 9.55,12.78C10.06,11.75 10.38,10.73 10.63,9.85V9.84C10.26,8.63 10.04,7.9 10.41,6.57C10.5,6.19 10.83,5.8 11.2,5.8H11.44C11.67,5.8 11.89,5.88 12.05,6.04C12.71,6.7 12.4,8.31 12.07,9.64C12.05,9.7 12.04,9.75 12.03,9.78C12.43,10.91 13,11.82 13.63,12.34C13.89,12.54 14.18,12.74 14.5,12.92C14.95,12.87 15.38,12.85 15.79,12.85C17.03,12.85 17.78,13.07 18.07,13.54C18.17,13.7 18.22,13.89 18.19,14.09C18.18,14.34 18.09,14.57 17.91,14.75M19,3H5C3.89,3 3,3.89 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V5C21,3.89 20.1,3 19,3M17.5,14.04C17.4,13.94 17,13.69 15.6,13.69C15.53,13.69 15.46,13.69 15.37,13.79C16.1,14.11 16.81,14.3 17.27,14.3C17.34,14.3 17.4,14.29 17.46,14.28H17.5C17.55,14.26 17.58,14.25 17.59,14.15C17.57,14.12 17.55,14.08 17.5,14.04M8.33,15.5C8.12,15.62 7.95,15.73 7.85,15.81C7.14,16.46 6.69,17.12 6.64,17.5C7.09,17.35 7.68,16.69 8.33,15.5M11.35,8.59L11.4,8.55C11.47,8.23 11.5,7.95 11.56,7.73L11.59,7.57C11.69,7 11.67,6.71 11.5,6.47L11.35,6.42C11.33,6.45 11.3,6.5 11.28,6.54C11.11,6.96 11.12,7.69 11.35,8.59Z" />
</React.Fragment>,
'PDF'
)
import React from 'react'
import createSvgIcon from '@material-ui/icons/utils/createSvgIcon'
export default createSvgIcon(
<React.Fragment>
<path d="M9.8,13.4H12.3C13.8,13.4 14.46,13.12 15.1,12.58C15.74,12.03 16,11.25 16,10.23C16,9.26 15.75,8.5 15.1,7.88C14.45,7.29 13.83,7 12.3,7H8V17H9.8V13.4M19,3A2,2 0 0,1 21,5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V5C3,3.89 3.9,3 5,3H19M9.8,12V8.4H12.1C12.76,8.4 13.27,8.65 13.6,9C13.93,9.35 14.1,9.72 14.1,10.24C14.1,10.8 13.92,11.19 13.6,11.5C13.28,11.81 12.9,12 12.22,12H9.8Z" />
</React.Fragment>,