Commit 69278a80 authored by Alexandros Georgantas's avatar Alexandros Georgantas

refactor(team manager):

parent 3ae1a5ff
type File {
id: ID!
filename: String!
name: String!
source: String!
mimetype: String!
}
input NewFileInput {
filename: String
name: String
source: String
mimetype: String
}
input UpdateFileInput {
id: ID!
name: String
source: String
mimetype: String
}
extend type Mutation {
......
......@@ -21,10 +21,12 @@ input UpdateTemplateInput {
id: ID!
name: String!
thumbnail: Upload
deleteThumbnail: String
author: String
trimSize: String
target: String
files: [Upload]!
deleteFiles: [String]!
}
extend type Query {
......
......@@ -33,7 +33,11 @@ const getTemplates = async (_, { ascending, sortKey }, ctx) => {
const result = map(sorted, item => find(templates, { id: item.id }))
return result
}
const getTemplate = async (_, { id }, ctx) => Template.findById(id)
const getTemplate = async (_, { id }, ctx) => {
const template = await Template.findById(id)
console.log('template', template)
return template
}
const createTemplate = async (_, { input }, ctx) => {
const { name, author, files, target, trimSize, thumbnail } = input
......@@ -93,7 +97,7 @@ const createTemplate = async (_, { input }, ctx) => {
try {
logger.info('File uploaded to server')
const newFile = await new File({
filename,
name: filename,
mimetype,
source: outPath,
templateId: newTemplate.id,
......@@ -150,7 +154,7 @@ const createTemplate = async (_, { input }, ctx) => {
try {
logger.info('Thumbnail uploaded to the server')
const newThumbnail = await new File({
filename,
name: filename,
mimetype,
source: outPath,
templateId: newTemplate.id,
......@@ -183,7 +187,205 @@ const createTemplate = async (_, { input }, ctx) => {
}
// TODO:
const updateTemplate = async (_, {}, ctx) => {}
const updateTemplate = async (_, { input }, ctx) => {
console.log('input', input)
const {
id,
name,
author,
files,
target,
trimSize,
thumbnail,
deleteFiles,
deleteThumbnail,
} = input
// const allowedFonts = ['.otf', '.woff', '.woff2']
const allowedThumbnails = ['.png', '.jpg', '.jpeg']
const allowedFiles = ['.css', '.otf', '.woff', '.woff2']
const regexFiles = new RegExp(
'([a-zA-Z0-9s_\\.-:])+(' + allowedFiles.join('|') + ')$',
)
const regexThumbnails = new RegExp(
'([a-zA-Z0-9s_\\.-:])+(' + allowedThumbnails.join('|') + ')$',
)
try {
const pubsub = await pubsubManager.getPubsub()
if (files.length > 0) {
logger.info(
`There is/are ${
files.length
} new file/s to be uploaded for the template`,
)
await Promise.all(
map(files, async file => {
const { createReadStream, filename, mimetype, encoding } = await file
if (!regexFiles.test(filename))
throw new Error('File extension is not allowed')
const outPath = path.join(uploadsPath, 'templates', id, filename)
await fs.ensureDir(uploadsPath)
await fs.ensureDir(`${uploadsPath}/templates`)
await fs.ensureDir(`${uploadsPath}/templates/${id}`)
logger.info(`The path the the files will be stored is ${outPath}`)
const outStream = fs.createWriteStream(outPath)
const stream = createReadStream()
stream.pipe(
outStream,
{ encoding },
)
outStream.on('error', () => {
throw new Error('Unable to write file')
})
return new Promise((resolve, reject) => {
stream.on('end', async () => {
try {
logger.info('File uploaded to server')
const newFile = await new File({
name: filename,
mimetype,
source: outPath,
templateId: id,
}).save()
logger.info(
`File representation created on the db with file id ${
newFile.id
}`,
)
resolve()
} catch (e) {
throw new Error(e)
}
})
stream.on('error', reject)
})
}),
)
}
if (deleteThumbnail) {
logger.info(
`Existing thumbnail with id ${deleteThumbnail} will be patched and set to deleted true`,
)
const deletedThumbnail = await File.query().patchAndFetchById(
deleteThumbnail,
{ deleted: true },
)
logger.info(`File with id ${deletedThumbnail.id} was patched`)
const thumbnailPath = path.join(
uploadsPath,
'templates',
id,
deletedThumbnail.name,
)
await fs.remove(thumbnailPath)
logger.info(
`File with name ${deletedThumbnail.name} removed from the server`,
)
await Template.query()
.patch({ thumbnailId: null })
.findById(id)
logger.info('Template thumbnailId property updated')
}
if (thumbnail) {
logger.info(
'There is a new thumbnail file to be uploaded for the template',
)
await new Promise(async (resolve, reject) => {
const {
createReadStream,
filename,
mimetype,
encoding,
} = await thumbnail
if (!regexThumbnails.test(filename))
throw new Error('File extension is not allowed')
const outPath = path.join(uploadsPath, 'templates', id, filename)
await fs.ensureDir(uploadsPath)
await fs.ensureDir(`${uploadsPath}/templates`)
await fs.ensureDir(`${uploadsPath}/templates/${id}`)
const outStream = fs.createWriteStream(outPath)
const stream = createReadStream()
stream.pipe(
outStream,
{ encoding },
)
outStream.on('error', () => {
throw new Error('Unable to write file')
})
stream.on('end', async () => {
try {
logger.info('Thumbnail uploaded to the server')
const newThumbnail = await new File({
name: filename,
mimetype,
source: outPath,
templateId: id,
}).save()
logger.info(
`Thumbnail representation created on the db with file id ${
newThumbnail.id
}`,
)
await Template.query()
.patch({ thumbnailId: newThumbnail.id })
.findById(id)
logger.info('Template thumbnailId property updated')
resolve()
} catch (e) {
throw new Error(e)
}
})
stream.on('error', reject)
})
}
if (deleteFiles) {
logger.info(
`Existing file/s with id/s ${deleteFiles} will be patched and set to deleted true`,
)
await Promise.all(
map(deleteFiles, async fileId => {
const deletedFile = await File.query().patchAndFetchById(fileId, {
deleted: true,
})
logger.info(`File with id ${deletedFile.id} was patched`)
const thumbnailPath = path.join(
uploadsPath,
'templates',
id,
deletedFile.name,
)
await fs.remove(thumbnailPath)
logger.info(
`File with name ${deletedFile.name} removed from the server`,
)
}),
)
}
const updatedTemplate = await Template.query().patchAndFetchById(id, {
name,
author,
trimSize,
target,
})
pubsub.publish(TEMPLATE_UPDATED, {
templateUpdated: updatedTemplate,
})
logger.info('Template updated msg broadcasted')
return updatedTemplate
} catch (e) {
throw new Error(e)
}
}
const deleteTemplate = async (_, { id }, ctx) => {
try {
......@@ -195,8 +397,23 @@ const deleteTemplate = async (_, { id }, ctx) => {
`Template with id ${toBeDeleted.id} patched with deleted set to true`,
)
const files = await toBeDeleted.getFiles()
const thumbnail = await toBeDeleted.getThumbnail()
const templatePath = path.join(uploadsPath, 'templates', toBeDeleted.id)
if (thumbnail) {
const deletedThumbnail = await File.query().patchAndFetchById(
thumbnail.id,
{
deleted: true,
},
)
logger.info(
`Thumbnail with id ${
deletedThumbnail.id
} patched with deleted set to true`,
)
}
logger.info(
`${
files.length
......@@ -241,10 +458,16 @@ module.exports = {
},
Template: {
async files(template, _, ctx) {
return template.getFiles()
console.log('template1', template)
const files = await template.getFiles()
console.log('files', files)
return files
},
async thumbnail(template, _, ctx) {
return template.getThumbnail()
console.log('template2', template)
const thumbnail = await template.getThumbnail()
console.log('thum', thumbnail)
return thumbnail
},
},
Subscription: {
......
......@@ -67,9 +67,9 @@ class File extends Base {
static get schema() {
return {
type: 'object',
required: ['filename', 'source'],
required: ['name', 'source'],
properties: {
filename: stringNotEmpty,
name: stringNotEmpty,
bookId: id,
bookComponentId: id,
templateId: id,
......
......@@ -18,5 +18,5 @@ create table file (
mimetype text,
source text,
tags text[],
filename text not null
name text not null
);
\ No newline at end of file
......@@ -265,7 +265,7 @@ const schema = {
],
},
id: {
type: 'string',
type: ['string', 'null'],
format: 'uuid',
},
integerPositive: {
......@@ -295,7 +295,7 @@ const schema = {
type: 'object',
},
string: {
type: 'string',
type: ['string', 'null'],
},
stringNotEmpty: {
type: 'string',
......
exports.up = async knex =>
knex.schema.table('template', table => {
table.uuid('thumbnailId').references('file')
table
.uuid('thumbnailId')
.nullable()
.references('file')
})
......@@ -61,9 +61,11 @@ class Template extends Base {
return associatedFiles
}
async getThumbnail() {
const associatedThumbnails = await this.$relatedQuery('thumbnail')
remove(associatedThumbnails, file => file.deleted === true)
return associatedThumbnails[0]
const { thumbnailId } = this
if (thumbnailId) {
return this.$relatedQuery('thumbnail')
}
return null
}
}
......
......@@ -7,6 +7,7 @@ import Templates from './Templates'
import {
getTemplatesQuery,
createTemplateMutation,
updateTemplateMutation,
deleteTemplateMutation,
templateCreatedSubscription,
templateUpdatedSubscription,
......@@ -16,17 +17,19 @@ import {
const mapper = {
withModal,
getTemplatesQuery,
createTemplateMutation,
deleteTemplateMutation,
templateCreatedSubscription,
templateUpdatedSubscription,
templateDeletedSubscription,
createTemplateMutation,
updateTemplateMutation,
deleteTemplateMutation,
}
const mapProps = args => {
return {
templates: get(args.getTemplatesQuery, 'data.getTemplates'),
createTemplate: args.createTemplateMutation.createTemplate,
updateTemplate: args.updateTemplateMutation.updateTemplate,
deleteTemplateMutation: args.deleteTemplateMutation.deleteTemplate,
showModal: args.withModal.showModal,
hideModal: args.withModal.hideModal,
......@@ -43,7 +46,7 @@ const mapProps = args => {
author,
target,
trimSize,
}) =>
}) => {
createTemplate({
variables: {
input: {
......@@ -55,11 +58,56 @@ const mapProps = args => {
thumbnail,
},
},
}).then(() => {
hideModal()
})
}
showModal('createTemplateModal', {
onConfirm,
hideModal,
headerText: 'Create New Template',
mode: 'create',
})
},
onUpdateTemplate: templateId => {
const { updateTemplateMutation, withModal } = args
const { updateTemplate } = updateTemplateMutation
const { showModal, hideModal } = withModal
const onConfirm = ({
files,
deleteFiles,
thumbnail,
deleteThumbnail,
name,
author,
target,
trimSize,
}) => {
updateTemplate({
variables: {
input: {
id: templateId,
files,
deleteThumbnail,
deleteFiles,
name,
author,
target,
trimSize,
thumbnail,
},
},
}).then(() => {
hideModal()
})
}
showModal('updateTemplateModal', {
onConfirm,
hideModal,
mode: 'update',
templateId,
headerText: 'Update Template',
})
},
onDeleteTemplate: (templateId, templateName) => {
......@@ -92,6 +140,7 @@ const Connected = () => (
{({
templates,
onCreateTemplate,
onUpdateTemplate,
onDeleteTemplate,
onChangeSort,
refetching,
......@@ -102,6 +151,7 @@ const Connected = () => (
<Templates
templates={templates}
onCreateTemplate={onCreateTemplate}
onUpdateTemplate={onUpdateTemplate}
onDeleteTemplate={onDeleteTemplate}
onChangeSort={onChangeSort}
refetching={refetching}
......
......@@ -22,10 +22,9 @@ export class Template extends Component {
const {
templates,
onCreateTemplate,
onUpdateTemplate,
onDeleteTemplate,
onChangeSort,
createTemplate,
refetching,
loading,
} = this.props
......@@ -41,18 +40,11 @@ export class Template extends Component {
title="Templates"
/>
<InnerWrapper>
<TemplatesGrid templates={templates} onDeleteTemplate={onDeleteTemplate} />
{/* <TemplateList
templates={templates}
// bookRules={rules.bookRules}
// refetching={refetching}
// onDeleteBook={onDeleteBook}
// onArchiveBook={onArchiveBook}
// remove={deleteBook}
// renameBook={renameBook}
// archiveBook={archiveBook}
/> */}
{/* <UploadFilesButton createTemplate={createTemplate} /> */}
<TemplatesGrid
onDeleteTemplate={onDeleteTemplate}
onUpdateTemplate={onUpdateTemplate}
templates={templates}
/>
</InnerWrapper>
</Fragment>
</Container>
......
......@@ -7,7 +7,7 @@ const CREATE_TEMPLATE = gql`
createTemplate(input: $input) {
id
files {
filename
name
}
}
}
......
import React from 'react'
import { Query } from 'react-apollo'
import gql from 'graphql-tag'
const GET_TEMPLATE = gql`
query GetTemplate($id: ID!) {
getTemplate(id: $id) {
id
name
thumbnail {
name
mimetype
id
source
}
author
trimSize
target
files {
name
mimetype
id
source
}
}
}
`
const getTemplateQuery = props => {
const { templateId: id, render } = props
return (
<Query
fetchPolicy="cache-and-network"
notifyOnNetworkStatusChange
query={GET_TEMPLATE}
variables={{ id }}
>
{render}
</Query>
)
}
export { GET_TEMPLATE }
export default getTemplateQuery
......@@ -10,6 +10,15 @@ const GET_TEMPLATES = gql`
getTemplates(ascending: $ascending, sortKey: $sortKey) {
id
name
thumbnail {
name
mimetype
id
source
}
author
trimSize
target
}
}
`
......
export { default as createTemplateMutation } from './createTemplate'
export { default as deleteTemplateMutation } from './deleteTemplate'
export { default as getTemplatesQuery } from './getTemplates'
export { default as getTemplateQuery } from './getTemplate'
export { default as updateTemplateMutation } from './updateTemplate'
export {
templateCreatedSubscription,
templateUpdatedSubscription,
......
import React from 'react'
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
const UPDATE_TEMPLATE = gql`
mutation UpdateTemplate($input: UpdateTemplateInput!) {
updateTemplate(input: $input) {
id
files {
name
}
}
}
`
const updateTemplateMutation = props => {
const { render } = props
return (
<Mutation mutation={UPDATE_TEMPLATE}>
{(updateTemplate, updateTemplateResult) =>
render({ updateTemplate, updateTemplateResult })
}
</Mutation>
)
}
export default updateTemplateMutation
import React from 'react'
import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
import { DefaultButton } from '../../ui'
const ButtonsContainer = styled.div`
display: flex;
flex-direction: column;
width:50%;
align-items: flex-start;
justify-content: flex-start;
margin-top: 20px;
`
const randomColor = () => {
const value = ((Math.random() * 0xffffff) << 0).toString(16)
return `#${value}`
}
const StyledButton = styled.button`
align-items: center;
justify-content: center;
......@@ -38,11 +43,46 @@ const StyledButton = styled.button`
outline: 0;
}
`
const PlaceholderContainer = styled.div`
height: 100%;
width: 188px;
svg {
#color {
fill: ${({ color }) => color};
}
}
`
const thumbnailPlaceholder = (
<svg
width="188"
height="282 "
viewBox="0 0 188 282"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect id="back" width="188" height="266" fill="url(#pattern0)" />
<rect id="color" width="188" height="282" fill="black" fillOpacity="0.3" />
<defs>
<pattern
id="pattern0"
patternContentUnits="objectBoundingBox"
width="1"
height="1"
>
<use transform="translate(-0.635627 -0.594882) scale(0.00182688 0.00129118)" />
</pattern>
<image id="image0" width="3902" height="2195" />
</defs>
</svg>
)
const Overlay = styled.div`
background: transparent;
z-index: 2;
/* transition: 0.5s ease; */
opacity: 1;
opacity: 0;
position: absolute;
top: 0;
left: 0;
......@@ -50,13 +90,18 @@ const Overlay = styled.div`
width: 100%;
`
const Image = styled.img`
height: 100%;
width: 188px;
`