mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-03 04:35:31 +00:00
466 lines
13 KiB
JavaScript
466 lines
13 KiB
JavaScript
import ElementService from '@baserow/modules/builder/services/element'
|
|
import PublicBuilderService from '@baserow/modules/builder/services/publishedBuilder'
|
|
import { calculateTempOrder } from '@baserow/modules/core/utils/order'
|
|
import BigNumber from 'bignumber.js'
|
|
|
|
const populateElement = (element) => {
|
|
element._ = {
|
|
contentLoading: false,
|
|
content: [],
|
|
hasNextPage: false,
|
|
reset: 0,
|
|
shouldBeFocused: false,
|
|
}
|
|
|
|
return element
|
|
}
|
|
|
|
const state = {
|
|
// The currently selected element
|
|
selected: null,
|
|
}
|
|
|
|
const updateContext = {
|
|
updateTimeout: null,
|
|
promiseResolve: null,
|
|
lastUpdatedValues: null,
|
|
valuesToUpdate: {},
|
|
}
|
|
|
|
const mutations = {
|
|
SET_ITEMS(state, { page, elements }) {
|
|
state.selected = null
|
|
page.elements = elements.map(populateElement)
|
|
},
|
|
ADD_ITEM(state, { page, element, beforeId = null }) {
|
|
page.elements.push(populateElement(element))
|
|
},
|
|
UPDATE_ITEM(state, { page, element: elementToUpdate, values }) {
|
|
page.elements.forEach((element) => {
|
|
if (element.id === elementToUpdate.id) {
|
|
Object.assign(element, values)
|
|
}
|
|
})
|
|
if (state.selected?.id === elementToUpdate.id) {
|
|
Object.assign(state.selected, values)
|
|
}
|
|
},
|
|
DELETE_ITEM(state, { page, elementId }) {
|
|
const index = page.elements.findIndex((element) => element.id === elementId)
|
|
if (index > -1) {
|
|
page.elements.splice(index, 1)
|
|
}
|
|
},
|
|
MOVE_ITEM(state, { page, index, oldIndex }) {
|
|
page.elements.splice(index, 0, page.elements.splice(oldIndex, 1)[0])
|
|
},
|
|
SELECT_ITEM(state, { element }) {
|
|
state.selected = element
|
|
},
|
|
CLEAR_ITEMS(state, { page }) {
|
|
page.elements = []
|
|
},
|
|
}
|
|
|
|
const actions = {
|
|
clearAll({ commit }, { page }) {
|
|
commit('CLEAR_ITEMS', { page })
|
|
},
|
|
forceCreate({ commit }, { page, element }) {
|
|
commit('ADD_ITEM', { page, element })
|
|
|
|
const elementType = this.$registry.get('element', element.type)
|
|
elementType.afterCreate(element, page)
|
|
},
|
|
forceUpdate({ commit }, { page, element, values }) {
|
|
commit('UPDATE_ITEM', { page, element, values })
|
|
const elementType = this.$registry.get('element', element.type)
|
|
elementType.afterUpdate(element, page)
|
|
},
|
|
forceDelete({ commit, getters }, { page, elementId }) {
|
|
const elementsOfPage = getters.getElements(page)
|
|
const elementIndex = elementsOfPage.findIndex(
|
|
(element) => element.id === elementId
|
|
)
|
|
const elementToDelete = elementsOfPage[elementIndex]
|
|
|
|
if (getters.getSelected?.id === elementId) {
|
|
commit('SELECT_ITEM', { element: null })
|
|
}
|
|
commit('DELETE_ITEM', { page, elementId })
|
|
|
|
const elementType = this.$registry.get('element', elementToDelete.type)
|
|
elementType.afterDelete(elementToDelete, page)
|
|
},
|
|
forceMove(
|
|
{ commit, getters },
|
|
{ page, elementId, beforeElementId, parentElementId, placeInContainer }
|
|
) {
|
|
const beforeElement = getters.getElementById(page, beforeElementId)
|
|
const afterOrder = beforeElement?.order || null
|
|
const beforeOrder =
|
|
getters.getPreviousElement(
|
|
page,
|
|
beforeElement,
|
|
parentElementId,
|
|
placeInContainer
|
|
)?.order || null
|
|
const tempOrder = calculateTempOrder(beforeOrder, afterOrder)
|
|
|
|
commit('UPDATE_ITEM', {
|
|
page,
|
|
element: getters.getElementById(page, elementId),
|
|
values: {
|
|
order: tempOrder,
|
|
parent_element_id: parentElementId,
|
|
place_in_container: placeInContainer,
|
|
},
|
|
})
|
|
},
|
|
select({ commit }, { element }) {
|
|
updateContext.lastUpdatedValues = null
|
|
commit('SELECT_ITEM', { element })
|
|
},
|
|
async create(
|
|
{ dispatch },
|
|
{
|
|
page,
|
|
elementType: elementTypeName,
|
|
beforeId = null,
|
|
configuration = null,
|
|
forceCreate = true,
|
|
}
|
|
) {
|
|
const elementType = this.$registry.get('element', elementTypeName)
|
|
const { data: element } = await ElementService(this.$client).create(
|
|
page.id,
|
|
elementTypeName,
|
|
beforeId,
|
|
elementType.getDefaultValues(page, configuration)
|
|
)
|
|
|
|
if (forceCreate) {
|
|
await dispatch('forceCreate', { page, element })
|
|
await dispatch('select', { element })
|
|
}
|
|
|
|
return element
|
|
},
|
|
async update({ dispatch }, { page, element, values }) {
|
|
const oldValues = {}
|
|
const newValues = {}
|
|
Object.keys(values).forEach((name) => {
|
|
if (Object.prototype.hasOwnProperty.call(element, name)) {
|
|
oldValues[name] = element[name]
|
|
newValues[name] = values[name]
|
|
}
|
|
})
|
|
|
|
await dispatch('forceUpdate', { page, element, values: newValues })
|
|
|
|
try {
|
|
await ElementService(this.$client).update(element.id, values)
|
|
} catch (error) {
|
|
await dispatch('forceUpdate', { page, element, values: oldValues })
|
|
throw error
|
|
}
|
|
},
|
|
|
|
async debouncedUpdateSelected({ dispatch, getters }, { page, values }) {
|
|
const element = getters.getSelected
|
|
|
|
const oldValues = {}
|
|
Object.keys(values).forEach((name) => {
|
|
if (Object.prototype.hasOwnProperty.call(element, name)) {
|
|
oldValues[name] = element[name]
|
|
// Accumulate the changed values to send all the ongoing changes with the
|
|
// final request
|
|
updateContext.valuesToUpdate[name] = values[name]
|
|
}
|
|
})
|
|
|
|
await dispatch('forceUpdate', {
|
|
page,
|
|
element,
|
|
values: updateContext.valuesToUpdate,
|
|
})
|
|
|
|
return new Promise((resolve, reject) => {
|
|
const fire = async () => {
|
|
const toUpdate = updateContext.valuesToUpdate
|
|
updateContext.valuesToUpdate = {}
|
|
try {
|
|
await ElementService(this.$client).update(element.id, toUpdate)
|
|
updateContext.lastUpdatedValues = null
|
|
resolve()
|
|
} catch (error) {
|
|
// Revert to old values on error
|
|
await dispatch('forceUpdate', {
|
|
page,
|
|
element,
|
|
values: updateContext.lastUpdatedValues,
|
|
})
|
|
updateContext.lastUpdatedValues = null
|
|
reject(error)
|
|
}
|
|
}
|
|
|
|
if (updateContext.promiseResolve) {
|
|
updateContext.promiseResolve()
|
|
updateContext.promiseResolve = null
|
|
}
|
|
|
|
clearTimeout(updateContext.updateTimeout)
|
|
|
|
if (!updateContext.lastUpdatedValues) {
|
|
updateContext.lastUpdatedValues = oldValues
|
|
}
|
|
|
|
updateContext.updateTimeout = setTimeout(fire, 500)
|
|
updateContext.promiseResolve = resolve
|
|
})
|
|
},
|
|
async delete({ dispatch, getters }, { page, elementId }) {
|
|
const elementsOfPage = getters.getElements(page)
|
|
const elementIndex = elementsOfPage.findIndex(
|
|
(element) => element.id === elementId
|
|
)
|
|
const elementToDelete = elementsOfPage[elementIndex]
|
|
|
|
const descendants = getters.getDescendants(page, elementToDelete)
|
|
|
|
// First delete all children
|
|
await Promise.all(
|
|
descendants.map((descendant) =>
|
|
dispatch('forceDelete', { page, elementId: descendant.id })
|
|
)
|
|
)
|
|
|
|
await dispatch('forceDelete', { page, elementId })
|
|
|
|
try {
|
|
await ElementService(this.$client).delete(elementId)
|
|
} catch (error) {
|
|
await dispatch('forceCreate', {
|
|
page,
|
|
element: elementToDelete,
|
|
})
|
|
// Then restore all children
|
|
await Promise.all(
|
|
descendants.map((descendant) =>
|
|
dispatch('forceCreate', { page, element: descendant })
|
|
)
|
|
)
|
|
throw error
|
|
}
|
|
},
|
|
async fetch({ commit }, { page }) {
|
|
const { data: elements } = await ElementService(this.$client).fetchAll(
|
|
page.id
|
|
)
|
|
|
|
commit('SET_ITEMS', { page, elements })
|
|
|
|
return elements
|
|
},
|
|
async fetchPublished({ commit }, { page }) {
|
|
const { data: elements } = await PublicBuilderService(
|
|
this.$client
|
|
).fetchElements(page)
|
|
|
|
commit('SET_ITEMS', { page, elements })
|
|
|
|
return elements
|
|
},
|
|
async move(
|
|
{ commit, dispatch, getters },
|
|
{
|
|
page,
|
|
elementId,
|
|
beforeElementId,
|
|
parentElementId = null,
|
|
placeInContainer = null,
|
|
}
|
|
) {
|
|
const element = getters.getElementById(page, elementId)
|
|
|
|
await dispatch('forceMove', {
|
|
page,
|
|
elementId,
|
|
beforeElementId,
|
|
parentElementId,
|
|
placeInContainer,
|
|
})
|
|
|
|
try {
|
|
const { data: elementUpdated } = await ElementService(this.$client).move(
|
|
elementId,
|
|
beforeElementId,
|
|
parentElementId,
|
|
placeInContainer
|
|
)
|
|
|
|
dispatch('forceUpdate', {
|
|
page,
|
|
element: elementUpdated,
|
|
values: { ...elementUpdated },
|
|
})
|
|
} catch (error) {
|
|
await dispatch('forceUpdate', {
|
|
page,
|
|
element,
|
|
values: element,
|
|
})
|
|
throw error
|
|
}
|
|
},
|
|
async duplicate({ commit, dispatch, getters }, { page, elementId }) {
|
|
const {
|
|
data: { elements, workflow_actions: workflowActions },
|
|
} = await ElementService(this.$client).duplicate(elementId)
|
|
|
|
const elementPromises = elements.map((element) =>
|
|
dispatch('forceCreate', { page, element })
|
|
)
|
|
const workflowActionPromises = workflowActions.map((workflowAction) =>
|
|
dispatch(
|
|
'workflowAction/forceCreate',
|
|
{ page, workflowAction },
|
|
{ root: true }
|
|
)
|
|
)
|
|
|
|
await Promise.all(elementPromises.concat(workflowActionPromises))
|
|
|
|
const elementToDuplicate = getters.getElementById(page, elementId)
|
|
const elementToSelect = elements.find(
|
|
({ parent_element_id: parentId }) =>
|
|
parentId === elementToDuplicate.parent_element_id
|
|
)
|
|
|
|
commit('SELECT_ITEM', { element: elementToSelect })
|
|
|
|
return elements
|
|
},
|
|
emitElementEvent({ getters }, { event, page, ...rest }) {
|
|
const elements = getters.getElements(page)
|
|
|
|
elements.forEach((element) => {
|
|
const elementType = this.$registry.get('element', element.type)
|
|
elementType.onElementEvent(event, { page, element, ...rest })
|
|
})
|
|
},
|
|
}
|
|
|
|
/** Recursively order the elements from up to down and left to right */
|
|
const orderElements = (elements, parentElementId = null) => {
|
|
return elements
|
|
.filter(
|
|
({ parent_element_id: curentParentElementId }) =>
|
|
curentParentElementId === parentElementId
|
|
)
|
|
.sort((a, b) => {
|
|
if (a.place_in_container !== b.place_in_container) {
|
|
return a.place_in_container > b.place_in_container ? 1 : -1
|
|
}
|
|
|
|
return a.order.gt(b.order) ? 1 : -1
|
|
})
|
|
.map((element) => [element, ...orderElements(elements, element.id)])
|
|
.flat()
|
|
}
|
|
|
|
const getters = {
|
|
getElements: (state) => (page) => {
|
|
return page.elements.map((element) => ({
|
|
...element,
|
|
order: new BigNumber(element.order),
|
|
}))
|
|
},
|
|
getElementById: (state, getters) => (page, id) => {
|
|
return getters.getElements(page).find((e) => e.id === id)
|
|
},
|
|
getElementsOrdered: (state, getters) => (page) => {
|
|
return orderElements(getters.getElements(page))
|
|
},
|
|
getRootElements: (state, getters) => (page) => {
|
|
return getters
|
|
.getElementsOrdered(page)
|
|
.filter((e) => e.parent_element_id === null)
|
|
},
|
|
getChildren: (state, getters) => (page, element) => {
|
|
return getters
|
|
.getElementsOrdered(page)
|
|
.filter((e) => e.parent_element_id === element.id)
|
|
},
|
|
getDescendants: (state, getters) => (page, element) => {
|
|
return getters
|
|
.getChildren(page, element)
|
|
.map((child) => [...getters.getChildren(page, child), child])
|
|
.flat()
|
|
},
|
|
getAncestors: (state, getters) => (page, element) => {
|
|
const getElementAncestors = (element) => {
|
|
if (element.parent_element_id === null) {
|
|
return []
|
|
} else {
|
|
const parentElement = getters.getElementById(
|
|
page,
|
|
element.parent_element_id
|
|
)
|
|
return [...getElementAncestors(parentElement), parentElement]
|
|
}
|
|
}
|
|
return getElementAncestors(element)
|
|
},
|
|
getSiblings: (state, getters) => (page, element) => {
|
|
return getters
|
|
.getElementsOrdered(page)
|
|
.filter((e) => e.parent_element_id === element.parent_element_id)
|
|
},
|
|
getElementPosition:
|
|
(state, getters) =>
|
|
(page, element, sameType = false) => {
|
|
const elements = getters.getElementsOrdered(page)
|
|
|
|
return (
|
|
(sameType
|
|
? elements.filter(({ type }) => type === element.type)
|
|
: elements
|
|
).findIndex(({ id }) => id === element.id) + 1
|
|
)
|
|
},
|
|
getElementsInPlace:
|
|
(state, getters) => (page, parentId, placeInContainer) => {
|
|
return getters
|
|
.getElementsOrdered(page)
|
|
.filter(
|
|
(e) =>
|
|
e.parent_element_id === parentId &&
|
|
e.place_in_container === placeInContainer
|
|
)
|
|
},
|
|
getPreviousElement:
|
|
(state, getters) => (page, before, parentId, placeInContainer) => {
|
|
const elementsInPlace = getters.getElementsInPlace(
|
|
page,
|
|
parentId,
|
|
placeInContainer
|
|
)
|
|
return before
|
|
? elementsInPlace.reverse().find((e) => e.order.lt(before.order)) ||
|
|
null
|
|
: elementsInPlace.at(-1)
|
|
},
|
|
getSelected(state) {
|
|
return state.selected
|
|
},
|
|
}
|
|
|
|
export default {
|
|
namespaced: true,
|
|
state,
|
|
getters,
|
|
actions,
|
|
mutations,
|
|
}
|