1
0
mirror of https://gitlab.com/bramw/baserow.git synced 2024-11-21 23:37:55 +00:00
bramw_baserow/web-frontend/modules/builder/store/elementContent.js

313 lines
10 KiB
JavaScript

import DataSourceService from '@baserow/modules/builder/services/dataSource'
import PublishedBuilderService from '@baserow/modules/builder/services/publishedBuilder'
import { rangeDiff } from '@baserow/modules/core/utils/range'
const state = {}
const mutations = {
SET_CONTENT(state, { element, value, range = null }) {
// Return early when value is null since there is nothing to set.
if (value === null) {
return
}
// If we have no range, then the `value` is the full content for `element`,
// we'll apply it and return early. This will happen if we are setting the
// content of a collection element's `schema_property`.
if (range === null) {
element._.content = value
return
}
const [offset] = range
const missingIndexes = offset + value.length - element._.content.length
let newContent
if (missingIndexes > 0) {
newContent = element._.content.concat(Array(missingIndexes).fill(null))
} else {
newContent = [...element._.content]
}
value.forEach((record, index) => {
newContent[offset + index] = record
})
element._.content = newContent
},
SET_HAS_MORE_PAGE(state, { element, value }) {
element._.hasNextPage = value
},
CLEAR_CONTENT(state, { element }) {
element._.content = []
element._.hasNextPage = false
},
TRIGGER_RESET(state, { element }) {
element._.reset += 1
},
SET_LOADING(state, { element, value }) {
element._.contentLoading = value
},
}
const actions = {
/**
* Fetch the data from the server and add them to the element store.
* @param {object} page - the page object
* @param {object} element - the element object
* @param {object} dataSource - the data source we want to dispatch
* @param {object} range - the range of the data we want to fetch
* @param {object} filters - the adhoc filters to apply to the data
* @param {object} sortings - the adhoc sortings to apply to the data
* @param {object} search - the adhoc search to apply to the data
* @param {string} searchMode - the search mode to apply to the data.
* @param {string} mode - the mode of the application
* @param {object} dispatchContext - the context to dispatch to the data
* @param {bool} replace - if we want to replace the current content
* @param {object} data - the query body
*/
async fetchElementContent(
{ commit, getters },
{
page,
element,
dataSource,
range,
filters = {},
sortings = null,
search = '',
searchMode = '',
mode,
data: dispatchContext,
replace = false,
}
) {
/**
* If `dataSource` is `null`, this means that we are trying to fetch the content
* of a nested collection element, such as a repeat nested in a repeat.
*
* The nested collection fetches its content by finding, either the root-level
* collection element with a dataSource, or its immediate parent with a schema property.
*
* If we have a parent with a schema property: this nested collection element
* is a child of a collection element using a schema property as well, e.g.:
*
* - Root collection element (with a dataSource):
* - Parent collection element (with a schema property)
* - Grandchild collection element (this `element`!) with a schema property.
*
* If we don't have a parent element with a schema property, we are a child of
* the root collection element with a dataSource, e.g.:
*
* - Root collection element (with a dataSource):
* - Parent collection element (this `element`!) with a schema property.
*/
if (dataSource === null) {
if (!element.schema_property) {
// We have a collection element that supports schema properties, and
// we have A) no data source and B) no schema property
// or,
// We have a collection element that doesn't support schema properties
// (record selector), and there's no data source.
commit('SET_LOADING', { element, value: false })
return
}
commit('SET_LOADING', { element, value: true })
// Collect all collection element ancestors, with a `data_source_id`.
const collectionAncestors = this.app.store.getters[
'element/getAncestors'
](page, element, {
predicate: (ancestor) =>
this.app.$registry.get('element', ancestor.type)
.isCollectionElement && ancestor.data_source_id !== null,
})
// Pluck out the root ancestor, which has a data source.
const rootAncestorWithDataSource = collectionAncestors[0]
// Next, find this element's parent.
const parent = this.app.store.getters['element/getParent'](page, element)
// If the parent has a `schema_property`, we'll want to use the
// parent's element content for `element` to use. If the parent
// doesn't have a property, we'll access to the root ancestor's
// (which has a data source) for the content.
const targetElement = parent.schema_property
? parent
: rootAncestorWithDataSource
const targetContent =
this.app.store.getters['elementContent/getElementContent'](
targetElement
)
let elementContent = []
if (parent.schema_property) {
// If the parent has a `schema_property`, it's an array of values
// *inside* `schema_property`, so we just copy the array.
elementContent = [...targetContent]
} else {
// Build a new array of content, for this `element`, which
// will only contain the property `schema_property`.
elementContent = targetContent.map((obj) => ({
[element.schema_property]: obj[element.schema_property],
}))
}
commit('CLEAR_CONTENT', {
element,
})
commit('SET_CONTENT', {
element,
value: elementContent,
})
commit('SET_LOADING', { element, value: false })
return
}
const serviceType = this.app.$registry.get('service', dataSource.type)
// We have a data source, but if it doesn't return a list,
// it needs to have a `schema_property` to work correctly.
if (!serviceType.returnsList && element.schema_property === null) {
// If we previously had a list data source, we might have content,
// so rather than leave the content *until a schema property is set*,
// clear it.
commit('CLEAR_CONTENT', {
element,
})
commit('SET_LOADING', { element, value: false })
return
}
commit('SET_LOADING', { element, value: true })
try {
if (serviceType.isValid(dataSource)) {
let rangeToFetch = range
if (!replace) {
// Let's compute the range that really needs to be fetched if necessary
const [offset, count] = range
rangeToFetch = rangeDiff(getters.getContentRange(element), [
offset,
offset + count,
])
// Everything is already loaded we can quit now
if (!rangeToFetch) {
commit('SET_LOADING', { element, value: false })
return
}
rangeToFetch = [rangeToFetch[0], rangeToFetch[1] - rangeToFetch[0]]
}
let service = DataSourceService
if (['preview', 'public'].includes(mode)) {
service = PublishedBuilderService
}
const { data } = await service(this.app.$client).dispatch(
dataSource.id,
dispatchContext,
{ range: rangeToFetch, filters, sortings, search, searchMode }
)
// With a list-type data source, the data object will return
// a `has_next_page` field for paging to the next set of results.
const { has_next_page: hasNextPage = false } = data
if (replace) {
commit('CLEAR_CONTENT', {
element,
})
}
if (serviceType.returnsList) {
// The service type returns a list of results, we'll set the content
// using the results key and set the range for future paging.
commit('SET_CONTENT', {
element,
value: data.results,
range,
})
} else {
// The service type returns a single row of results, we'll set the
// content using the element's schema property. Not how there's no
// range for paging, all results are set at once.
commit('SET_CONTENT', {
element,
value: data[element.schema_property],
})
}
commit('SET_HAS_MORE_PAGE', {
element,
value: hasNextPage,
})
} else {
commit('CLEAR_CONTENT', {
element,
})
}
} catch (e) {
// If fetching the content failed, and we're trying to
// replace the element's content, then we'll clear the
// element instead of reverting to our previousContent
// as it could be out of date anyway.
if (replace) {
commit('CLEAR_CONTENT', { element })
}
throw e
} finally {
commit('SET_LOADING', { element, value: false })
}
},
clearElementContent({ commit }, { element }) {
commit('CLEAR_CONTENT', { element })
},
triggerElementContentReset({ commit }, { element }) {
commit('TRIGGER_RESET', { element })
},
}
const getters = {
getElementContent:
(state) =>
(element, applicationContext = {}) => {
// If we have a recordIndexPath to work with, and the element has
// its content loaded, then we're fetching content for a nested
// collection+container element, which has a schema property. We'll
// return the content at a specific index path, and from that property.
const { recordIndexPath = [] } = applicationContext
if (recordIndexPath.length && element._.content.length) {
const contentAtIndex = element._.content[recordIndexPath[0]]
return contentAtIndex?.[element.schema_property] || []
}
return element._.content
},
getHasMorePage: (state) => (element) => {
return element._.hasNextPage
},
getLoading: (state) => (element) => {
return element._.contentLoading
},
getReset: (state) => (element) => {
return element._.reset
},
getContentRange: (state) => (element) => {
return [0, element._.content.length]
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}