budibase/packages/client/src/components/app/DataProvider.svelte

221 lines
5.3 KiB
Svelte

<script lang="ts">
import { getContext } from "svelte"
import { Pagination, ProgressCircle } from "@budibase/bbui"
import {
fetchData,
QueryUtils,
DataFetchOptions,
ProviderDatasource,
} from "@budibase/frontend-core"
import {
LogicalOperator,
EmptyFilterOption,
TableSchema,
SortOrder,
SearchFilters,
} from "@budibase/types"
import { SDK, Component } from "../../index"
export let dataSource: ProviderDatasource
export let filter
export let sortColumn
export let sortOrder
export let limit
export let paginate
export let autoRefresh
const { styleable, Provider, ActionTypes, API } = getContext<SDK>("sdk")
const component = getContext<Component>("component")
let interval: NodeJS.Timeout
let queryExtensions: Record<string, any> = {}
$: defaultQuery = QueryUtils.buildQuery(filter)
// We need to manage our lucene query manually as we want to allow components
// to extend it
$: query = extendQuery(defaultQuery, queryExtensions)
$: fetch = createFetch(dataSource)
$: fetch.update({
query,
sortColumn,
sortOrder,
limit,
paginate,
})
$: schema = sanitizeSchema($fetch.schema)
$: setUpAutoRefresh(autoRefresh)
$: actions = [
{
type: ActionTypes.RefreshDatasource,
callback: () => fetch.refresh(),
metadata: { dataSource },
},
{
type: ActionTypes.AddDataProviderQueryExtension,
callback: addQueryExtension,
},
{
type: ActionTypes.RemoveDataProviderQueryExtension,
callback: removeQueryExtension,
},
{
type: ActionTypes.SetDataProviderSorting,
callback: ({
column,
order,
}: {
column: string
order: SortOrder | undefined
}) => {
let newOptions: Partial<DataFetchOptions<never>> = {}
if (column) {
newOptions.sortColumn = column
}
if (order) {
newOptions.sortOrder = order
}
if (Object.keys(newOptions)?.length) {
fetch.update(newOptions)
}
},
},
]
$: dataContext = {
rows: $fetch.rows,
info: $fetch.info,
datasource: dataSource || {},
schema,
rowsLength: $fetch.rows.length,
pageNumber: $fetch.pageNumber + 1,
// Undocumented properties. These aren't supposed to be used in builder
// bindings, but are used internally by other components
id: $component?.id,
state: {
query: $fetch.query,
},
limit,
primaryDisplay: ($fetch.definition as any)?.primaryDisplay,
}
const createFetch = (datasource: ProviderDatasource) => {
return fetchData({
API,
datasource,
options: {
query,
sortColumn,
sortOrder,
limit,
paginate,
},
})
}
const sanitizeSchema = (schema: TableSchema | null) => {
if (!schema) {
return schema
}
let cloned = { ...schema }
Object.entries(cloned).forEach(([field, fieldSchema]) => {
if (fieldSchema.visible === false) {
delete cloned[field]
}
})
return cloned
}
const addQueryExtension = (key: string, extension: any) => {
if (!key || !extension) {
return
}
queryExtensions = { ...queryExtensions, [key]: extension }
}
const removeQueryExtension = (key: string) => {
if (!key) {
return
}
const newQueryExtensions = { ...queryExtensions }
delete newQueryExtensions[key]
queryExtensions = newQueryExtensions
}
const extendQuery = (
defaultQuery: SearchFilters,
extensions: Record<string, any>
): SearchFilters => {
if (!Object.keys(extensions).length) {
return defaultQuery
}
const extended: SearchFilters = {
[LogicalOperator.AND]: {
conditions: [
...(defaultQuery ? [defaultQuery] : []),
...Object.values(extensions || {}),
],
},
onEmptyFilter: EmptyFilterOption.RETURN_NONE,
}
// If there are no conditions applied at all, clear the request.
return (extended[LogicalOperator.AND]?.conditions?.length ?? 0) > 0
? extended
: {}
}
const setUpAutoRefresh = (autoRefresh: number) => {
clearInterval(interval)
if (autoRefresh) {
interval = setInterval(fetch.refresh, Math.max(10000, autoRefresh * 1000))
}
}
</script>
<div use:styleable={$component.styles} class="container">
<Provider {actions} data={dataContext}>
{#if !$fetch.loaded}
<div class="loading">
<ProgressCircle />
</div>
{:else}
<slot />
{#if paginate && $fetch.supportsPagination}
<div class="pagination">
<Pagination
page={$fetch.pageNumber + 1}
hasPrevPage={$fetch.hasPrevPage}
hasNextPage={$fetch.hasNextPage}
goToPrevPage={fetch.prevPage}
goToNextPage={fetch.nextPage}
/>
</div>
{/if}
{/if}
</Provider>
</div>
<style>
.container {
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
}
.loading {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
height: 100px;
}
.pagination {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
margin-top: var(--spacing-xl);
}
</style>