1
0
Fork 0
mirror of synced 2024-09-08 05:31:47 +12:00

Optimise sheet data loading and add sort button

This commit is contained in:
Andrew Kingston 2023-03-10 16:23:56 +00:00
parent a78ba19cf8
commit 9231ce88c6
6 changed files with 137 additions and 64 deletions

View file

@ -86,10 +86,6 @@
// Expose ability to retrieve context externally to allow sheet control
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
onMount(() => createWebsocket(context))
</script>
@ -112,7 +108,7 @@
<UserAvatars />
</div>
</div>
{#if initialised}
{#if $loaded}
<div class="sheet-data">
<StickyColumn />
<div class="sheet-main">

View file

@ -1,9 +1,10 @@
<script>
import { getContext } from "svelte"
import { ActionButton } from "@budibase/bbui"
import SortButton from "./controls/SortButton.svelte"
const { rows } = getContext("sheet")
</script>
<ActionButton icon="SortOrderDown" quiet size="M">Sort</ActionButton>
<SortButton />
<ActionButton icon="VisibilityOff" quiet size="M">Hide fields</ActionButton>

View file

@ -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>

View file

@ -1,5 +1,4 @@
import { writable, derived, get } from "svelte/store"
import { LuceneUtils } from "../../../index"
import { fetchData } from "../../../fetch/fetchData"
import { notifications } from "@budibase/bbui"
@ -11,20 +10,25 @@ export const createRowsStore = context => {
const table = writable(null)
const filter = writable([])
const loaded = writable(false)
const fetch = writable(null)
const sort = writable({
column: null,
order: null,
})
// Enrich rows with an index property
const enrichedRows = derived(rows, $rows => {
return $rows.map((row, idx) => ({
...row,
__idx: idx,
}))
})
// Generate a lookup map to quick find a row by ID
const rowLookupMap = derived(enrichedRows, $rows => {
let map = {}
for (let i = 0; i < $rows.length; i++) {
map[$rows[i]._id] = i
for (let row of $rows) {
map[row._id] = row.__idx
}
return map
})
@ -33,69 +37,84 @@ export const createRowsStore = context => {
let rowCacheMap = {}
// Reset everything when table ID changes
tableId.subscribe(() => {
filter.set([])
let unsubscribe = null
tableId.subscribe($tableId => {
// Unsub from previous fetch if one exists
unsubscribe?.()
fetch.set(null)
// Reset state
sort.set({
column: null,
order: null,
})
})
filter.set([])
// Local stores for managing fetching data
const query = derived(filter, $filter =>
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({
// Create new fetch model
const newFetch = fetchData({
API,
datasource: {
type: "table",
tableId: $tableId,
},
options: {
sortColumn: $sort.column,
sortOrder: $sort.order,
query: $query,
filter: [],
sortColumn: null,
sortOrder: null,
limit: 100,
paginate: true,
},
})
})
// Observe each data fetch and extract some data
fetch.subscribe($fetch => {
if (!$fetch) {
return
}
$fetch.subscribe($$fetch => {
if ($$fetch.loaded) {
if (!get(loaded)) {
// Subscribe to changes of this fetch model
unsubscribe = newFetch.subscribe($fetch => {
if ($fetch.loaded && !$fetch.loading) {
if ($fetch.pageNumber === 0) {
// Hydrate initial data
loaded.set(true)
rowCacheMap = {}
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
let newSchema = $$fetch.schema
const primaryDisplay = $$fetch.definition?.primaryDisplay
let newSchema = $fetch.schema
const primaryDisplay = $fetch.definition?.primaryDisplay
if (primaryDisplay && newSchema[primaryDisplay]) {
newSchema[primaryDisplay].primaryDisplay = true
}
schema.set(newSchema)
table.set($$fetch.definition)
table.set($fetch.definition)
// 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

View file

@ -21,11 +21,11 @@ export default class DataFetch {
this.API = null
// Feature flags
this.featureStore = writable({
this.features = {
supportsSearch: false,
supportsSort: false,
supportsPagination: false,
})
}
// Config
this.options = {
@ -81,17 +81,14 @@ export default class DataFetch {
this.prevPage = this.prevPage.bind(this)
// Derive certain properties to return
this.derivedStore = derived(
[this.store, this.featureStore],
([$store, $featureStore]) => {
return {
...$store,
...$featureStore,
hasNextPage: this.hasNextPage($store),
hasPrevPage: this.hasPrevPage($store),
}
this.derivedStore = derived(this.store, $store => {
return {
...$store,
...this.features,
hasNextPage: this.hasNextPage($store),
hasPrevPage: this.hasPrevPage($store),
}
)
})
// Mark as loaded if we have no datasource
if (!this.options.datasource) {
@ -120,11 +117,11 @@ export default class DataFetch {
// Fetch datasource definition and determine feature flags
const definition = await this.getDefinition(datasource)
const features = this.determineFeatureFlags(definition)
this.featureStore.set({
this.features = {
supportsSearch: !!features?.supportsSearch,
supportsSort: !!features?.supportsSort,
supportsPagination: paginate && !!features?.supportsPagination,
})
}
// Fetch and enrich schema
let schema = this.getSchema(datasource, definition)
@ -138,11 +135,17 @@ export default class DataFetch {
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) {
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
let sortType = "string"
@ -167,6 +170,8 @@ export default class DataFetch {
loading: true,
cursors: [],
cursor: null,
sortOrder,
sortColumn,
}))
// Actually fetch data
@ -189,23 +194,23 @@ export default class DataFetch {
async getPage() {
const { sortColumn, sortOrder, sortType, limit } = this.options
const { query } = get(this.store)
const features = get(this.featureStore)
// Get the actual data
console.log("===== FETCH =====")
let { rows, info, hasNextPage, cursor, error } = await this.getData()
// If we don't support searching, do a client search
if (!features.supportsSearch) {
if (!this.features.supportsSearch) {
rows = runLuceneQuery(rows, query)
}
// If we don't support sorting, do a client-side sort
if (!features.supportsSort) {
if (!this.features.supportsSort) {
rows = luceneSort(rows, sortColumn, sortOrder, sortType)
}
// If we don't support pagination, do a client-side limit
if (!features.supportsPagination) {
if (!this.features.supportsPagination) {
rows = luceneLimit(rows, limit)
}

View file

@ -31,7 +31,7 @@ export default class QueryFetch extends DataFetch {
async getData() {
const { datasource, limit, paginate } = this.options
const { supportsPagination } = get(this.featureStore)
const { supportsPagination } = this.features
const { cursor, definition } = get(this.store)
const type = definition?.fields?.pagination?.type