pubsweet-component-wax
Version:
PubSweet component for the Wax collaborative document editor
474 lines (427 loc) • 12.8 kB
JavaScript
import { get, isEmpty } from 'lodash'
import React from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'
import config from 'config'
import Wax from 'wax-editor-react'
import WaxHeader from './WaxHeader'
const Container = styled.div`
display: flex;
flex-direction: column;
height: 100%;
overflow-y: hidden;
width: 100%;
.editor-wrapper {
height: 88vh;
}
`
export class WaxPubsweet extends React.Component {
constructor(props) {
super(props)
this.fileUpload = this.fileUpload.bind(this)
this.save = this.save.bind(this)
this.update = this.update.bind(this)
this.renderWax = this.renderWax.bind(this)
this.onUnload = this.onUnload.bind(this)
this.unlock = this.unlock.bind(this)
this.lock = this.lock.bind(this)
this.handleAssetManager = this.handleAssetManager.bind(this)
}
componentWillMount(nextProps) {
const { bookComponentId, editing } = this.props
if (editing === 'preview' || editing === 'selection') return
this.lock(bookComponentId)
}
componentDidMount() {
const { editing } = this.props
if (editing === 'preview' || editing === 'selection') return
window.addEventListener('beforeunload', this.onUnload)
}
componentWillReceiveProps(nextProps) {
const { history, onUnlocked } = this.props
const { bookComponent: bookComponentBefore, bookComponentId } = this.props
const { lock: lockBefore } = bookComponentBefore
const { bookComponent: bookComponentAfter } = nextProps
const { lock: lockAfter, id: nextPropId } = bookComponentAfter
const onConfirm = () => {
history.push(`/books/${bookComponentAfter.bookId}/book-builder`)
}
if (
lockBefore !== null &&
lockAfter === null &&
nextPropId === bookComponentId
) {
onUnlocked(
'The admin just unlocked this book component!! You will be redirected back to the Book Builder.',
onConfirm,
)
}
}
componentWillUnmount() {
const {
bookComponent: { id },
editing,
} = this.props
if (editing === 'preview' || editing === 'selection') return
this.unlock(id)
}
unlock(id) {
const { unlockBookComponent } = this.props
unlockBookComponent({
variables: {
input: {
id,
},
},
})
window.removeEventListener('beforeunload', this.onUnload)
}
lock(id) {
const { lockBookComponent } = this.props
lockBookComponent({
variables: {
input: {
id,
},
},
})
}
save(content) {
const { bookComponent, updateBookComponentContent } = this.props
return updateBookComponentContent({
variables: {
input: {
id: bookComponent.id,
content,
},
},
})
}
handleAssetManager() {
const { bookId, onAssetManager } = this.props
return onAssetManager(bookId)
}
// TODO -- Theoretically, we shouldn't lock when the editor is in read only
// mode. This gets complicated however, as the user will be able to be add
// comments, which will in turn affect the fragment.
shouldLock() {
const { config } = this.props
return config.lockWhenEditing
}
fileUpload(file) {
const { uploadFile } = this.props
return new Promise((resolve, reject) => {
uploadFile({
variables: {
file,
},
}).then(res => {
resolve({ file: `/uploads${res.data.upload.url}` })
})
})
}
update(patch) {
const {
bookComponent,
updateBookComponentTrackChanges,
renameBookComponent,
updateCustomTags,
addCustomTags,
} = this.props
const { trackChanges, title, tags } = patch
if (tags) {
const addTags = tags.filter(tag => !tag.id)
const updateTags = tags.filter(tag => tag.id)
if (addTags.length > 0) {
addCustomTags({
variables: {
input: addTags,
},
})
}
if (updateTags.length > 0) {
updateCustomTags({
variables: {
input: updateTags,
},
})
}
}
if (trackChanges !== undefined) {
return updateBookComponentTrackChanges({
variables: {
input: {
id: bookComponent.id,
trackChangesEnabled: trackChanges,
},
},
})
}
if (title) {
return renameBookComponent({
variables: {
input: {
id: bookComponent.id,
title,
},
},
})
}
// const { actions, book, fragment, history } = this.props
// const { updateFragment } = actions
// // if (!patch.id) { patch.id = fragment.id }
// // return updateFragment(book, patch)
// this.stackUpdateData.push(patch)
// // TODO -- this is temporary but works
// // It should DEFINITELY be removed in the near future though
// /* eslint-disable */
// debounce(() => {
// const patchData = this.stackUpdateData.reduce((acc, x) => {
// for (const key in x) acc[key] = x[key]
// return acc
// }, {})
// if (this.stackUpdateData.length > 0) {
// if (!patchData.id) {
// patchData.id = fragment.id
// }
// updateFragment(book, patchData).then(res => {
// // const { user, fragment } = this.props
// if (res.error) {
// // When you reload within the editor check if the same user has the lock
// // if (fragment.lock && user.id !== fragment.lock.editor.userId) {
// history.push(`/books/${book.id}/book-builder`)
// // }
// } else if (this.state.lockConflict !== false) {
// this.setState({ lockConflict: false })
// }
// })
// }
// this.stackUpdateData = []
// }, 100)()
// /* eslint-enable */
return Promise.resolve()
}
onUnload(event) {
const { bookComponentId } = this.props
this.unlock(bookComponentId)
}
renderWax(editing) {
const { bookComponent, checkSpell, history, user, tags } = this.props
const waxConfig = {
layout: config.wax.layout,
lockWhenEditing: config.wax.lockWhenEditing,
theme: config.wax.theme,
autoSave: config.wax.autoSave,
menus: (
config.wax[bookComponent.divisionType.toLowerCase()][
bookComponent.componentType
] || config.wax[bookComponent.divisionType.toLowerCase()].default
).menus,
}
const { layout, autoSave, menus } = waxConfig
// From editoria config, this is just for testing purposes
let translatedEditing
const mode = {
trackChanges: {
toggle: true,
view: true,
color: '#fff',
own: {
accept: true,
reject: true,
},
others: {
accept: true,
reject: true,
},
},
styling: true, // isAuthor
}
switch (editing) {
case 'selection':
mode.trackChanges.toggle = false
mode.trackChanges.view = true
mode.trackChanges.own.accept = false
mode.trackChanges.own.reject = false
mode.trackChanges.others.accept = false
mode.trackChanges.others.reject = false
mode.styling = false
translatedEditing = 'selection'
break
case 'preview':
mode.trackChanges.toggle = false
mode.trackChanges.view = false
mode.trackChanges.own.accept = false
mode.trackChanges.own.reject = false
mode.trackChanges.others.accept = false
mode.trackChanges.others.reject = false
mode.styling = false
translatedEditing = 'disabled'
break
case 'selection_without_tc':
mode.trackChanges.toggle = false
mode.trackChanges.view = false
mode.trackChanges.own.accept = false
mode.trackChanges.own.reject = false
mode.trackChanges.others.accept = false
mode.trackChanges.others.reject = false
mode.styling = false
translatedEditing = 'selection'
break
case 'review':
mode.trackChanges.toggle = false
mode.trackChanges.view = true
mode.trackChanges.own.accept = false
mode.trackChanges.own.reject = false
mode.trackChanges.others.accept = true
mode.trackChanges.others.reject = false
mode.styling = false
translatedEditing = 'full'
break
case 'full_without_tc':
mode.trackChanges.toggle = false
mode.trackChanges.view = false
mode.trackChanges.own.accept = false
mode.trackChanges.own.reject = false
mode.trackChanges.others.accept = true
mode.trackChanges.others.reject = false
mode.styling = true
translatedEditing = 'full'
break
default:
mode.trackChanges.toggle = true
mode.trackChanges.view = true
mode.trackChanges.own.accept = true
mode.trackChanges.own.reject = true
mode.trackChanges.others.accept = true
mode.trackChanges.others.reject = true
mode.styling = true
translatedEditing = 'full'
break
}
// TODO -- these won't change properly on fragment change
// see trackChanges hack in mapStateToProps
// const content = get(bookComponent, 'content')
let { content } = bookComponent
if (content === null) {
content = ''
}
const trackChangesEnabled = get(bookComponent, 'trackChangesEnabled')
let chapterNumber
if (get(bookComponent, 'componentType') === 'chapter') {
chapterNumber = get(bookComponent, 'componentTypeOrder')
}
return (
<Container>
<WaxHeader bookComponent={bookComponent} />
<Wax
assetManager={this.handleAssetManager}
autoSave={autoSave === undefined ? false : autoSave}
chapterNumber={chapterNumber}
checkSpell={checkSpell}
className="editor-wrapper"
content={content}
customTags={tags}
editing={translatedEditing}
fileUpload={this.fileUpload}
history={history}
layout={layout}
menus={menus}
mode={mode}
onSave={this.save}
trackChanges={trackChangesEnabled}
update={this.update}
user={user}
/>
</Container>
)
}
render() {
const { loading, waxLoading, teamsLoading, editing } = this.props
if (loading || waxLoading || teamsLoading || isEmpty(config))
return 'Loading...'
return this.renderWax(editing)
}
}
// TODO -- review required props
WaxPubsweet.propTypes = {
actions: PropTypes.objectOf(PropTypes.func).isRequired,
book: PropTypes.shape({
id: PropTypes.string,
rev: PropTypes.string,
title: PropTypes.string,
}).isRequired,
fragment: PropTypes.shape({
alignment: PropTypes.objectOf(PropTypes.bool),
author: PropTypes.string,
book: PropTypes.string,
division: PropTypes.string,
id: PropTypes.string,
index: PropTypes.number,
kind: PropTypes.string,
lock: PropTypes.shape({
editor: PropTypes.shape({
username: PropTypes.string,
}),
timestamp: PropTypes.oneOfType([
PropTypes.string,
PropTypes.instanceOf(Date),
]),
}),
number: PropTypes.number,
owners: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.string,
username: PropTypes.string,
}),
),
progress: PropTypes.objectOf(PropTypes.number),
rev: PropTypes.string,
source: PropTypes.string,
status: PropTypes.string,
subCategory: PropTypes.string,
title: PropTypes.string,
trackChanges: PropTypes.bool,
type: PropTypes.string,
}).isRequired,
history: PropTypes.any,
user: PropTypes.shape({
admin: PropTypes.bool,
email: PropTypes.string,
id: PropTypes.string,
rev: PropTypes.string,
type: PropTypes.string,
username: PropTypes.string,
}).isRequired,
}
/* eslint-disable */
WaxPubsweet.defaultProps = {
config: {
layout: 'default',
lockWhenEditing: false,
pollingTimer: 1000,
},
editing: 'full',
history: null,
}
/* eslint-enable */
// const getRoles = (user, book) => {
// const teams = filter(user.teams, t => {
// return t.object.id === book.id
// })
// let roles = []
// const addRole = role => {
// roles = union(roles, [role])
// }
// if (user.admin) addRole('admin')
// each(teams, team => {
// const name = team.teamType.name
// const modified = name
// .trim()
// .toLowerCase()
// .replace(' ', '-')
// addRole(modified)
// })
// return roles
// }
export default WaxPubsweet // withAuthsome()