Commit a16e38c7 authored by Alexandros Georgantas's avatar Alexandros Georgantas

refactor(template manager): building feature

parent 50ac3778
const orderBy = require('lodash/orderBy')
const map = require('lodash/map')
const find = require('lodash/find')
const clone = require('lodash/clone')
const path = require('path')
const fs = require('fs-extra')
const config = require('config')
......@@ -28,8 +29,6 @@ const getTemplate = async (_, { id }, ctx) => Template.query().findById(id)
const createTemplate = async (_, { input }, ctx) => {
const { templateName, author, files, target } = input
const fileIds = []
let thumbnailId
const allowedFonts = ['.otf', '.woff', '.woff2']
const allowedFiles = ['.css', '.otf', '.woff', '.woff2']
......@@ -42,6 +41,7 @@ const createTemplate = async (_, { input }, ctx) => {
templateName,
author,
target,
files: [],
}).save()
await Promise.all(
......@@ -72,14 +72,12 @@ const createTemplate = async (_, { input }, ctx) => {
return new Promise((resolve, reject) => {
stream.on('end', async () => {
try {
console.log('filename', typeof filename)
const newFile = await new File({
filename: 'hahahaha',
await new File({
filename,
mimetype,
src: outPath,
source: outPath,
templateId: newTemplate.id,
}).save()
fileIds.push(newFile.id)
resolve()
} catch (e) {
throw new Error(e)
......@@ -89,9 +87,7 @@ const createTemplate = async (_, { input }, ctx) => {
})
}),
)
return Template.query().patchAndFetchById(newTemplate.id, {
files: fileIds,
})
return newTemplate
} catch (e) {
throw new Error(e)
}
......@@ -112,8 +108,8 @@ module.exports = {
},
Template: {
async files(template, _, ctx) {
// TODO:
console.log('template', template)
const temp = await Template.findById(template.id)
return temp.getFiles()
},
},
Subscription: {},
......
......@@ -31,6 +31,14 @@ const large = css`
right: 40px;
top: 40px;
`
const largeNarrow = css`
bottom: 40px;
left: 20%;
right: 20%;
top: 40px;
`
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 */
......
......@@ -39,6 +39,7 @@ class Book extends Base {
constructor(properties) {
super(properties)
this.type = 'book'
console.log('in book c')
}
static get tableName() {
......
......@@ -5,6 +5,7 @@ const Base = require('../editoriaBase')
const {
arrayOfStringsNotEmpty,
foreignType,
string,
id,
integerPositive,
mimetype,
......@@ -16,6 +17,7 @@ class File extends Base {
constructor(properties) {
super(properties)
this.type = 'file'
console.log('in model c', this)
}
static get tableName() {
......@@ -63,7 +65,7 @@ class File extends Base {
},
}
}
static get schema() {
return {
type: 'object',
......
......@@ -12,7 +12,7 @@ create table file (
book_id uuid references book,
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,
......
......@@ -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',
......@@ -303,7 +303,7 @@ const schema = {
},
uri: {
type: 'string',
format: 'uri',
format: 'uri-reference',
},
year: {
// type: 'string',
......
......@@ -10,7 +10,6 @@ create table template (
reference_id uuid,
author text,
template_name text not null,
target text,
files text[]
name text not null,
target text
);
\ No newline at end of file
......@@ -22,13 +22,12 @@ 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,
target: targetType,
},
}
......
......@@ -20,6 +20,30 @@ const mapProps = args => {
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,
},
},
})
hideModal()
}
showModal('createTemplateModal', {
onConfirm,
hideModal,
})
},
refetching:
args.getTemplatesQuery.networkStatus === 4 ||
args.getTemplatesQuery.networkStatus === 2, // possible apollo bug
......@@ -30,11 +54,11 @@ const Composed = adopt(mapper, mapProps)
const Connected = () => (
<Composed>
{({ templates, createTemplate, onChangeSort, refetching, loading }) => {
{({ templates, onCreateTemplate, onChangeSort, refetching, loading }) => {
return (
<Templates
templates={templates}
createTemplate={createTemplate}
onCreateTemplate={onCreateTemplate}
onChangeSort={onChangeSort}
refetching={refetching}
loading={loading}
......
import React, { Component } from 'react'
import React, { Component, Fragment } from 'react'
import styled from 'styled-components'
import { UploadFilesButton } from './ui'
import { UploadFilesButton, TemplatesHeader, TemplatesGrid } from './ui'
const Container = styled.div`
display: block;
......@@ -21,7 +21,7 @@ export class Template extends Component {
render() {
const {
templates,
createTemplate,
onCreateTemplate,
onChangeSort,
refetching,
loading,
......@@ -31,8 +31,28 @@ export class Template extends Component {
return (
<Container>
<h1>Hello templates</h1>
<UploadFilesButton createTemplate={createTemplate} />
<Fragment>
<TemplatesHeader
canAddTemplates
onChangeSort={onChangeSort}
onCreateTemplate={onCreateTemplate}
title="Templates"
/>
<InnerWrapper>
<TemplatesGrid templates={templates} />
{/* <TemplateList
templates={templates}
// bookRules={rules.bookRules}
// refetching={refetching}
// onDeleteBook={onDeleteBook}
// onArchiveBook={onArchiveBook}
// remove={deleteBook}
// renameBook={renameBook}
// archiveBook={archiveBook}
/> */}
{/* <UploadFilesButton createTemplate={createTemplate} /> */}
</InnerWrapper>
</Fragment>
</Container>
)
}
......
......@@ -6,6 +6,9 @@ const CREATE_TEMPLATE = gql`
mutation CreateTemplate($input: CreateTemplateInput!) {
createTemplate(input: $input) {
id
files {
filename
}
}
}
`
......
export { default as UploadButton } from './src/UploadButton'
export { default as UploadFilesButton } from './src/UploadFilesButton'
export { default as TemplatesHeader } from './src/TemplatesHeader'
export { default as TemplatesGrid } from './src/TemplatesGrid'
export { default as Template } from './src/Template'
export { ButtonWithIcon, DefaultButton, ButtonWithoutLabel } from './src/Button'
import React from 'react'
import styled from 'styled-components'
import { th } from '@pubsweet/ui-toolkit'
const Button = styled.button`
align-items: center;
cursor: pointer;
background: none;
border: none;
color: #828282;
display: flex;
padding: 0;
border-bottom: 1px solid ${th('colorBackground')};
&:not(:disabled):hover {
color: ${th('colorPrimary')};
svg {
#circle {
fill: ${th('colorPrimary')};
}
#cross {
fill: ${th('colorPrimary')};
}
}
}
&:not(:disabled):active {
border: none;
color: ${th('colorPrimary')};
outline: none;
border-bottom: 1px solid ${th('colorPrimary')};
svg {
#circle {
fill: ${th('colorPrimary')};
}
#cross {
fill: ${th('colorPrimary')};
}
}
}
&:focus {
outline: 0;
}
`
const Icon = styled.i`
height: calc(3.5 * ${th('gridUnit')});
/* margin: 0 ${th('gridUnit')} 0 0; */
display: flex;
align-items:center;
justify-content: center;
padding: 0;
width: calc(3.5 * ${th('gridUnit')});
`
const Label = styled.span`
font-family: 'Fira Sans Condensed';
font-size: ${th('fontSizeBase')};
line-height: ${th('lineHeightBase')};
`
const addTemplateButton = (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.79999 7.2H10.4C10.84 7.2 11.2 7.56 11.2 8C11.2 8.44 10.84 8.8 10.4 8.8H8.79999V10.4C8.79999 10.84 8.43999 11.2 7.99999 11.2C7.55999 11.2 7.19999 10.84 7.19999 10.4V8.8H5.59999C5.15999 8.8 4.79999 8.44 4.79999 8C4.79999 7.56 5.15999 7.2 5.59999 7.2H7.19999V5.6C7.19999 5.16 7.55999 4.8 7.99999 4.8C8.43999 4.8 8.79999 5.16 8.79999 5.6V7.2Z"
fill="#828282"
id="circle"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
id="cross"
d="M8 0C3.5888 0 0 3.5888 0 8C0 12.4112 3.5888 16 8 16C12.4112 16 16 12.4112 16 8C16 3.5888 12.4112 0 8 0ZM8 14.4C4.4712 14.4 1.6 11.5288 1.6 8C1.6 4.4712 4.4712 1.6 8 1.6C11.5288 1.6 14.4 4.4712 14.4 8C14.4 11.5288 11.5288 14.4 8 14.4Z"
fill="#828282"
/>
</svg>
)
const label = 'Add Template'
const AddTemplateButton = ({ onClick }) => (
<Button data-cy="add-book-btn" onClick={onClick}>
<Icon>{addTemplateButton}</Icon>
<Label>{label.toLocaleUpperCase()}</Label>
</Button>
)
export default AddTemplateButton
......@@ -19,8 +19,8 @@ const Button = styled.button`
fill: #828282;
}
}
width:28px;
height:28px;
width:16px;
height:16px;
}
&:disabled {
......@@ -65,10 +65,10 @@ const Button = styled.button`
}
`
const Icon = styled.span`
height: calc(3.5 * ${th('gridUnit')});
/* margin: 0 ${th('gridUnit')} 0 0; */
height: calc(2 * ${th('gridUnit')});
margin: 0 ${th('gridUnit')} 0 0;
padding: 0;
width: calc(3.5 * ${th('gridUnit')});
width: calc(2 * ${th('gridUnit')});
`
const OnlyIcon = styled.span`
height: calc(3.5 * ${th('gridUnit')});
......
import React from 'react'
import styled, { css } from 'styled-components'
const transition = css`
transition: 0.2s ease;
`
const Wrapper = styled.div`
border-radius: 50%;
cursor: pointer;
height: 24px;
margin-left: 4px;
width: 24px;
> svg {
> #up {
fill: ${props => (props.ascending ? '#0d78f2' : '#3f3f3f')};
${transition};
}
> #down {
fill: ${props => (props.ascending ? '#3f3f3f' : '#0d78f2')};
${transition};
}
}
&:hover {
background: #f0f0f0;
${transition};
}
`
const Arrows = props => {
const { ascending, className, ...rest } = props
return (
<Wrapper
ascending={ascending}
className={className}
title={ascending ? 'Ascending' : 'Descending'}
{...rest}
>
<svg viewBox="0 0 28 28" xmlns="http://www.w3.org/2000/svg">
<path
d="M11.2857 20L10.7143 20C9.53292 20 8.5715 19.0129 8.5715 17.8L8.5715 10.2594L6.16081 12.2394C5.85296 12.4917 5.40225 12.4411 5.15654 12.125C4.91011 11.8082 4.96011 11.3469 5.26796 11.0939L8.83935 8.1606C8.97007 8.05353 9.12792 8 9.28578 8C9.44363 8 9.60149 8.05353 9.7322 8.1606L13.3036 11.0939C13.6114 11.3469 13.6614 11.8082 13.415 12.125C13.1693 12.4411 12.7193 12.4924 12.4107 12.2394L10.0001 10.2594L10.0001 17.8C10.0001 18.2041 10.3208 18.5333 10.7143 18.5333L11.2857 18.5333C11.68 18.5333 12 18.8619 12 19.2667C12 19.6715 11.68 20 11.2857 20Z"
id="up"
/>
<path
d="M16.2858 8H16.8572C18.0386 8 19 8.98707 19 10.2V17.7406L21.4107 15.7606C21.7186 15.5083 22.1693 15.5589 22.415 15.875C22.6614 16.1918 22.6114 16.6531 22.3036 16.9061L18.7322 19.8394C18.6015 19.9465 18.4436 20 18.2858 20C18.1279 20 17.97 19.9465 17.8393 19.8394L14.2679 16.9061C13.9601 16.6531 13.9101 16.1918 14.1565 15.875C14.4022 15.5589 14.8522 15.5076 15.1608 15.7606L17.5715 17.7406V10.2C17.5715 9.79593 17.2508 9.46667 16.8572 9.46667H16.2858C15.8915 9.46667 15.5715 9.13813 15.5715 8.73333C15.5715 8.32853 15.8915 8 16.2858 8Z"
id="down"
/>
</svg>
</Wrapper>
)
}
export default Arrows
import React from 'react'
import styled, { css } from 'styled-components'
import { State } from 'react-powerplug'
import { th } from '@pubsweet/ui-toolkit'
import { Menu as UIMenu } from '@pubsweet/ui'
import SortIcon from './SortIcon'
const triangle = css`
background: #3f3f3f;
content: ' ';
display: block;
height: 15px;
position: absolute;
transition: 0.2s ease-in-out;
width: 15px;
z-index: 200;
`
const triangleLeft = css`
${triangle};
clip-path: polygon(49% 49%, 0 0, 0 100%);
`
const triangleUp = css`
${triangle};
clip-path: polygon(0% 100%, 50% 50%, 100% 100%);
`
const triangleOption = css`
${triangleLeft};
left: 0;
top: 8px;
`
const Menu = styled(UIMenu)`
display: inline-flex;
div[role='listbox'] {
background: white;
> div:nth-child(2) {
left: 95%;
transform: translate(-95%, 0);
width: 120px;
z-index: 100;
}
div[open] {
background: white;
border: 1px solid #666;
box-shadow: 0 2px 10px #666;
margin-top: 16px;
position: relative;
overflow-y: unset;
text-transform: uppercase;
width: 120px;
&::before {
${triangleUp}
left: calc(50% - 15px / 2);
top: -19px;
}
}
div[role='option'] {
cursor: pointer;
font-family: 'Fira Sans Condensed';
padding: 4px 4px 4px 12px;
font-size: ${th('fontSizeBase')};
line-height: ${th('lineHeightBase')};
color: #828282;
position: relative;
&::selection {
background: none;
}
&::before {
${triangleOption}
opacity: 0;
}
&[aria-selected='true'] {
color: #0d78f2;
font-weight: normal;
&::before {
background: #0d78f2;
opacity: 1;
}
}
&:hover {
background: #fafafa;
color: #0d78f2;
transition: 0.2s ease-in-out;
&::before {
background: #0d78f2;
opacity: 1;
}
}
}
}
`
const OpenerWrapper = styled.div`
display: flex;
align-items: center;
font-size: ${th('fontSizeBase')};
line-height: ${th('lineHeightBase')};
> span {
font-family: 'Fira Sans Condensed';
text-transform: uppercase;
color: #828282;
span {
cursor: pointer;
color: #3f3f3f;
font-weight: bold;
}
}
`
const Opener = props => {
const { ascending, onChangeSortOrder, selected, toggleMenu } = props
return (
<OpenerWrapper>
<span>
Sort By <span onClick={toggleMenu}>{selected}</span>
</span>
<SortIcon ascending={ascending} onClick={onChangeSortOrder} />
</OpenerWrapper>
)
}
const options = [
{
label: 'name',
value: 'name',
},
{
label: 'author',
value: 'author',
},
{
label: 'target type',
value: 'targetType',
},
]
const SortMenu = ({ onChange }) => (
<State
initial={{ ascending: true, sortKey: 'name' }}
onChange={onChange}
>
{({ state, setState }) => {
const { ascending, sortKey } = state
const handleChangeSortKey = value => {
setState({ sortKey: value })
}
const handleChangeSortOrder = () => {
setState({ ascending: !state.ascending })
}
return (
<Menu
ascending={ascending}
onChange={handleChangeSortKey}
onChangeSortOrder={handleChangeSortOrder}
options={options}
renderOpener={Opener}
value={sortKey}
/>
)
}}
</State>
)
export default SortMenu
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;
align-items: flex-start;
justify-content: flex-start;
margin-top: 20px;
`
const StyledButton = styled.button`
align-items: center;
justify-content: center;
cursor: pointer;
background: white;
border: none;
font-size: 18px;
color: #828282;
width: 110px;
height: 40px;
display: flex;
margin-bottom: 10px;
padding: 0;
border-bottom: 1px solid ${th('colorBackground')};
&:not(:disabled):hover {
color: ${th('colorPrimary')};
}
&:not(:disabled):active {
border: none;
color: ${th('colorPrimary')};
outline: none;
border-bottom: 1px solid ${th('colorPrimary')};
}
&:focus {
outline: 0;
}
`
const Overlay = styled.div`
background: transparent;
/* transition: 0.5s ease; */
opacity: 1;
position: absolute;
top: 0;
left: 0;