jsreport-studio
Version:
jsreport templates editor and designer
415 lines (338 loc) • 12.1 kB
JavaScript
import * as entities from '../entities'
import * as progress from '../progress'
import * as ActionTypes from './constants.js'
import uid from '../../helpers/uid.js'
import api from '../../helpers/api.js'
import * as selectors from './selectors.js'
import { push } from 'connected-react-router'
import shortid from 'shortid'
import reformatter from '../../helpers/reformatter'
import preview from '../../helpers/preview'
import resolveUrl from '../../helpers/resolveUrl.js'
import { engines, recipes, entitySets, previewListeners, previewConfigurationHandler, locationResolver, editorComponents, concurrentUpdateModal, modalHandler } from '../../lib/configuration.js'
export function closeTab (id) {
return (dispatch, getState) => {
const entity = entities.selectors.getById(getState(), id, false)
if (entity) {
const dependantEntityTabs = getState().editor.tabs.filter((t) => {
return (
t.type === 'entity' &&
t._id === id &&
// this check includes tabs like header-footer/pdf-utils
t.key !== t._id
)
})
// close also dependants tabs (like header-footer, pdf-utils, etc)
// if the entity is new of if it is dirty
dependantEntityTabs.forEach((t) => {
if (entity.__isNew || entity.__isDirty) {
dispatch({
type: ActionTypes.CLOSE_TAB,
key: t.key
})
}
})
}
dispatch({
type: ActionTypes.CLOSE_TAB,
key: id
})
if (entity) {
dispatch(entities.actions.unload(id))
}
}
}
export function openTab (tab) {
return async function (dispatch, getState) {
if (tab.shortid && !tab._id) {
try {
tab._id = entities.selectors.getByShortid(getState(), tab.shortid)._id
} catch (e) {
dispatch(push(resolveUrl('/')))
return
}
}
if (tab._id) {
await entities.actions.load(tab._id)(dispatch, getState)
tab.entitySet = entities.selectors.getById(getState(), tab._id).__entitySet
}
tab.type = tab._id ? 'entity' : 'custom'
tab.key = tab.key || tab._id
dispatch({
type: ActionTypes.OPEN_TAB,
tab: tab
})
dispatch(activateTab(tab.key))
}
}
export function openNewTab ({ entitySet, entity, name }) {
const shouldClone = entity != null && entity._id != null
return async function (dispatch, getState) {
let id = uid()
let newEntity
let clonedEntity
if (shouldClone) {
await entities.actions.load(entity._id)(dispatch, getState)
clonedEntity = entities.selectors.getById(getState(), entity._id)
newEntity = {
...clonedEntity,
_id: id,
__entitySet: entitySet,
shortid: shortid.generate(),
[entitySets[entitySet].nameAttribute]: name
}
} else {
if (entity != null) {
newEntity = Object.assign({}, entity)
}
newEntity = Object.assign(newEntity, {
_id: id,
__entitySet: entitySet,
shortid: shortid.generate(),
[entitySets[entitySet].nameAttribute]: name
})
if (entitySet === 'templates') {
newEntity.recipe = recipes.includes('chrome-pdf') ? 'chrome-pdf' : 'html'
newEntity.engine = engines.includes('handlebars') ? 'handlebars' : engines[0]
}
}
dispatch(entities.actions.add(newEntity))
dispatch({
type: ActionTypes.OPEN_NEW_TAB,
tab: {
_id: id,
key: id,
entitySet: entitySet,
type: 'entity'
}
})
}
}
export function activateTab (id) {
return (dispatch, getState) => {
dispatch({
type: ActionTypes.ACTIVATE_TAB,
key: id
})
}
}
export function updateHistory () {
return (dispatch, getState) => {
const entity = selectors.getActiveEntity(getState())
let path
if (entity && entity.shortid) {
path = resolveUrl(`/studio/${entity.__entitySet}/${entity.shortid}`)
} else {
path = resolveUrl('/')
}
if (locationResolver) {
path = locationResolver(path, entity)
}
if (path !== getState().router.location.pathname) {
dispatch(push(path))
}
}
}
export function update (entity) {
return async function (dispatch, getState) {
await entities.actions.update(entity)(dispatch, getState)
}
}
export function groupedUpdate (entity) {
return async function (dispatch, getState) {
await entities.actions.groupedUpdate(entity)(dispatch, getState)
}
}
export function hierarchyMove (source, target, shouldCopy = false, replace = false, retry = true) {
return async function (dispatch, getState) {
let response
let sourceEntity = entities.selectors.getById(getState(), source.id)
if (sourceEntity.__isNew || sourceEntity.__isDirty) {
dispatch(entities.actions.flushUpdates())
sourceEntity = entities.selectors.getById(getState(), source.id)
dispatch(entities.actions.update(Object.assign({}, sourceEntity, {
folder: target.shortid != null ? { shortid: target.shortid } : null
})))
} else {
try {
dispatch(entities.actions.apiStart())
response = await api.post('/studio/hierarchyMove', {
data: {
source: {
entitySet: source.entitySet,
id: source.id,
onlyChildren: source.onlyChildren
},
target: {
shortid: target.shortid,
updateReferences: target.updateReferences
},
copy: shouldCopy === true,
replace: replace === true
}
})
if (replace === true) {
if (Array.isArray(target.children)) {
const sourceEntity = entities.selectors.getById(getState(), source.id, false)
const sourceEntitySetNameAttr = entitySets[sourceEntity.__entitySet].nameAttribute
let childTargetId
let childTargetChildren = []
const allFoldersInsideTarget = target.children.reduce((acu, childId) => {
const childEntity = entities.selectors.getById(getState(), childId, false)
const childEntitySetNameAttr = entitySets[childEntity.__entitySet].nameAttribute
if (
((target.shortid == null && childEntity.folder == null) ||
(target.shortid != null && childEntity.folder.shortid === target.shortid)) &&
childEntity[childEntitySetNameAttr] === sourceEntity[sourceEntitySetNameAttr]
) {
childTargetId = childEntity._id
}
if (childEntity.__entitySet === 'folders') {
acu.push(childEntity.shortid)
}
return acu
}, [])
target.children.forEach((childId) => {
const childEntity = entities.selectors.getById(getState(), childId, false)
if (childEntity.folder && allFoldersInsideTarget.indexOf(childEntity.folder.shortid) !== -1) {
childTargetChildren.push(childEntity._id)
}
})
if (childTargetId) {
dispatch(entities.actions.removeExisting(childTargetId, childTargetChildren))
}
}
}
response.items.forEach((item) => {
dispatch(entities.actions.addExisting(item))
})
dispatch(entities.actions.apiDone())
} catch (e) {
if (retry && e.code === 'DUPLICATED_ENTITY' && e.existingEntityEntitySet !== 'folders') {
dispatch(entities.actions.apiDone())
return {
duplicatedEntity: true,
existingEntity: e.existingEntity,
existingEntityEntitySet: e.existingEntityEntitySet
}
}
dispatch(entities.actions.apiFailed(e))
}
if (target.updateReferences) {
// refresh target
const targetEntity = entities.selectors.getByShortid(getState(), target.shortid)
await entities.actions.load(targetEntity._id, true)(dispatch, getState)
}
return response.items
}
}
}
export function save () {
return async function (dispatch, getState) {
let entityId
try {
entityId = selectors.getActiveTab(getState())._id
dispatch({
type: ActionTypes.SAVE_STARTED
})
await entities.actions.save(entityId, { ignoreFailed: true })(dispatch, getState)
dispatch({
type: ActionTypes.SAVE_SUCCESS
})
} catch (e) {
console.error(e)
if (e.error && e.error.code === 'CONCURRENT_UPDATE_INVALID') {
dispatch(progress.actions.stop())
modalHandler.open(concurrentUpdateModal, {
entityId: entityId,
modificationDate: e.error.modificationDate
})
} else {
dispatch(entities.actions.apiFailed(e))
}
}
}
}
export function saveAll () {
return async function (dispatch, getState) {
try {
dispatch({
type: ActionTypes.SAVE_STARTED
})
const entitiesToUpdate = getState().editor.tabs.filter((t) => {
return (
t.type === 'entity' &&
// this check excludes tabs like header-footer/pdf-utils
t.key === t._id
)
})
await Promise.all(entitiesToUpdate.map((t) => {
const entity = entities.selectors.getById(getState(), t._id)
// only save for new or entities that have changed
if (entity.__isNew || entity.__isDirty) {
return entities.actions.save(t._id, { ignoreFailed: true })(dispatch, getState)
}
}))
dispatch({
type: ActionTypes.SAVE_SUCCESS
})
} catch (e) {
if (e.error && e.error.code === 'CONCURRENT_UPDATE_INVALID') {
dispatch(progress.actions.stop())
modalHandler.open(concurrentUpdateModal, {
entityId: e.entityId
})
} else {
dispatch(entities.actions.apiFailed(e))
}
}
}
}
export function reformat (shouldThrow = false) {
return async function (dispatch, getState) {
// this flushed the updates
dispatch(entities.actions.flushUpdates())
const tab = selectors.getActiveTab(getState())
const editorReformat = editorComponents[tab.editorComponentKey || tab.entitySet].reformat
if (!editorReformat && !shouldThrow) {
return false
}
const activeEntity = selectors.getActiveEntity(getState())
const toUpdate = editorReformat(reformatter, activeEntity, tab)
dispatch(update(Object.assign({ _id: activeEntity._id }, toUpdate)))
return true
}
}
export function remove () {
return async function (dispatch, getState) {
const tab = selectors.getActiveTab(getState())
await dispatch(entities.actions.remove(tab._id))
}
}
export function run (target) {
return async function (dispatch, getState) {
let template = Object.assign({}, selectors.getLastActiveTemplate(getState()))
let request = { template: template, options: {} }
const entities = Object.assign({}, getState().entities)
const previewListenersReturns = await Promise.all([...previewListeners.map((l) => l(request, entities, target))])
const previewConfig = previewListenersReturns.reduce((acu, value) => {
if (value != null) {
acu = Object.assign(acu, value)
}
return acu
}, {})
dispatch({ type: ActionTypes.RUN })
await previewConfigurationHandler({ ...previewConfig, src: null })
preview(request, target || 'previewFrame')
}
}
export function activateUndockMode () {
return {
type: ActionTypes.ACTIVATE_UNDOCK_MODE
}
}
export function desactivateUndockMode () {
return {
type: ActionTypes.DESACTIVATE_UNDOCK_MODE
}
}