Commit 3378924b authored by Alexandros Georgantas's avatar Alexandros Georgantas

Merge branch 'template-manager' into 'master'

Template manager

See merge request !144
parents 104ca992 69278a80
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 {
......
......@@ -6,6 +6,7 @@ const customTag = require('./customTag')
const division = require('./division')
const team = require('./team')
const user = require('./user')
const template = require('./template')
// const bookCollectionTranslation = require('./bookCollectionTranslation')
// const bookComponentState = require('./bookComponentState')
// const bookComponentTranslation = require('./bookComponentTranslation')
......@@ -24,6 +25,7 @@ module.exports = {
file.typeDefs,
team.typeDefs,
user.typeDefs,
template.typeDefs,
].join(' '),
resolvers: merge(
{},
......@@ -35,6 +37,7 @@ module.exports = {
division.resolvers,
file.resolvers,
team.resolvers,
template.resolvers,
user.resolvers,
),
// context: {
......
const TEMPLATE_CREATED = 'TEMPLATE_CREATED'
const TEMPLATE_UPDATED = 'TEMPLATE_UPDATED'
const TEMPLATE_DELETED = 'TEMPLATE_DELETED'
module.exports = { TEMPLATE_CREATED, TEMPLATE_UPDATED, TEMPLATE_DELETED }
const models = require('editoria-data-model')
module.exports = {
resolvers: require('./template.resolvers'),
typeDefs: require('../graphqlLoaderUtil')('template/template.graphql'),
model: models.template,
}
type Template {
id: ID!
name: String!
thumbnail: File
author: String
trimSize: String
target: String
files: [File]!
}
input CreateTemplateInput {
name: String!
thumbnail: Upload
author: String
trimSize: String
target: String
files: [Upload]!
}
input UpdateTemplateInput {
id: ID!
name: String!
thumbnail: Upload
deleteThumbnail: String
author: String
trimSize: String
target: String
files: [Upload]!
deleteFiles: [String]!
}
extend type Query {
getTemplates(ascending: Boolean, sortKey: String): [Template]!
getTemplate(id: ID!): Template!
}
extend type Mutation {
createTemplate(input: CreateTemplateInput): Template!
updateTemplate(input: UpdateTemplateInput): Template!
deleteTemplate(id: ID!): ID!
}
extend type Subscription {
templateCreated: Template!
templateDeleted: Template!
templateUpdated: Template!
}
This diff is collapsed.
......@@ -31,6 +31,14 @@ const large = css`
right: 40px;
top: 40px;
`
const largeNarrow = css`
bottom: 10%;
left: 20%;
right: 20%;
top: 10%;
`
const medium = css`
height: 530px;
top: 50%;
......@@ -74,6 +82,7 @@ const StyledModal = styled(ReactModalAdapter).attrs({
/* stylelint-disable order/properties-alphabetical-order */
${props => props.size === 'large' && large};
${props => props.size === 'largeNarrow' && largeNarrow};
${props => props.size === 'small' && small};
${props => props.size === 'medium' && medium};
/* stylelint-enable order/properties-alphabetical-order */
......
......@@ -31,6 +31,13 @@ const large = css`
right: 40px;
top: 40px;
`
const largeNarrow = css`
bottom: 40px;
width: 940px;
left: 25%;
top: 40px;
`
const medium = css`
height: 650px;
top: 50%;
......@@ -74,6 +81,7 @@ const StyledModal = styled(ReactModalAdapter).attrs({
/* stylelint-disable order/properties-alphabetical-order */
${props => props.size === 'large' && large};
${props => props.size === 'largeNarrow' && largeNarrow};
${props => props.size === 'small' && small};
${props => props.size === 'medium' && medium};
/* stylelint-enable order/properties-alphabetical-order */
......
......@@ -27,13 +27,7 @@ const Base = require('../editoriaBase')
const { model: BookCollection } = require('../bookCollection')
const { model: Division } = require('../division')
const {
booleanDefaultFalse,
date,
id,
string,
year,
} = require('../helpers').schema
const { booleanDefaultFalse, id, string, year } = require('../helpers').schema
class Book extends Base {
constructor(properties) {
......@@ -121,6 +115,7 @@ class Book extends Base {
async addDivision(label) {
return new Division({
bookId: this.id,
bookComponents: [],
label,
}).save()
}
......
......@@ -13,7 +13,7 @@ create table book (
collection_id uuid not null references book_collection,
/*
to do
we cannot enforce the integrity of division id's, as an array of foreign
we ceannot enforc the integrity of division id's, as an array of foreign
keys is not yet supported in postgres. there seems to be some work on this,
so we should update when the feature is in postgres.
*/
......
......@@ -41,7 +41,7 @@ class Division extends Base {
static get relationMappings() {
const { model: Book } = require('../book')
const { model: BookComponent } = require('../bookComponent')
// const { model: BookComponent } = require('../bookComponent')
return {
book: {
......@@ -52,14 +52,14 @@ class Division extends Base {
to: 'Book.id',
},
},
bookComponents: {
relation: Model.HasManyRelation,
modelClass: BookComponent,
join: {
from: 'BookComponent.divisionId',
to: 'Division.id',
},
},
// bookComponents: {
// relation: Model.HasManyRelation,
// modelClass: BookComponent,
// join: {
// from: 'Division.id',
// to: 'BookComponent.divisionId',
// },
// },
}
}
......
......@@ -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,
......
......@@ -10,13 +10,13 @@ create table file (
--foreign
book_id uuid references book,
book_component_id uuid references bookComponent
book_component_id uuid references book_component,
template_id uuid references template,
reference_id uuid not null,
reference_id uuid,
size int,
foreign_type text,
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: {
......@@ -286,7 +286,7 @@ const schema = {
},
mimetype: {
type: 'string',
pattern: /^(application|audio|font|image|model|multipart|text|video)\/[a-z0-9]+([-+.][a-z0-9]+)*$/,
pattern: "^(application|audio|font|image|model|multipart|text|video)\/[a-z0-9]+([-+.][a-z0-9]+)*$",
// if you want to know why this is default, look at
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Complete_list_of_MIME_types
default: 'application/octet-stream',
......@@ -295,7 +295,7 @@ const schema = {
type: 'object',
},
string: {
type: 'string',
type: ['string', 'null'],
},
stringNotEmpty: {
type: 'string',
......@@ -303,7 +303,7 @@ const schema = {
},
uri: {
type: 'string',
format: 'uri',
format: 'uri-reference',
},
year: {
// type: 'string',
......
......@@ -10,8 +10,7 @@ create table template (
reference_id uuid,
author text,
thumbnail_id uuid references file,
templateName text not null
target string,
files text[],
name text not null,
target text,
trim_size text
);
\ No newline at end of file
exports.up = async knex =>
knex.schema.table('template', table => {
table
.uuid('thumbnailId')
.nullable()
.references('file')
})
const { Model } = require('./node_modules/objection')
const { Model } = require('objection')
const remove = require('lodash/remove')
const Base = require('../editoriaBase')
const { arrayOfIds, id, stringNotEmpty, string, targetType } = require('../helpers').schema
const { id, stringNotEmpty, string, targetType } = require('../helpers').schema
class Template extends Base {
constructor(properties) {
......@@ -16,14 +16,14 @@ class Template extends Base {
static get schema() {
return {
type: 'object',
required: ['templateName'],
required: ['name'],
properties: {
templateName: stringNotEmpty,
name: stringNotEmpty,
referenceId: id,
author: string,
thumbnailId: id,
files: arrayOfIds,
author: string,
target: targetType,
trimSize: string,
},
}
}
......@@ -51,11 +51,21 @@ class Template extends Base {
}
}
getFiles() {
return this.$relatedQuery('files')
async getFiles() {
const { thumbnailId } = this
const associatedFiles = await this.$relatedQuery('files')
if (thumbnailId) {
remove(associatedFiles, file => file.id === thumbnailId)
}
remove(associatedFiles, file => file.deleted === true)
return associatedFiles
}
getThumbnail() {
return this.$relatedQuery('thumbnail')
async getThumbnail() {
const { thumbnailId } = this
if (thumbnailId) {
return this.$relatedQuery('thumbnail')
}
return null
}
}
......
......@@ -52,7 +52,10 @@ class Navigation extends React.Component {
render() {
const { logoutUser, currentUser, client } = this.props
const links = [<Action to="/books">Books</Action>]
const links = [
<Action to="/books">Books</Action>,
<Action to="/templates">Templates</Action>,
]
if (currentUser === null) return null
......
{
"name": "pubsweet-component-editoria-templates",
"version": "0.1.0",
"description": "The application dashboard for the Editoria project.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"pubsweet-component",
"pubsweet-client"
],
"repository": {
"type": "git",
"url": "https://gitlab.coko.foundation/editoria/editoria-templates"
},
"author": "Alexandros Georgantas",
"license": "ISC",
"dependencies": {
"@pubsweet/ui": "^10.3.0",
"editoria-common": "^0.1.6",
"formik": "^1.5.1",
"lodash": "4.17.4",
"pubsweet-client": "^9.2.3",
"react": "^16.2.0",
"react-adopt": "^0.6.0",
"react-modal": "^3.6.1",
"react-powerplug": "^1.0.0",
"react-router-dom": "^5.0.0",
"styled-components": "^4.1.3",
"react-select":"^3.0.4"
},
"devDependencies": {
"enzyme": "^2.9.1",
"enzyme-to-json": "^1.5.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^20.0.4",
"prop-types": "^15.5.10",
"react-addons-test-utils": "^15.6.0",
"react-test-renderer": "^15.6.1",
"sinon": "^2.3.8",
"sinon-as-promised": "^4.0.3"
}
}
import React from 'react'
import { get } from 'lodash'
import { adopt } from 'react-adopt'
import withModal from 'editoria-common/src/withModal'
import Templates from './Templates'
import {
getTemplatesQuery,
createTemplateMutation,
updateTemplateMutation,
deleteTemplateMutation,
templateCreatedSubscription,
templateUpdatedSubscription,
templateDeletedSubscription,
} from './queries'
const mapper = {
withModal,
getTemplatesQuery,
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,
loading: args.getTemplatesQuery.networkStatus === 1,
onChangeSort: args.getTemplatesQuery.refetch,
onCreateTemplate: () => {
const { createTemplateMutation, withModal } = args
const { createTemplate } = createTemplateMutation
const { showModal, hideModal } = withModal
const onConfirm = ({
files,
thumbnail,
name,
author,
target,
trimSize,
}) => {
createTemplate({
variables: {
input: {
files,
name,
author,
target,
trimSize,
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) => {
const { deleteTemplateMutation, withModal } = args
const { deleteTemplate } = deleteTemplateMutation
const { showModal, hideModal } = withModal
const onConfirm = () => {
deleteTemplate({
variables: {
id: templateId,
},
})
hideModal()
}
showModal('deleteTemplateModal', {
onConfirm,
templateName,
})
},
refetching:
args.getTemplatesQuery.networkStatus === 4 ||
args.getTemplatesQuery.networkStatus === 2, // possible apollo bug
}
}
const Composed = adopt(mapper, mapProps)
const Connected = () => (
<Composed>
{({
templates,
onCreateTemplate,
onUpdateTemplate,
onDeleteTemplate,
onChangeSort,
refetching,
loading,
createTemplate,
}) => {
return (
<Templates
templates={templates}
onCreateTemplate={onCreateTemplate}
onUpdateTemplate={onUpdateTemplate}
onDeleteTemplate={onDeleteTemplate}
onChangeSort={onChangeSort}
refetching={refetching}
createTemplate={createTemplate}
loading={loading}
/>
)
}}
</Composed>
)
export default Connected
import React, { Component, Fragment } from 'react'
import styled from 'styled-components'
import { UploadFilesButton, TemplatesHeader, TemplatesGrid } from './ui'
const Container = styled.div`
display: block;
clear: both;
float: none;
margin: 0 auto;
max-width: 100%;
`
const InnerWrapper = styled.div`
display: block;
clear: both;
float: none;
margin: 0 auto;
max-width: 76%;
`
export class Template extends Component {
render() {
const {
templates,
onCreateTemplate,
onUpdateTemplate,
onDeleteTemplate,
onChangeSort,
loading,
} = this.props
if (loading) return 'Loading...'
return (
<Container>
<Fragment>
<TemplatesHeader
canAddTemplates
onChangeSort={onChangeSort}
onCreateTemplate={onCreateTemplate}
title="Templates"
/>
<InnerWrapper>
<TemplatesGrid
onDeleteTemplate={onDeleteTemplate}
onUpdateTemplate={onUpdateTemplate}
templates={templates}
/>
</InnerWrapper>
</Fragment>
</Container>
)
}
}
export default Template
import React from 'react'
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
const CREATE_TEMPLATE = gql`
mutation CreateTemplate($input: CreateTemplateInput!) {
createTemplate(input: $input) {
id
files {
name
}
}
}
`
const createTemplateMutation = props => {
const { render } = props
return (
<Mutation mutation={CREATE_TEMPLATE}>
{(createTemplate, createTemplateResult) =>
render({ createTemplate, createTemplateResult })
}
</Mutation>
)
}
export default createTemplateMutation
import React from 'react'
import { Mutation } from 'react-apollo'
import gql from 'graphql-tag'
const DELETE_TEMPLATE = gql`
mutation DeleteTemplate($id: ID!) {
deleteTemplate(id: $id)
}
`
const deleteTemplateMutation = props => {
const { render } = props
return (
<Mutation mutation={DELETE_TEMPLATE}>
{(deleteTemplate, deleteTemplateResult) =>
render({ deleteTemplate, deleteTemplateResult })
}
</Mutation>
)
}
export default deleteTemplateMutation