2021-03-16 14:54:34 +01:00
|
|
|
<script>
|
|
|
|
import { getContext } from "svelte"
|
|
|
|
|
|
|
|
export let dataSource
|
|
|
|
export let filter
|
|
|
|
export let sortColumn
|
|
|
|
export let sortOrder
|
2021-04-30 17:29:53 +02:00
|
|
|
export let limit = 50
|
2021-03-16 14:54:34 +01:00
|
|
|
|
|
|
|
const { API, styleable, Provider, ActionTypes } = getContext("sdk")
|
|
|
|
const component = getContext("component")
|
|
|
|
|
|
|
|
// Loading flag every time data is being fetched
|
|
|
|
let loading = false
|
|
|
|
|
|
|
|
// Loading flag for the initial load
|
|
|
|
let loaded = false
|
|
|
|
|
2021-04-30 17:29:53 +02:00
|
|
|
// Provider state
|
|
|
|
let rows = []
|
2021-05-11 12:24:16 +02:00
|
|
|
let allRows = []
|
2021-03-18 16:53:25 +01:00
|
|
|
let schema = {}
|
2021-04-30 17:29:53 +02:00
|
|
|
let bookmarks = [null]
|
|
|
|
let pageNumber = 0
|
2021-03-18 16:53:25 +01:00
|
|
|
|
2021-05-11 12:24:16 +02:00
|
|
|
$: internalTable = dataSource?.type === "table"
|
2021-04-30 17:29:53 +02:00
|
|
|
$: query = dataSource?.type === "table" ? buildLuceneQuery(filter) : null
|
|
|
|
$: hasNextPage = bookmarks[pageNumber + 1] != null
|
|
|
|
$: hasPrevPage = pageNumber > 0
|
2021-03-18 16:53:25 +01:00
|
|
|
$: getSchema(dataSource)
|
2021-05-11 12:24:16 +02:00
|
|
|
$: sortType = getSortType(schema, sortColumn)
|
|
|
|
$: fetchData(dataSource, query, limit, sortColumn, sortOrder, sortType)
|
|
|
|
$: {
|
|
|
|
if (internalTable) {
|
|
|
|
rows = allRows
|
|
|
|
} else {
|
|
|
|
rows = sortRows(allRows, sortColumn, sortOrder)
|
|
|
|
rows = limitRows(rows, limit)
|
|
|
|
}
|
|
|
|
}
|
2021-03-16 14:54:34 +01:00
|
|
|
$: actions = [
|
|
|
|
{
|
|
|
|
type: ActionTypes.RefreshDatasource,
|
|
|
|
callback: () => fetchData(dataSource),
|
|
|
|
metadata: { dataSource },
|
|
|
|
},
|
2021-04-30 17:29:53 +02:00
|
|
|
{
|
|
|
|
type: ActionTypes.NextPage,
|
|
|
|
callback: () => nextPage(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
type: ActionTypes.PrevPage,
|
|
|
|
callback: () => prevPage(),
|
|
|
|
},
|
2021-03-16 14:54:34 +01:00
|
|
|
]
|
|
|
|
$: dataContext = {
|
|
|
|
rows,
|
2021-03-18 16:53:25 +01:00
|
|
|
schema,
|
2021-03-16 14:54:34 +01:00
|
|
|
rowsLength: rows.length,
|
|
|
|
loading,
|
|
|
|
loaded,
|
2021-04-30 17:29:53 +02:00
|
|
|
pageNumber: pageNumber + 1,
|
|
|
|
hasNextPage,
|
|
|
|
hasPrevPage,
|
2021-03-16 14:54:34 +01:00
|
|
|
}
|
|
|
|
|
2021-05-11 12:24:16 +02:00
|
|
|
const getSortType = (schema, sortColumn) => {
|
|
|
|
if (!schema || !sortColumn || !schema[sortColumn]) {
|
|
|
|
return "string"
|
|
|
|
}
|
|
|
|
const type = schema?.[sortColumn]?.type
|
|
|
|
return type === "number" ? "number" : "string"
|
|
|
|
}
|
|
|
|
|
|
|
|
const buildLuceneQuery = filter => {
|
2021-04-30 17:29:53 +02:00
|
|
|
let query = {
|
|
|
|
string: {},
|
|
|
|
fuzzy: {},
|
|
|
|
range: {},
|
|
|
|
equal: {},
|
|
|
|
notEqual: {},
|
|
|
|
empty: {},
|
|
|
|
notEmpty: {},
|
|
|
|
}
|
|
|
|
if (Array.isArray(filter)) {
|
2021-05-11 12:24:16 +02:00
|
|
|
filter.forEach(expression => {
|
2021-04-30 17:29:53 +02:00
|
|
|
if (expression.operator.startsWith("range")) {
|
|
|
|
let range = {
|
|
|
|
low: Number.MIN_SAFE_INTEGER,
|
|
|
|
high: Number.MAX_SAFE_INTEGER,
|
|
|
|
}
|
|
|
|
if (expression.operator === "rangeLow") {
|
|
|
|
range.low = expression.value
|
|
|
|
} else if (expression.operator === "rangeHigh") {
|
|
|
|
range.high = expression.value
|
|
|
|
}
|
|
|
|
query.range[expression.field] = range
|
|
|
|
} else if (query[expression.operator]) {
|
|
|
|
query[expression.operator][expression.field] = expression.value
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return query
|
|
|
|
}
|
|
|
|
|
2021-05-11 12:24:16 +02:00
|
|
|
const fetchData = async (
|
|
|
|
dataSource,
|
|
|
|
query,
|
|
|
|
limit,
|
|
|
|
sortColumn,
|
|
|
|
sortOrder,
|
|
|
|
sortType
|
|
|
|
) => {
|
2021-03-16 14:54:34 +01:00
|
|
|
loading = true
|
2021-04-30 17:29:53 +02:00
|
|
|
if (dataSource?.type === "table") {
|
|
|
|
const res = await API.searchTable({
|
|
|
|
tableId: dataSource.tableId,
|
|
|
|
query,
|
|
|
|
limit,
|
|
|
|
sort: sortColumn,
|
|
|
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
2021-05-11 12:24:16 +02:00
|
|
|
sortType,
|
2021-04-30 17:29:53 +02:00
|
|
|
})
|
|
|
|
pageNumber = 0
|
2021-05-11 12:24:16 +02:00
|
|
|
allRows = res.rows
|
2021-04-30 17:29:53 +02:00
|
|
|
|
|
|
|
// Check we have next data
|
|
|
|
const next = await API.searchTable({
|
|
|
|
tableId: dataSource.tableId,
|
|
|
|
query,
|
|
|
|
limit: 1,
|
|
|
|
bookmark: res.bookmark,
|
|
|
|
sort: sortColumn,
|
|
|
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
2021-05-11 12:24:16 +02:00
|
|
|
sortType,
|
2021-04-30 17:29:53 +02:00
|
|
|
})
|
|
|
|
if (next.rows?.length) {
|
|
|
|
bookmarks = [null, res.bookmark]
|
|
|
|
} else {
|
|
|
|
bookmarks = [null]
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const rows = await API.fetchDatasource(dataSource)
|
2021-05-11 12:24:16 +02:00
|
|
|
allRows = inMemoryFilterRows(rows, filter)
|
2021-04-30 17:29:53 +02:00
|
|
|
}
|
2021-03-16 14:54:34 +01:00
|
|
|
loading = false
|
2021-03-16 20:11:00 +01:00
|
|
|
loaded = true
|
2021-03-16 14:54:34 +01:00
|
|
|
}
|
|
|
|
|
2021-04-30 17:29:53 +02:00
|
|
|
const inMemoryFilterRows = (rows, filter) => {
|
2021-03-16 14:54:34 +01:00
|
|
|
let filteredData = [...rows]
|
2021-05-11 12:24:16 +02:00
|
|
|
Object.entries(filter || {}).forEach(([field, value]) => {
|
2021-03-16 14:54:34 +01:00
|
|
|
if (value != null && value !== "") {
|
2021-05-11 12:24:16 +02:00
|
|
|
filteredData = filteredData.filter(row => {
|
2021-03-16 14:54:34 +01:00
|
|
|
return row[field] === value
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
return filteredData
|
|
|
|
}
|
|
|
|
|
|
|
|
const sortRows = (rows, sortColumn, sortOrder) => {
|
|
|
|
if (!sortColumn || !sortOrder) {
|
|
|
|
return rows
|
|
|
|
}
|
|
|
|
return rows.slice().sort((a, b) => {
|
|
|
|
const colA = a[sortColumn]
|
|
|
|
const colB = b[sortColumn]
|
2021-03-16 20:11:00 +01:00
|
|
|
if (sortOrder === "Descending") {
|
2021-03-16 14:54:34 +01:00
|
|
|
return colA > colB ? -1 : 1
|
|
|
|
} else {
|
|
|
|
return colA > colB ? 1 : -1
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const limitRows = (rows, limit) => {
|
|
|
|
const numLimit = parseFloat(limit)
|
|
|
|
if (isNaN(numLimit)) {
|
|
|
|
return rows
|
|
|
|
}
|
|
|
|
return rows.slice(0, numLimit)
|
|
|
|
}
|
2021-03-18 16:53:25 +01:00
|
|
|
|
2021-05-11 12:24:16 +02:00
|
|
|
const getSchema = async dataSource => {
|
2021-03-18 16:53:25 +01:00
|
|
|
if (dataSource?.schema) {
|
|
|
|
schema = dataSource.schema
|
|
|
|
} else if (dataSource?.tableId) {
|
|
|
|
const definition = await API.fetchTableDefinition(dataSource.tableId)
|
|
|
|
schema = definition?.schema ?? {}
|
|
|
|
} else {
|
|
|
|
schema = {}
|
|
|
|
}
|
2021-03-25 09:14:53 +01:00
|
|
|
|
2021-05-04 12:04:42 +02:00
|
|
|
// Ensure there are "name" properties for all fields and that field schema
|
|
|
|
// are objects
|
|
|
|
let fixedSchema = {}
|
|
|
|
Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => {
|
|
|
|
if (typeof fieldSchema === "string") {
|
|
|
|
fixedSchema[fieldName] = {
|
|
|
|
type: fieldSchema,
|
|
|
|
name: fieldName,
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
fixedSchema[fieldName] = {
|
|
|
|
...fieldSchema,
|
|
|
|
name: fieldName,
|
|
|
|
}
|
2021-03-25 09:14:53 +01:00
|
|
|
}
|
|
|
|
})
|
2021-05-04 12:04:42 +02:00
|
|
|
schema = fixedSchema
|
2021-03-18 16:53:25 +01:00
|
|
|
}
|
2021-04-30 17:29:53 +02:00
|
|
|
|
|
|
|
const nextPage = async () => {
|
|
|
|
if (!hasNextPage) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const res = await API.searchTable({
|
|
|
|
tableId: dataSource?.tableId,
|
|
|
|
query,
|
|
|
|
bookmark: bookmarks[pageNumber + 1],
|
|
|
|
limit,
|
|
|
|
sort: sortColumn,
|
|
|
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
2021-05-11 12:24:16 +02:00
|
|
|
sortType,
|
2021-04-30 17:29:53 +02:00
|
|
|
})
|
|
|
|
pageNumber++
|
2021-05-11 12:24:16 +02:00
|
|
|
allRows = res.rows
|
2021-04-30 17:29:53 +02:00
|
|
|
|
|
|
|
// Check we have next data
|
|
|
|
const next = await API.searchTable({
|
|
|
|
tableId: dataSource.tableId,
|
|
|
|
query,
|
|
|
|
limit: 1,
|
|
|
|
bookmark: res.bookmark,
|
|
|
|
sort: sortColumn,
|
|
|
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
2021-05-11 12:24:16 +02:00
|
|
|
sortType,
|
2021-04-30 17:29:53 +02:00
|
|
|
})
|
|
|
|
if (next.rows?.length) {
|
|
|
|
bookmarks[pageNumber + 1] = res.bookmark
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const prevPage = async () => {
|
|
|
|
if (!hasPrevPage) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const res = await API.searchTable({
|
|
|
|
tableId: dataSource?.tableId,
|
|
|
|
query,
|
|
|
|
bookmark: bookmarks[pageNumber - 1],
|
|
|
|
limit,
|
|
|
|
sort: sortColumn,
|
|
|
|
sortOrder: sortOrder?.toLowerCase() ?? "ascending",
|
2021-05-11 12:24:16 +02:00
|
|
|
sortType,
|
2021-04-30 17:29:53 +02:00
|
|
|
})
|
|
|
|
pageNumber--
|
2021-05-11 12:24:16 +02:00
|
|
|
allRows = res.rows
|
2021-04-30 17:29:53 +02:00
|
|
|
}
|
2021-03-16 14:54:34 +01:00
|
|
|
</script>
|
|
|
|
|
2021-04-29 10:41:49 +02:00
|
|
|
<div use:styleable={$component.styles}>
|
|
|
|
<Provider {actions} data={dataContext}>
|
|
|
|
<slot />
|
|
|
|
</Provider>
|
|
|
|
</div>
|