Optimise sheet data loading and add sort button
This commit is contained in:
parent
a78ba19cf8
commit
9231ce88c6
|
@ -86,10 +86,6 @@
|
||||||
// Expose ability to retrieve context externally to allow sheet control
|
// Expose ability to retrieve context externally to allow sheet control
|
||||||
export const getContext = () => context
|
export const getContext = () => context
|
||||||
|
|
||||||
// Local flag for if the sheet has ever had data
|
|
||||||
let initialised = false
|
|
||||||
loaded.subscribe(state => (initialised = initialised || state))
|
|
||||||
|
|
||||||
// Initialise websocket for multi-user
|
// Initialise websocket for multi-user
|
||||||
onMount(() => createWebsocket(context))
|
onMount(() => createWebsocket(context))
|
||||||
</script>
|
</script>
|
||||||
|
@ -112,7 +108,7 @@
|
||||||
<UserAvatars />
|
<UserAvatars />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#if initialised}
|
{#if $loaded}
|
||||||
<div class="sheet-data">
|
<div class="sheet-data">
|
||||||
<StickyColumn />
|
<StickyColumn />
|
||||||
<div class="sheet-main">
|
<div class="sheet-main">
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
<script>
|
<script>
|
||||||
import { getContext } from "svelte"
|
import { getContext } from "svelte"
|
||||||
import { ActionButton } from "@budibase/bbui"
|
import { ActionButton } from "@budibase/bbui"
|
||||||
|
import SortButton from "./controls/SortButton.svelte"
|
||||||
|
|
||||||
const { rows } = getContext("sheet")
|
const { rows } = getContext("sheet")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ActionButton icon="SortOrderDown" quiet size="M">Sort</ActionButton>
|
<SortButton />
|
||||||
<ActionButton icon="VisibilityOff" quiet size="M">Hide fields</ActionButton>
|
<ActionButton icon="VisibilityOff" quiet size="M">Hide fields</ActionButton>
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { ActionButton, Popover, Select } from "@budibase/bbui"
|
||||||
|
|
||||||
|
const { sort, columns, stickyColumn } = getContext("sheet")
|
||||||
|
const orderOptions = [
|
||||||
|
{ label: "Ascending", value: "ascending" },
|
||||||
|
{ label: "Descending", value: "descending" },
|
||||||
|
]
|
||||||
|
|
||||||
|
let open = false
|
||||||
|
let anchor
|
||||||
|
|
||||||
|
$: columnOptions = getColumnOptions($stickyColumn, $columns)
|
||||||
|
$: console.log($sort)
|
||||||
|
|
||||||
|
const getColumnOptions = (stickyColumn, columns) => {
|
||||||
|
let options = []
|
||||||
|
if (stickyColumn) {
|
||||||
|
options.push(stickyColumn.name)
|
||||||
|
}
|
||||||
|
return [...options, ...columns.map(col => col.name)]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={anchor}>
|
||||||
|
<ActionButton
|
||||||
|
icon="SortOrderDown"
|
||||||
|
quiet
|
||||||
|
size="M"
|
||||||
|
on:click={() => (open = !open)}
|
||||||
|
selected={!!$sort.order}
|
||||||
|
>
|
||||||
|
Sort
|
||||||
|
</ActionButton>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Popover bind:open {anchor} align="left">
|
||||||
|
<div class="content">
|
||||||
|
<Select bind:value={$sort.column} options={columnOptions} autoWidth />
|
||||||
|
<Select bind:value={$sort.order} options={orderOptions} autoWidth />
|
||||||
|
</div>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.content {
|
||||||
|
padding: 12px 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -1,5 +1,4 @@
|
||||||
import { writable, derived, get } from "svelte/store"
|
import { writable, derived, get } from "svelte/store"
|
||||||
import { LuceneUtils } from "../../../index"
|
|
||||||
import { fetchData } from "../../../fetch/fetchData"
|
import { fetchData } from "../../../fetch/fetchData"
|
||||||
import { notifications } from "@budibase/bbui"
|
import { notifications } from "@budibase/bbui"
|
||||||
|
|
||||||
|
@ -11,20 +10,25 @@ export const createRowsStore = context => {
|
||||||
const table = writable(null)
|
const table = writable(null)
|
||||||
const filter = writable([])
|
const filter = writable([])
|
||||||
const loaded = writable(false)
|
const loaded = writable(false)
|
||||||
|
const fetch = writable(null)
|
||||||
const sort = writable({
|
const sort = writable({
|
||||||
column: null,
|
column: null,
|
||||||
order: null,
|
order: null,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Enrich rows with an index property
|
||||||
const enrichedRows = derived(rows, $rows => {
|
const enrichedRows = derived(rows, $rows => {
|
||||||
return $rows.map((row, idx) => ({
|
return $rows.map((row, idx) => ({
|
||||||
...row,
|
...row,
|
||||||
__idx: idx,
|
__idx: idx,
|
||||||
}))
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Generate a lookup map to quick find a row by ID
|
||||||
const rowLookupMap = derived(enrichedRows, $rows => {
|
const rowLookupMap = derived(enrichedRows, $rows => {
|
||||||
let map = {}
|
let map = {}
|
||||||
for (let i = 0; i < $rows.length; i++) {
|
for (let row of $rows) {
|
||||||
map[$rows[i]._id] = i
|
map[row._id] = row.__idx
|
||||||
}
|
}
|
||||||
return map
|
return map
|
||||||
})
|
})
|
||||||
|
@ -33,69 +37,84 @@ export const createRowsStore = context => {
|
||||||
let rowCacheMap = {}
|
let rowCacheMap = {}
|
||||||
|
|
||||||
// Reset everything when table ID changes
|
// Reset everything when table ID changes
|
||||||
tableId.subscribe(() => {
|
let unsubscribe = null
|
||||||
filter.set([])
|
tableId.subscribe($tableId => {
|
||||||
|
// Unsub from previous fetch if one exists
|
||||||
|
unsubscribe?.()
|
||||||
|
fetch.set(null)
|
||||||
|
|
||||||
|
// Reset state
|
||||||
sort.set({
|
sort.set({
|
||||||
column: null,
|
column: null,
|
||||||
order: null,
|
order: null,
|
||||||
})
|
})
|
||||||
})
|
filter.set([])
|
||||||
|
|
||||||
// Local stores for managing fetching data
|
// Create new fetch model
|
||||||
const query = derived(filter, $filter =>
|
const newFetch = fetchData({
|
||||||
LuceneUtils.buildLuceneQuery($filter)
|
|
||||||
)
|
|
||||||
const fetch = derived([tableId, query, sort], ([$tableId, $query, $sort]) => {
|
|
||||||
if (!$tableId) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
// Wipe state and fully hydrate next time our fetch returns data
|
|
||||||
loaded.set(false)
|
|
||||||
|
|
||||||
// Create fetch and load initial data
|
|
||||||
return fetchData({
|
|
||||||
API,
|
API,
|
||||||
datasource: {
|
datasource: {
|
||||||
type: "table",
|
type: "table",
|
||||||
tableId: $tableId,
|
tableId: $tableId,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
sortColumn: $sort.column,
|
filter: [],
|
||||||
sortOrder: $sort.order,
|
sortColumn: null,
|
||||||
query: $query,
|
sortOrder: null,
|
||||||
limit: 100,
|
limit: 100,
|
||||||
paginate: true,
|
paginate: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
// Observe each data fetch and extract some data
|
// Subscribe to changes of this fetch model
|
||||||
fetch.subscribe($fetch => {
|
unsubscribe = newFetch.subscribe($fetch => {
|
||||||
if (!$fetch) {
|
if ($fetch.loaded && !$fetch.loading) {
|
||||||
return
|
if ($fetch.pageNumber === 0) {
|
||||||
}
|
|
||||||
$fetch.subscribe($$fetch => {
|
|
||||||
if ($$fetch.loaded) {
|
|
||||||
if (!get(loaded)) {
|
|
||||||
// Hydrate initial data
|
// Hydrate initial data
|
||||||
loaded.set(true)
|
|
||||||
rowCacheMap = {}
|
rowCacheMap = {}
|
||||||
rows.set([])
|
rows.set([])
|
||||||
|
|
||||||
|
// Update sorting from fetch if required
|
||||||
|
const $sort = get(sort)
|
||||||
|
if (!$sort.column) {
|
||||||
|
sort.set({
|
||||||
|
column: $fetch.sortColumn,
|
||||||
|
order: $fetch.sortOrder,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update schema and enrich primary display into schema
|
// Update schema and enrich primary display into schema
|
||||||
let newSchema = $$fetch.schema
|
let newSchema = $fetch.schema
|
||||||
const primaryDisplay = $$fetch.definition?.primaryDisplay
|
const primaryDisplay = $fetch.definition?.primaryDisplay
|
||||||
if (primaryDisplay && newSchema[primaryDisplay]) {
|
if (primaryDisplay && newSchema[primaryDisplay]) {
|
||||||
newSchema[primaryDisplay].primaryDisplay = true
|
newSchema[primaryDisplay].primaryDisplay = true
|
||||||
}
|
}
|
||||||
schema.set(newSchema)
|
schema.set(newSchema)
|
||||||
table.set($$fetch.definition)
|
table.set($fetch.definition)
|
||||||
|
|
||||||
// Process new rows
|
// Process new rows
|
||||||
handleNewRows($$fetch.rows)
|
handleNewRows($fetch.rows)
|
||||||
|
|
||||||
|
// Notify that we're loaded
|
||||||
|
loaded.set(true)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
fetch.set(newFetch)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update fetch when filter or sort config changes
|
||||||
|
filter.subscribe($filter => {
|
||||||
|
get(fetch)?.update({
|
||||||
|
filter: $filter,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
sort.subscribe($sort => {
|
||||||
|
get(fetch)?.update({
|
||||||
|
sortOrder: $sort.order,
|
||||||
|
sortColumn: $sort.column,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// Adds a new empty row
|
// Adds a new empty row
|
||||||
|
|
|
@ -21,11 +21,11 @@ export default class DataFetch {
|
||||||
this.API = null
|
this.API = null
|
||||||
|
|
||||||
// Feature flags
|
// Feature flags
|
||||||
this.featureStore = writable({
|
this.features = {
|
||||||
supportsSearch: false,
|
supportsSearch: false,
|
||||||
supportsSort: false,
|
supportsSort: false,
|
||||||
supportsPagination: false,
|
supportsPagination: false,
|
||||||
})
|
}
|
||||||
|
|
||||||
// Config
|
// Config
|
||||||
this.options = {
|
this.options = {
|
||||||
|
@ -81,17 +81,14 @@ export default class DataFetch {
|
||||||
this.prevPage = this.prevPage.bind(this)
|
this.prevPage = this.prevPage.bind(this)
|
||||||
|
|
||||||
// Derive certain properties to return
|
// Derive certain properties to return
|
||||||
this.derivedStore = derived(
|
this.derivedStore = derived(this.store, $store => {
|
||||||
[this.store, this.featureStore],
|
return {
|
||||||
([$store, $featureStore]) => {
|
...$store,
|
||||||
return {
|
...this.features,
|
||||||
...$store,
|
hasNextPage: this.hasNextPage($store),
|
||||||
...$featureStore,
|
hasPrevPage: this.hasPrevPage($store),
|
||||||
hasNextPage: this.hasNextPage($store),
|
|
||||||
hasPrevPage: this.hasPrevPage($store),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
// Mark as loaded if we have no datasource
|
// Mark as loaded if we have no datasource
|
||||||
if (!this.options.datasource) {
|
if (!this.options.datasource) {
|
||||||
|
@ -120,11 +117,11 @@ export default class DataFetch {
|
||||||
// Fetch datasource definition and determine feature flags
|
// Fetch datasource definition and determine feature flags
|
||||||
const definition = await this.getDefinition(datasource)
|
const definition = await this.getDefinition(datasource)
|
||||||
const features = this.determineFeatureFlags(definition)
|
const features = this.determineFeatureFlags(definition)
|
||||||
this.featureStore.set({
|
this.features = {
|
||||||
supportsSearch: !!features?.supportsSearch,
|
supportsSearch: !!features?.supportsSearch,
|
||||||
supportsSort: !!features?.supportsSort,
|
supportsSort: !!features?.supportsSort,
|
||||||
supportsPagination: paginate && !!features?.supportsPagination,
|
supportsPagination: paginate && !!features?.supportsPagination,
|
||||||
})
|
}
|
||||||
|
|
||||||
// Fetch and enrich schema
|
// Fetch and enrich schema
|
||||||
let schema = this.getSchema(datasource, definition)
|
let schema = this.getSchema(datasource, definition)
|
||||||
|
@ -138,11 +135,17 @@ export default class DataFetch {
|
||||||
this.options.sortOrder = "ascending"
|
this.options.sortOrder = "ascending"
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no sort column, use the first field in the schema
|
// If no sort column, use the primary display and fallback to first column
|
||||||
if (!this.options.sortColumn) {
|
if (!this.options.sortColumn) {
|
||||||
this.options.sortColumn = Object.keys(schema)[0]
|
let newSortColumn
|
||||||
|
if (definition?.primaryDisplay && schema[definition.primaryDisplay]) {
|
||||||
|
newSortColumn = definition.primaryDisplay
|
||||||
|
} else {
|
||||||
|
newSortColumn = Object.keys(schema)[0]
|
||||||
|
}
|
||||||
|
this.options.sortColumn = newSortColumn
|
||||||
}
|
}
|
||||||
const { sortColumn } = this.options
|
const { sortOrder, sortColumn } = this.options
|
||||||
|
|
||||||
// Determine what sort type to use
|
// Determine what sort type to use
|
||||||
let sortType = "string"
|
let sortType = "string"
|
||||||
|
@ -167,6 +170,8 @@ export default class DataFetch {
|
||||||
loading: true,
|
loading: true,
|
||||||
cursors: [],
|
cursors: [],
|
||||||
cursor: null,
|
cursor: null,
|
||||||
|
sortOrder,
|
||||||
|
sortColumn,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Actually fetch data
|
// Actually fetch data
|
||||||
|
@ -189,23 +194,23 @@ export default class DataFetch {
|
||||||
async getPage() {
|
async getPage() {
|
||||||
const { sortColumn, sortOrder, sortType, limit } = this.options
|
const { sortColumn, sortOrder, sortType, limit } = this.options
|
||||||
const { query } = get(this.store)
|
const { query } = get(this.store)
|
||||||
const features = get(this.featureStore)
|
|
||||||
|
|
||||||
// Get the actual data
|
// Get the actual data
|
||||||
|
console.log("===== FETCH =====")
|
||||||
let { rows, info, hasNextPage, cursor, error } = await this.getData()
|
let { rows, info, hasNextPage, cursor, error } = await this.getData()
|
||||||
|
|
||||||
// If we don't support searching, do a client search
|
// If we don't support searching, do a client search
|
||||||
if (!features.supportsSearch) {
|
if (!this.features.supportsSearch) {
|
||||||
rows = runLuceneQuery(rows, query)
|
rows = runLuceneQuery(rows, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't support sorting, do a client-side sort
|
// If we don't support sorting, do a client-side sort
|
||||||
if (!features.supportsSort) {
|
if (!this.features.supportsSort) {
|
||||||
rows = luceneSort(rows, sortColumn, sortOrder, sortType)
|
rows = luceneSort(rows, sortColumn, sortOrder, sortType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we don't support pagination, do a client-side limit
|
// If we don't support pagination, do a client-side limit
|
||||||
if (!features.supportsPagination) {
|
if (!this.features.supportsPagination) {
|
||||||
rows = luceneLimit(rows, limit)
|
rows = luceneLimit(rows, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ export default class QueryFetch extends DataFetch {
|
||||||
|
|
||||||
async getData() {
|
async getData() {
|
||||||
const { datasource, limit, paginate } = this.options
|
const { datasource, limit, paginate } = this.options
|
||||||
const { supportsPagination } = get(this.featureStore)
|
const { supportsPagination } = this.features
|
||||||
const { cursor, definition } = get(this.store)
|
const { cursor, definition } = get(this.store)
|
||||||
const type = definition?.fields?.pagination?.type
|
const type = definition?.fields?.pagination?.type
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue