1
0
Fork 0
mirror of https://gitlab.com/bramw/baserow.git synced 2025-04-03 04:35:31 +00:00
bramw_baserow/web-frontend/modules/builder/store/element.js
2024-03-05 08:16:41 +00:00

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,
}