Initial row conversion
This commit is contained in:
parent
f92eb73e11
commit
0c53610231
|
@ -10,8 +10,8 @@ interface DatasourceBaseActions<
|
||||||
TSaveDefinitionRequest = UpdateViewRequest | SaveTableRequest
|
TSaveDefinitionRequest = UpdateViewRequest | SaveTableRequest
|
||||||
> {
|
> {
|
||||||
saveDefinition: (newDefinition: TSaveDefinitionRequest) => Promise<void>
|
saveDefinition: (newDefinition: TSaveDefinitionRequest) => Promise<void>
|
||||||
addRow: (row: SaveRowRequest) => Promise<UIRow | void>
|
addRow: (row: SaveRowRequest) => Promise<UIRow | undefined>
|
||||||
updateRow: (row: SaveRowRequest) => Promise<UIRow | void>
|
updateRow: (row: SaveRowRequest) => Promise<UIRow | undefined>
|
||||||
deleteRows: (rows: UIRow[]) => Promise<void>
|
deleteRows: (rows: UIRow[]) => Promise<void>
|
||||||
getRow: (id: string) => Promise<UIRow | void>
|
getRow: (id: string) => Promise<UIRow | void>
|
||||||
isDatasourceValid: (datasource: UIDatasource) => boolean | void
|
isDatasourceValid: (datasource: UIDatasource) => boolean | void
|
||||||
|
|
|
@ -69,12 +69,10 @@ export type Store = BaseStore &
|
||||||
Clipboard.Store &
|
Clipboard.Store &
|
||||||
Scroll.Store & {
|
Scroll.Store & {
|
||||||
// TODO while typing the rest of stores
|
// TODO while typing the rest of stores
|
||||||
fetch: Writable<any>
|
|
||||||
sort: Writable<any>
|
sort: Writable<any>
|
||||||
initialFilter: Writable<any>
|
initialFilter: Writable<any>
|
||||||
initialSortColumn: Writable<any>
|
initialSortColumn: Writable<any>
|
||||||
initialSortOrder: Writable<any>
|
initialSortOrder: Writable<any>
|
||||||
rows: Writable<any> & { actions: any }
|
|
||||||
subscribe: any
|
subscribe: any
|
||||||
config: Writable<any>
|
config: Writable<any>
|
||||||
dispatch: (event: string, data: any) => any
|
dispatch: (event: string, data: any) => any
|
||||||
|
@ -82,13 +80,11 @@ export type Store = BaseStore &
|
||||||
schemaOverrides: Writable<any>
|
schemaOverrides: Writable<any>
|
||||||
gridID: string
|
gridID: string
|
||||||
props: Writable<any>
|
props: Writable<any>
|
||||||
rowLookupMap: Writable<any>
|
|
||||||
width: Writable<number>
|
width: Writable<number>
|
||||||
fixedRowHeight: Writable<number>
|
fixedRowHeight: Writable<number>
|
||||||
rowChangeCache: Readable<any>
|
|
||||||
bounds: Readable<any>
|
bounds: Readable<any>
|
||||||
height: Readable<number>
|
height: Readable<number>
|
||||||
}
|
} & Rows.Store
|
||||||
|
|
||||||
export const attachStores = (context: Store): Store => {
|
export const attachStores = (context: Store): Store => {
|
||||||
// Atomic store creation
|
// Atomic store creation
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { writable, derived, get } from "svelte/store"
|
import { writable, derived, get, Writable, Readable } from "svelte/store"
|
||||||
import { fetchData } from "../../../fetch"
|
import { fetchData } from "../../../fetch"
|
||||||
import { NewRowID, RowPageSize } from "../lib/constants"
|
import { NewRowID, RowPageSize } from "../lib/constants"
|
||||||
import {
|
import {
|
||||||
|
@ -10,10 +10,49 @@ import {
|
||||||
import { tick } from "svelte"
|
import { tick } from "svelte"
|
||||||
import { Helpers } from "@budibase/bbui"
|
import { Helpers } from "@budibase/bbui"
|
||||||
import { sleep } from "../../../utils/utils"
|
import { sleep } from "../../../utils/utils"
|
||||||
import { FieldType } from "@budibase/types"
|
import { FieldType, Row, UIRow } from "@budibase/types"
|
||||||
import { getRelatedTableValues } from "../../../utils"
|
import { getRelatedTableValues } from "../../../utils"
|
||||||
|
import { Store as StoreContext } from "."
|
||||||
|
|
||||||
export const createStores = () => {
|
interface RowStore {
|
||||||
|
rows: Writable<any[]>
|
||||||
|
fetch: Writable<any>
|
||||||
|
loaded: Writable<any>
|
||||||
|
refreshing: Writable<any>
|
||||||
|
loading: Writable<any>
|
||||||
|
rowChangeCache: Writable<any>
|
||||||
|
inProgressChanges: Writable<any>
|
||||||
|
hasNextPage: Writable<any>
|
||||||
|
error: Writable<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowDerivedStore {
|
||||||
|
rows: RowStore["rows"]
|
||||||
|
rowLookupMap: Readable<any>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RowActionStore {
|
||||||
|
rows: RowStore["rows"] & {
|
||||||
|
actions: {
|
||||||
|
addRow: any
|
||||||
|
duplicateRow: any
|
||||||
|
bulkDuplicate: any
|
||||||
|
updateValue: any
|
||||||
|
applyRowChanges: any
|
||||||
|
deleteRows: any
|
||||||
|
loadNextPage: any
|
||||||
|
refreshRow: any
|
||||||
|
replaceRow: any
|
||||||
|
refreshData: any
|
||||||
|
cleanRow: any
|
||||||
|
bulkUpdate: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Store = RowStore & RowDerivedStore & RowActionStore
|
||||||
|
|
||||||
|
export const createStores = (): RowStore => {
|
||||||
const rows = writable([])
|
const rows = writable([])
|
||||||
const loading = writable(false)
|
const loading = writable(false)
|
||||||
const loaded = writable(false)
|
const loaded = writable(false)
|
||||||
|
@ -47,7 +86,7 @@ export const createStores = () => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deriveStores = context => {
|
export const deriveStores = (context: StoreContext): RowDerivedStore => {
|
||||||
const { rows, enrichedSchema } = context
|
const { rows, enrichedSchema } = context
|
||||||
|
|
||||||
// Enrich rows with an index property and any pending changes
|
// Enrich rows with an index property and any pending changes
|
||||||
|
@ -60,8 +99,8 @@ export const deriveStores = context => {
|
||||||
return $rows.map((row, idx) => ({
|
return $rows.map((row, idx) => ({
|
||||||
...row,
|
...row,
|
||||||
__idx: idx,
|
__idx: idx,
|
||||||
...customColumns.reduce((map, column) => {
|
...customColumns.reduce((map: any, column: any) => {
|
||||||
const fromField = $enrichedSchema[column.related.field]
|
const fromField = $enrichedSchema![column.related!.field]
|
||||||
map[column.name] = getRelatedTableValues(row, column, fromField)
|
map[column.name] = getRelatedTableValues(row, column, fromField)
|
||||||
return map
|
return map
|
||||||
}, {}),
|
}, {}),
|
||||||
|
@ -71,7 +110,7 @@ export const deriveStores = context => {
|
||||||
|
|
||||||
// Generate a lookup map to quick find a row by ID
|
// Generate a lookup map to quick find a row by ID
|
||||||
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
||||||
let map = {}
|
let map: Record<string, UIRow> = {}
|
||||||
for (let i = 0; i < $enrichedRows.length; i++) {
|
for (let i = 0; i < $enrichedRows.length; i++) {
|
||||||
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
||||||
}
|
}
|
||||||
|
@ -87,7 +126,7 @@ export const deriveStores = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createActions = context => {
|
export const createActions = (context: StoreContext): RowActionStore => {
|
||||||
const {
|
const {
|
||||||
rows,
|
rows,
|
||||||
rowLookupMap,
|
rowLookupMap,
|
||||||
|
@ -114,11 +153,11 @@ export const createActions = context => {
|
||||||
const instanceLoaded = writable(false)
|
const instanceLoaded = writable(false)
|
||||||
|
|
||||||
// Local cache of row IDs to speed up checking if a row exists
|
// Local cache of row IDs to speed up checking if a row exists
|
||||||
let rowCacheMap = {}
|
let rowCacheMap: Record<string, boolean> = {}
|
||||||
|
|
||||||
// Reset everything when datasource changes
|
// Reset everything when datasource changes
|
||||||
let unsubscribe = null
|
let unsubscribe: (() => void) | null = null
|
||||||
let lastResetKey = null
|
let lastResetKey: string | null = null
|
||||||
datasource.subscribe(async $datasource => {
|
datasource.subscribe(async $datasource => {
|
||||||
// Unsub from previous fetch if one exists
|
// Unsub from previous fetch if one exists
|
||||||
unsubscribe?.()
|
unsubscribe?.()
|
||||||
|
@ -157,7 +196,7 @@ export const createActions = context => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Subscribe to changes of this fetch model
|
// Subscribe to changes of this fetch model
|
||||||
unsubscribe = newFetch.subscribe(async $fetch => {
|
unsubscribe = newFetch.subscribe(async ($fetch: any) => {
|
||||||
if ($fetch.error) {
|
if ($fetch.error) {
|
||||||
// Present a helpful error to the user
|
// Present a helpful error to the user
|
||||||
let message = "An unknown error occurred"
|
let message = "An unknown error occurred"
|
||||||
|
@ -214,7 +253,7 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Handles validation errors from the rows API and updates local validation
|
// Handles validation errors from the rows API and updates local validation
|
||||||
// state, storing error messages against relevant cells
|
// state, storing error messages against relevant cells
|
||||||
const handleValidationError = (rowId, error) => {
|
const handleValidationError = (rowId: string, error: any) => {
|
||||||
let errorString
|
let errorString
|
||||||
if (typeof error === "string") {
|
if (typeof error === "string") {
|
||||||
errorString = error
|
errorString = error
|
||||||
|
@ -287,9 +326,19 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adds a new row
|
// Adds a new row
|
||||||
const addRow = async ({ row, idx, bubble = false, notify = true }) => {
|
const addRow = async ({
|
||||||
|
row,
|
||||||
|
idx,
|
||||||
|
bubble = false,
|
||||||
|
notify = true,
|
||||||
|
}: {
|
||||||
|
row: Row
|
||||||
|
idx: number
|
||||||
|
bubble: boolean
|
||||||
|
notify: boolean
|
||||||
|
}) => {
|
||||||
try {
|
try {
|
||||||
const newRow = await datasource.actions.addRow(row)
|
const newRow = (await datasource.actions.addRow(row))!
|
||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
if (idx != null) {
|
if (idx != null) {
|
||||||
|
@ -317,7 +366,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicates a row, inserting the duplicate row after the existing one
|
// Duplicates a row, inserting the duplicate row after the existing one
|
||||||
const duplicateRow = async row => {
|
const duplicateRow = async (row: UIRow) => {
|
||||||
let clone = cleanRow(row)
|
let clone = cleanRow(row)
|
||||||
delete clone._id
|
delete clone._id
|
||||||
delete clone._rev
|
delete clone._rev
|
||||||
|
@ -337,10 +386,13 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duplicates multiple rows, inserting them after the last source row
|
// Duplicates multiple rows, inserting them after the last source row
|
||||||
const bulkDuplicate = async (rowsToDupe, progressCallback) => {
|
const bulkDuplicate = async (
|
||||||
|
rowsToDupe: UIRow[],
|
||||||
|
progressCallback: (any: number) => void
|
||||||
|
) => {
|
||||||
// Find index of last row
|
// Find index of last row
|
||||||
const $rowLookupMap = get(rowLookupMap)
|
const $rowLookupMap = get(rowLookupMap)
|
||||||
const indices = rowsToDupe.map(row => $rowLookupMap[row._id]?.__idx)
|
const indices = rowsToDupe.map(row => $rowLookupMap[row._id!]?.__idx)
|
||||||
const index = Math.max(...indices)
|
const index = Math.max(...indices)
|
||||||
const count = rowsToDupe.length
|
const count = rowsToDupe.length
|
||||||
|
|
||||||
|
@ -357,8 +409,9 @@ export const createActions = context => {
|
||||||
let failed = 0
|
let failed = 0
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
try {
|
try {
|
||||||
saved.push(await datasource.actions.addRow(clones[i]))
|
const newRow = (await datasource.actions.addRow(clones[i]))!
|
||||||
rowCacheMap[saved._id] = true
|
saved.push(newRow)
|
||||||
|
rowCacheMap[newRow._id] = true
|
||||||
await sleep(50) // Small sleep to ensure we avoid rate limiting
|
await sleep(50) // Small sleep to ensure we avoid rate limiting
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
failed++
|
failed++
|
||||||
|
@ -370,7 +423,7 @@ export const createActions = context => {
|
||||||
// Add to state
|
// Add to state
|
||||||
if (saved.length) {
|
if (saved.length) {
|
||||||
rows.update(state => {
|
rows.update(state => {
|
||||||
return state.toSpliced(index + 1, 0, ...saved)
|
return state.splice(index + 1, 0, ...saved)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -385,7 +438,7 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Replaces a row in state with the newly defined row, handling updates,
|
// Replaces a row in state with the newly defined row, handling updates,
|
||||||
// addition and deletion
|
// addition and deletion
|
||||||
const replaceRow = (id, row) => {
|
const replaceRow = (id: string, row: UIRow | undefined) => {
|
||||||
// Get index of row to check if it exists
|
// Get index of row to check if it exists
|
||||||
const $rows = get(rows)
|
const $rows = get(rows)
|
||||||
const $rowLookupMap = get(rowLookupMap)
|
const $rowLookupMap = get(rowLookupMap)
|
||||||
|
@ -410,10 +463,10 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refreshes a specific row
|
// Refreshes a specific row
|
||||||
const refreshRow = async id => {
|
const refreshRow = async (id: string) => {
|
||||||
try {
|
try {
|
||||||
const row = await datasource.actions.getRow(id)
|
const row = await datasource.actions.getRow(id)
|
||||||
replaceRow(id, row)
|
replaceRow(id, row!)
|
||||||
} catch {
|
} catch {
|
||||||
// Do nothing - we probably just don't support refreshing individual rows
|
// Do nothing - we probably just don't support refreshing individual rows
|
||||||
}
|
}
|
||||||
|
@ -425,7 +478,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a changeset for a row actually mutates the row or not
|
// Checks if a changeset for a row actually mutates the row or not
|
||||||
const changesAreValid = (row, changes) => {
|
const changesAreValid = (row: UIRow, changes: Record<string, any>) => {
|
||||||
const columns = Object.keys(changes || {})
|
const columns = Object.keys(changes || {})
|
||||||
if (!row || !columns.length) {
|
if (!row || !columns.length) {
|
||||||
return false
|
return false
|
||||||
|
@ -437,7 +490,7 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Patches a row with some changes in local state, and returns whether a
|
// Patches a row with some changes in local state, and returns whether a
|
||||||
// valid pending change was made or not
|
// valid pending change was made or not
|
||||||
const stashRowChanges = (rowId, changes) => {
|
const stashRowChanges = (rowId: string, changes: Record<string, any>) => {
|
||||||
const $rowLookupMap = get(rowLookupMap)
|
const $rowLookupMap = get(rowLookupMap)
|
||||||
const $columnLookupMap = get(columnLookupMap)
|
const $columnLookupMap = get(columnLookupMap)
|
||||||
const row = $rowLookupMap[rowId]
|
const row = $rowLookupMap[rowId]
|
||||||
|
@ -477,13 +530,18 @@ export const createActions = context => {
|
||||||
changes = null,
|
changes = null,
|
||||||
updateState = true,
|
updateState = true,
|
||||||
handleErrors = true,
|
handleErrors = true,
|
||||||
|
}: {
|
||||||
|
rowId: string
|
||||||
|
changes?: any
|
||||||
|
updateState?: boolean
|
||||||
|
handleErrors?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const $rowLookupMap = get(rowLookupMap)
|
const $rowLookupMap = get(rowLookupMap)
|
||||||
const row = $rowLookupMap[rowId]
|
const row = $rowLookupMap[rowId]
|
||||||
if (row == null) {
|
if (row == null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let savedRow
|
let savedRow: UIRow | undefined = undefined
|
||||||
|
|
||||||
// Save change
|
// Save change
|
||||||
try {
|
try {
|
||||||
|
@ -496,7 +554,7 @@ export const createActions = context => {
|
||||||
// Update row
|
// Update row
|
||||||
const stashedChanges = get(rowChangeCache)[rowId]
|
const stashedChanges = get(rowChangeCache)[rowId]
|
||||||
const newRow = { ...cleanRow(row), ...stashedChanges, ...changes }
|
const newRow = { ...cleanRow(row), ...stashedChanges, ...changes }
|
||||||
savedRow = await datasource.actions.updateRow(newRow)
|
savedRow = (await datasource.actions.updateRow(newRow))!
|
||||||
|
|
||||||
// Update row state after a successful change
|
// Update row state after a successful change
|
||||||
if (savedRow?._id) {
|
if (savedRow?._id) {
|
||||||
|
@ -537,14 +595,27 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updates a value of a row
|
// Updates a value of a row
|
||||||
const updateValue = async ({ rowId, column, value, apply = true }) => {
|
const updateValue = async ({
|
||||||
|
rowId,
|
||||||
|
column,
|
||||||
|
value,
|
||||||
|
apply = true,
|
||||||
|
}: {
|
||||||
|
rowId: string
|
||||||
|
column: any
|
||||||
|
value: any
|
||||||
|
apply: boolean
|
||||||
|
}) => {
|
||||||
const success = stashRowChanges(rowId, { [column]: value })
|
const success = stashRowChanges(rowId, { [column]: value })
|
||||||
if (success && apply) {
|
if (success && apply) {
|
||||||
await applyRowChanges({ rowId })
|
await applyRowChanges({ rowId })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bulkUpdate = async (changeMap, progressCallback) => {
|
const bulkUpdate = async (
|
||||||
|
changeMap: Record<string, any>,
|
||||||
|
progressCallback: (any: number) => void
|
||||||
|
) => {
|
||||||
const rowIds = Object.keys(changeMap || {})
|
const rowIds = Object.keys(changeMap || {})
|
||||||
const count = rowIds.length
|
const count = rowIds.length
|
||||||
if (!count) {
|
if (!count) {
|
||||||
|
@ -613,7 +684,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deletes an array of rows
|
// Deletes an array of rows
|
||||||
const deleteRows = async rowsToDelete => {
|
const deleteRows = async (rowsToDelete: UIRow[]) => {
|
||||||
if (!rowsToDelete?.length) {
|
if (!rowsToDelete?.length) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -628,7 +699,7 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Local handler to process new rows inside the fetch, and append any new
|
// Local handler to process new rows inside the fetch, and append any new
|
||||||
// rows to state that we haven't encountered before
|
// rows to state that we haven't encountered before
|
||||||
const handleNewRows = (newRows, resetRows) => {
|
const handleNewRows = (newRows: UIRow[], resetRows?: boolean) => {
|
||||||
if (resetRows) {
|
if (resetRows) {
|
||||||
rowCacheMap = {}
|
rowCacheMap = {}
|
||||||
}
|
}
|
||||||
|
@ -658,7 +729,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local handler to remove rows from state
|
// Local handler to remove rows from state
|
||||||
const handleRemoveRows = rowsToRemove => {
|
const handleRemoveRows = (rowsToRemove: UIRow[]) => {
|
||||||
const deletedIds = rowsToRemove.map(row => row._id)
|
const deletedIds = rowsToRemove.map(row => row._id)
|
||||||
|
|
||||||
// We deliberately do not remove IDs from the cache map as the data may
|
// We deliberately do not remove IDs from the cache map as the data may
|
||||||
|
@ -675,11 +746,11 @@ export const createActions = context => {
|
||||||
|
|
||||||
// Cleans a row by removing any internal grid metadata from it.
|
// Cleans a row by removing any internal grid metadata from it.
|
||||||
// Call this before passing a row to any sort of external flow.
|
// Call this before passing a row to any sort of external flow.
|
||||||
const cleanRow = row => {
|
const cleanRow = (row: Row) => {
|
||||||
let clone = { ...row }
|
let clone = { ...row }
|
||||||
delete clone.__idx
|
delete clone.__idx
|
||||||
delete clone.__metadata
|
delete clone.__metadata
|
||||||
if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id)) {
|
if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id!)) {
|
||||||
delete clone._id
|
delete clone._id
|
||||||
}
|
}
|
||||||
return clone
|
return clone
|
||||||
|
@ -706,7 +777,7 @@ export const createActions = context => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialise = context => {
|
export const initialise = (context: StoreContext) => {
|
||||||
const {
|
const {
|
||||||
rowChangeCache,
|
rowChangeCache,
|
||||||
inProgressChanges,
|
inProgressChanges,
|
||||||
|
@ -733,7 +804,9 @@ export const initialise = context => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const { rowId, field } = parseCellID(id)
|
let { rowId, field } = parseCellID(id)
|
||||||
|
rowId = rowId!
|
||||||
|
field = field!
|
||||||
const hasChanges = field in (get(rowChangeCache)[rowId] || {})
|
const hasChanges = field in (get(rowChangeCache)[rowId] || {})
|
||||||
const hasErrors = validation.actions.rowHasErrors(rowId)
|
const hasErrors = validation.actions.rowHasErrors(rowId)
|
||||||
const isSavingChanges = get(inProgressChanges)[rowId]
|
const isSavingChanges = get(inProgressChanges)[rowId]
|
Loading…
Reference in New Issue