Merge pull request #15255 from Budibase/typing/stores-grid-rows
Typing grid rows stores
This commit is contained in:
commit
92f92d2c62
|
@ -2,6 +2,7 @@
|
|||
"extends": "./tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"lib": ["ESNext"],
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@budibase/*": [
|
||||
|
|
|
@ -16,7 +16,16 @@ interface DerivedColumnStore {
|
|||
hasNonAutoColumn: Readable<boolean>
|
||||
}
|
||||
|
||||
export type Store = ColumnStore & DerivedColumnStore
|
||||
interface ColumnActions {
|
||||
columns: ColumnStore["columns"] & {
|
||||
actions: {
|
||||
changeAllColumnWidths: (width: number) => Promise<void>
|
||||
isReadonly: (column: UIColumn) => boolean
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = ColumnStore & DerivedColumnStore & ColumnActions
|
||||
|
||||
export const createStores = (): ColumnStore => {
|
||||
const columns = writable<UIColumn[]>([])
|
||||
|
@ -95,7 +104,7 @@ export const deriveStores = (context: StoreContext): DerivedColumnStore => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = (context: StoreContext) => {
|
||||
export const createActions = (context: StoreContext): ColumnActions => {
|
||||
const { columns, datasource } = context
|
||||
|
||||
// Updates the width of all columns
|
||||
|
|
|
@ -3,12 +3,12 @@ import { getDatasourceDefinition, getDatasourceSchema } from "../../../fetch"
|
|||
import { enrichSchemaWithRelColumns, memo } from "../../../utils"
|
||||
import { cloneDeep } from "lodash"
|
||||
import {
|
||||
Row,
|
||||
SaveRowRequest,
|
||||
SaveTableRequest,
|
||||
UIDatasource,
|
||||
UIFieldMutation,
|
||||
UIFieldSchema,
|
||||
UIRow,
|
||||
UpdateViewRequest,
|
||||
ViewV2Type,
|
||||
} from "@budibase/types"
|
||||
|
@ -327,7 +327,7 @@ export const createActions = (context: StoreContext): ActionDatasourceStore => {
|
|||
}
|
||||
|
||||
// Deletes rows from the datasource
|
||||
const deleteRows = async (rows: Row[]) => {
|
||||
const deleteRows = async (rows: UIRow[]) => {
|
||||
return await getAPI()?.actions.deleteRows(rows)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
Row,
|
||||
SaveRowRequest,
|
||||
SaveTableRequest,
|
||||
UIDatasource,
|
||||
UIRow,
|
||||
UpdateViewRequest,
|
||||
} from "@budibase/types"
|
||||
|
||||
|
@ -10,10 +10,10 @@ interface DatasourceBaseActions<
|
|||
TSaveDefinitionRequest = UpdateViewRequest | SaveTableRequest
|
||||
> {
|
||||
saveDefinition: (newDefinition: TSaveDefinitionRequest) => Promise<void>
|
||||
addRow: (row: SaveRowRequest) => Promise<Row | void>
|
||||
updateRow: (row: SaveRowRequest) => Promise<Row | void>
|
||||
deleteRows: (rows: Row[]) => Promise<void>
|
||||
getRow: (id: string) => Promise<Row | void>
|
||||
addRow: (row: SaveRowRequest) => Promise<UIRow | undefined>
|
||||
updateRow: (row: SaveRowRequest) => Promise<UIRow | undefined>
|
||||
deleteRows: (rows: UIRow[]) => Promise<void>
|
||||
getRow: (id: string) => Promise<UIRow | void>
|
||||
isDatasourceValid: (datasource: UIDatasource) => boolean | void
|
||||
canUseColumn: (name: string) => boolean | void
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ export const initialise = (context: StoreContext) => {
|
|||
if (!isSameDatasource($fetch?.options?.datasource, $datasource)) {
|
||||
return
|
||||
}
|
||||
$fetch.update({
|
||||
$fetch?.update({
|
||||
filter: $allFilters,
|
||||
})
|
||||
})
|
||||
|
@ -120,7 +120,7 @@ export const initialise = (context: StoreContext) => {
|
|||
if (!isSameDatasource($fetch?.options?.datasource, $datasource)) {
|
||||
return
|
||||
}
|
||||
$fetch.update({
|
||||
$fetch?.update({
|
||||
sortOrder: $sort.order || SortOrder.ASCENDING,
|
||||
sortColumn: $sort.column,
|
||||
})
|
||||
|
|
|
@ -31,7 +31,8 @@ export const createActions = (context: StoreContext): TableActions => {
|
|||
...row,
|
||||
tableId: get(datasource)?.tableId,
|
||||
}
|
||||
return await API.saveRow(row, SuppressErrors)
|
||||
const newRow = await API.saveRow(row, SuppressErrors)
|
||||
return { ...newRow, _id: newRow._id! }
|
||||
}
|
||||
|
||||
const deleteRows = async (rows: Row[]) => {
|
||||
|
@ -52,7 +53,12 @@ export const createActions = (context: StoreContext): TableActions => {
|
|||
},
|
||||
paginate: false,
|
||||
})
|
||||
return res?.rows?.[0]
|
||||
const row = res?.rows?.[0]
|
||||
if (!row) {
|
||||
return
|
||||
}
|
||||
|
||||
return { ...row, _id: row._id! }
|
||||
}
|
||||
|
||||
const canUseColumn = (name: string) => {
|
||||
|
|
|
@ -34,8 +34,10 @@ export const createActions = (context: StoreContext): ViewActions => {
|
|||
tableId: $datasource?.tableId,
|
||||
_viewId: $datasource?.id,
|
||||
}
|
||||
const newRow = await API.saveRow(row, SuppressErrors)
|
||||
return {
|
||||
...(await API.saveRow(row, SuppressErrors)),
|
||||
...newRow,
|
||||
_id: newRow._id!,
|
||||
_viewId: row._viewId,
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +56,12 @@ export const createActions = (context: StoreContext): ViewActions => {
|
|||
},
|
||||
paginate: false,
|
||||
})
|
||||
return res?.rows?.[0]
|
||||
const row = res?.rows?.[0]
|
||||
if (!row) {
|
||||
return
|
||||
}
|
||||
|
||||
return { ...row, _id: row._id! }
|
||||
}
|
||||
|
||||
const isDatasourceValid = (datasource: UIDatasource) => {
|
||||
|
|
|
@ -69,12 +69,10 @@ export type Store = BaseStore &
|
|||
Clipboard.Store &
|
||||
Scroll.Store & {
|
||||
// TODO while typing the rest of stores
|
||||
fetch: Writable<any>
|
||||
sort: Writable<any>
|
||||
initialFilter: Writable<any>
|
||||
initialSortColumn: Writable<any>
|
||||
initialSortOrder: Writable<any>
|
||||
rows: Writable<any> & { actions: any }
|
||||
subscribe: any
|
||||
config: Writable<any>
|
||||
dispatch: (event: string, data: any) => any
|
||||
|
@ -82,13 +80,11 @@ export type Store = BaseStore &
|
|||
schemaOverrides: Writable<any>
|
||||
gridID: string
|
||||
props: Writable<any>
|
||||
rowLookupMap: Writable<any>
|
||||
width: Writable<number>
|
||||
fixedRowHeight: Writable<number>
|
||||
rowChangeCache: Readable<any>
|
||||
bounds: Readable<any>
|
||||
height: Readable<number>
|
||||
}
|
||||
} & Rows.Store
|
||||
|
||||
export const attachStores = (context: Store): Store => {
|
||||
// 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 { NewRowID, RowPageSize } from "../lib/constants"
|
||||
import {
|
||||
|
@ -10,10 +10,74 @@ import {
|
|||
import { tick } from "svelte"
|
||||
import { Helpers } from "@budibase/bbui"
|
||||
import { sleep } from "../../../utils/utils"
|
||||
import { FieldType } from "@budibase/types"
|
||||
import { FieldType, Row, UIFetchAPI, UIRow } from "@budibase/types"
|
||||
import { getRelatedTableValues } from "../../../utils"
|
||||
import { Store as StoreContext } from "."
|
||||
|
||||
export const createStores = () => {
|
||||
interface IndexedUIRow extends UIRow {
|
||||
__idx: number
|
||||
}
|
||||
|
||||
interface RowStore {
|
||||
rows: Writable<UIRow[]>
|
||||
fetch: Writable<UIFetchAPI | null>
|
||||
loaded: Writable<boolean>
|
||||
refreshing: Writable<boolean>
|
||||
loading: Writable<boolean>
|
||||
rowChangeCache: Writable<Record<string, Record<string, any>>>
|
||||
inProgressChanges: Writable<Record<string, number>>
|
||||
hasNextPage: Writable<boolean>
|
||||
error: Writable<string | null>
|
||||
}
|
||||
|
||||
interface RowDerivedStore {
|
||||
rows: RowStore["rows"]
|
||||
rowLookupMap: Readable<Record<string, IndexedUIRow>>
|
||||
}
|
||||
|
||||
interface RowActionStore {
|
||||
rows: RowStore["rows"] & {
|
||||
actions: {
|
||||
addRow: (params: {
|
||||
row: Row
|
||||
idx: number
|
||||
bubble: boolean
|
||||
notify: boolean
|
||||
}) => Promise<UIRow | undefined>
|
||||
duplicateRow: (row: UIRow) => Promise<UIRow | undefined>
|
||||
bulkDuplicate: (
|
||||
rowsToDupe: UIRow[],
|
||||
progressCallback: (progressPercentage: number) => void
|
||||
) => Promise<UIRow[]>
|
||||
updateValue: (params: {
|
||||
rowId: string
|
||||
column: string
|
||||
value: any
|
||||
apply: boolean
|
||||
}) => Promise<void>
|
||||
applyRowChanges: (params: {
|
||||
rowId: string
|
||||
changes?: Record<string, any>
|
||||
updateState?: boolean
|
||||
handleErrors?: boolean
|
||||
}) => Promise<UIRow | undefined>
|
||||
deleteRows: (rowsToDelete: UIRow[]) => Promise<void>
|
||||
loadNextPage: () => void
|
||||
refreshRow: (id: string) => Promise<void>
|
||||
replaceRow: (id: string, row: UIRow | undefined) => void
|
||||
refreshData: () => Promise<void>
|
||||
cleanRow: (row: UIRow) => Row
|
||||
bulkUpdate: (
|
||||
changeMap: Record<string, Record<string, any>>,
|
||||
progressCallback: (progressPercentage: number) => void
|
||||
) => Promise<void>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = RowStore & RowDerivedStore & RowActionStore
|
||||
|
||||
export const createStores = (): RowStore => {
|
||||
const rows = writable([])
|
||||
const loading = writable(false)
|
||||
const loaded = writable(false)
|
||||
|
@ -47,7 +111,7 @@ export const createStores = () => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deriveStores = context => {
|
||||
export const deriveStores = (context: StoreContext): RowDerivedStore => {
|
||||
const { rows, enrichedSchema } = context
|
||||
|
||||
// Enrich rows with an index property and any pending changes
|
||||
|
@ -57,12 +121,16 @@ export const deriveStores = context => {
|
|||
const customColumns = Object.values($enrichedSchema || {}).filter(
|
||||
f => f.related
|
||||
)
|
||||
return $rows.map((row, idx) => ({
|
||||
return $rows.map<IndexedUIRow>((row, idx) => ({
|
||||
...row,
|
||||
__idx: idx,
|
||||
...customColumns.reduce((map, column) => {
|
||||
const fromField = $enrichedSchema[column.related.field]
|
||||
map[column.name] = getRelatedTableValues(row, column, fromField)
|
||||
...customColumns.reduce<Record<string, string>>((map, column) => {
|
||||
const fromField = $enrichedSchema![column.related!.field]
|
||||
map[column.name] = getRelatedTableValues(
|
||||
row,
|
||||
{ ...column, related: column.related! },
|
||||
fromField
|
||||
)
|
||||
return map
|
||||
}, {}),
|
||||
}))
|
||||
|
@ -71,7 +139,7 @@ export const deriveStores = context => {
|
|||
|
||||
// Generate a lookup map to quick find a row by ID
|
||||
const rowLookupMap = derived(enrichedRows, $enrichedRows => {
|
||||
let map = {}
|
||||
let map: Record<string, IndexedUIRow> = {}
|
||||
for (let i = 0; i < $enrichedRows.length; i++) {
|
||||
map[$enrichedRows[i]._id] = $enrichedRows[i]
|
||||
}
|
||||
|
@ -87,7 +155,7 @@ export const deriveStores = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = context => {
|
||||
export const createActions = (context: StoreContext): RowActionStore => {
|
||||
const {
|
||||
rows,
|
||||
rowLookupMap,
|
||||
|
@ -114,11 +182,11 @@ export const createActions = context => {
|
|||
const instanceLoaded = writable(false)
|
||||
|
||||
// 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
|
||||
let unsubscribe = null
|
||||
let lastResetKey = null
|
||||
let unsubscribe: (() => void) | null = null
|
||||
let lastResetKey: string | null = null
|
||||
datasource.subscribe(async $datasource => {
|
||||
// Unsub from previous fetch if one exists
|
||||
unsubscribe?.()
|
||||
|
@ -157,7 +225,7 @@ export const createActions = context => {
|
|||
})
|
||||
|
||||
// Subscribe to changes of this fetch model
|
||||
unsubscribe = newFetch.subscribe(async $fetch => {
|
||||
unsubscribe = newFetch.subscribe(async ($fetch: UIFetchAPI) => {
|
||||
if ($fetch.error) {
|
||||
// Present a helpful error to the user
|
||||
let message = "An unknown error occurred"
|
||||
|
@ -214,7 +282,15 @@ export const createActions = context => {
|
|||
|
||||
// Handles validation errors from the rows API and updates local validation
|
||||
// state, storing error messages against relevant cells
|
||||
const handleValidationError = (rowId, error) => {
|
||||
const handleValidationError = (
|
||||
rowId: string,
|
||||
error:
|
||||
| string
|
||||
| {
|
||||
message?: string
|
||||
json: { validationErrors: Record<string, string | undefined> }
|
||||
}
|
||||
) => {
|
||||
let errorString
|
||||
if (typeof error === "string") {
|
||||
errorString = error
|
||||
|
@ -224,7 +300,11 @@ export const createActions = context => {
|
|||
|
||||
// If the server doesn't reply with a valid error, assume that the source
|
||||
// of the error is the focused cell's column
|
||||
if (!error?.json?.validationErrors && errorString) {
|
||||
if (
|
||||
typeof error !== "string" &&
|
||||
!error?.json?.validationErrors &&
|
||||
errorString
|
||||
) {
|
||||
const { field: focusedColumn } = parseCellID(get(focusedCellId))
|
||||
if (focusedColumn) {
|
||||
error = {
|
||||
|
@ -236,7 +316,7 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (error?.json?.validationErrors) {
|
||||
if (typeof error !== "string" && error?.json?.validationErrors) {
|
||||
// Normal validation errors
|
||||
const keys = Object.keys(error.json.validationErrors)
|
||||
const $columns = get(columns)
|
||||
|
@ -251,11 +331,11 @@ export const createActions = context => {
|
|||
missingColumns.push(column)
|
||||
}
|
||||
}
|
||||
|
||||
const { json } = error
|
||||
// Process errors for columns that we have
|
||||
for (let column of erroredColumns) {
|
||||
// Ensure we have a valid error to display
|
||||
let err = error.json.validationErrors[column]
|
||||
let err = json.validationErrors[column]
|
||||
if (Array.isArray(err)) {
|
||||
err = err[0]
|
||||
}
|
||||
|
@ -287,9 +367,19 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
const newRow = await datasource.actions.addRow(row)
|
||||
const newRow = (await datasource.actions.addRow(row))!
|
||||
|
||||
// Update state
|
||||
if (idx != null) {
|
||||
|
@ -306,7 +396,7 @@ export const createActions = context => {
|
|||
get(notifications).success("Row created successfully")
|
||||
}
|
||||
return newRow
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (bubble) {
|
||||
throw error
|
||||
} else {
|
||||
|
@ -317,7 +407,7 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Duplicates a row, inserting the duplicate row after the existing one
|
||||
const duplicateRow = async row => {
|
||||
const duplicateRow = async (row: UIRow) => {
|
||||
let clone = cleanRow(row)
|
||||
delete clone._id
|
||||
delete clone._rev
|
||||
|
@ -330,17 +420,20 @@ export const createActions = context => {
|
|||
})
|
||||
get(notifications).success("Duplicated 1 row")
|
||||
return duped
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
handleValidationError(row._id, error)
|
||||
validation.actions.focusFirstRowError(row._id)
|
||||
}
|
||||
}
|
||||
|
||||
// Duplicates multiple rows, inserting them after the last source row
|
||||
const bulkDuplicate = async (rowsToDupe, progressCallback) => {
|
||||
const bulkDuplicate = async (
|
||||
rowsToDupe: UIRow[],
|
||||
progressCallback: (progressPercentage: number) => void
|
||||
) => {
|
||||
// Find index of last row
|
||||
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 count = rowsToDupe.length
|
||||
|
||||
|
@ -357,8 +450,9 @@ export const createActions = context => {
|
|||
let failed = 0
|
||||
for (let i = 0; i < count; i++) {
|
||||
try {
|
||||
saved.push(await datasource.actions.addRow(clones[i]))
|
||||
rowCacheMap[saved._id] = true
|
||||
const newRow = (await datasource.actions.addRow(clones[i]))!
|
||||
saved.push(newRow)
|
||||
rowCacheMap[newRow._id] = true
|
||||
await sleep(50) // Small sleep to ensure we avoid rate limiting
|
||||
} catch (error) {
|
||||
failed++
|
||||
|
@ -385,7 +479,7 @@ export const createActions = context => {
|
|||
|
||||
// Replaces a row in state with the newly defined row, handling updates,
|
||||
// addition and deletion
|
||||
const replaceRow = (id, row) => {
|
||||
const replaceRow = (id: string, row: UIRow | undefined) => {
|
||||
// Get index of row to check if it exists
|
||||
const $rows = get(rows)
|
||||
const $rowLookupMap = get(rowLookupMap)
|
||||
|
@ -410,22 +504,22 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Refreshes a specific row
|
||||
const refreshRow = async id => {
|
||||
const refreshRow = async (id: string) => {
|
||||
try {
|
||||
const row = await datasource.actions.getRow(id)
|
||||
replaceRow(id, row)
|
||||
replaceRow(id, row!)
|
||||
} catch {
|
||||
// Do nothing - we probably just don't support refreshing individual rows
|
||||
}
|
||||
}
|
||||
|
||||
// Refreshes all data
|
||||
const refreshData = () => {
|
||||
get(fetch)?.getInitialData()
|
||||
const refreshData = async () => {
|
||||
await get(fetch)?.getInitialData()
|
||||
}
|
||||
|
||||
// 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 || {})
|
||||
if (!row || !columns.length) {
|
||||
return false
|
||||
|
@ -437,7 +531,7 @@ export const createActions = context => {
|
|||
|
||||
// Patches a row with some changes in local state, and returns whether a
|
||||
// valid pending change was made or not
|
||||
const stashRowChanges = (rowId, changes) => {
|
||||
const stashRowChanges = (rowId: string, changes: Record<string, any>) => {
|
||||
const $rowLookupMap = get(rowLookupMap)
|
||||
const $columnLookupMap = get(columnLookupMap)
|
||||
const row = $rowLookupMap[rowId]
|
||||
|
@ -477,13 +571,18 @@ export const createActions = context => {
|
|||
changes = null,
|
||||
updateState = true,
|
||||
handleErrors = true,
|
||||
}: {
|
||||
rowId: string
|
||||
changes?: Record<string, any> | null
|
||||
updateState?: boolean
|
||||
handleErrors?: boolean
|
||||
}) => {
|
||||
const $rowLookupMap = get(rowLookupMap)
|
||||
const row = $rowLookupMap[rowId]
|
||||
if (row == null) {
|
||||
return
|
||||
}
|
||||
let savedRow
|
||||
let savedRow: UIRow | undefined = undefined
|
||||
|
||||
// Save change
|
||||
try {
|
||||
|
@ -496,13 +595,13 @@ export const createActions = context => {
|
|||
// Update row
|
||||
const stashedChanges = get(rowChangeCache)[rowId]
|
||||
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
|
||||
if (savedRow?._id) {
|
||||
if (updateState) {
|
||||
rows.update(state => {
|
||||
state[row.__idx] = savedRow
|
||||
state[row.__idx] = savedRow!
|
||||
return state.slice()
|
||||
})
|
||||
}
|
||||
|
@ -521,7 +620,7 @@ export const createActions = context => {
|
|||
})
|
||||
return state
|
||||
})
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
if (handleErrors) {
|
||||
handleValidationError(rowId, error)
|
||||
validation.actions.focusFirstRowError(rowId)
|
||||
|
@ -537,14 +636,27 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// 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: string
|
||||
value: any
|
||||
apply: boolean
|
||||
}) => {
|
||||
const success = stashRowChanges(rowId, { [column]: value })
|
||||
if (success && apply) {
|
||||
await applyRowChanges({ rowId })
|
||||
}
|
||||
}
|
||||
|
||||
const bulkUpdate = async (changeMap, progressCallback) => {
|
||||
const bulkUpdate = async (
|
||||
changeMap: Record<string, Record<string, any>>,
|
||||
progressCallback: (progressPercentage: number) => void
|
||||
) => {
|
||||
const rowIds = Object.keys(changeMap || {})
|
||||
const count = rowIds.length
|
||||
if (!count) {
|
||||
|
@ -613,7 +725,7 @@ export const createActions = context => {
|
|||
}
|
||||
|
||||
// Deletes an array of rows
|
||||
const deleteRows = async rowsToDelete => {
|
||||
const deleteRows = async (rowsToDelete: UIRow[]) => {
|
||||
if (!rowsToDelete?.length) {
|
||||
return
|
||||
}
|
||||
|
@ -628,7 +740,7 @@ export const createActions = context => {
|
|||
|
||||
// Local handler to process new rows inside the fetch, and append any new
|
||||
// rows to state that we haven't encountered before
|
||||
const handleNewRows = (newRows, resetRows) => {
|
||||
const handleNewRows = (newRows: Row[], resetRows?: boolean) => {
|
||||
if (resetRows) {
|
||||
rowCacheMap = {}
|
||||
}
|
||||
|
@ -645,20 +757,20 @@ export const createActions = context => {
|
|||
newRow._id = generateRowID()
|
||||
}
|
||||
|
||||
if (!rowCacheMap[newRow._id]) {
|
||||
rowCacheMap[newRow._id] = true
|
||||
if (!rowCacheMap[newRow._id!]) {
|
||||
rowCacheMap[newRow._id!] = true
|
||||
rowsToAppend.push(newRow)
|
||||
}
|
||||
}
|
||||
if (resetRows) {
|
||||
rows.set(rowsToAppend)
|
||||
rows.set(rowsToAppend as UIRow[])
|
||||
} else if (rowsToAppend.length) {
|
||||
rows.update(state => [...state, ...rowsToAppend])
|
||||
rows.update(state => [...state, ...(rowsToAppend as UIRow[])])
|
||||
}
|
||||
}
|
||||
|
||||
// Local handler to remove rows from state
|
||||
const handleRemoveRows = rowsToRemove => {
|
||||
const handleRemoveRows = (rowsToRemove: UIRow[]) => {
|
||||
const deletedIds = rowsToRemove.map(row => row._id)
|
||||
|
||||
// We deliberately do not remove IDs from the cache map as the data may
|
||||
|
@ -675,11 +787,11 @@ export const createActions = context => {
|
|||
|
||||
// Cleans a row by removing any internal grid metadata from it.
|
||||
// Call this before passing a row to any sort of external flow.
|
||||
const cleanRow = row => {
|
||||
let clone = { ...row }
|
||||
const cleanRow = (row: UIRow) => {
|
||||
let clone: Row = { ...row }
|
||||
delete clone.__idx
|
||||
delete clone.__metadata
|
||||
if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id)) {
|
||||
if (!get(hasBudibaseIdentifiers) && isGeneratedRowID(clone._id!)) {
|
||||
delete clone._id
|
||||
}
|
||||
return clone
|
||||
|
@ -706,7 +818,7 @@ export const createActions = context => {
|
|||
}
|
||||
}
|
||||
|
||||
export const initialise = context => {
|
||||
export const initialise = (context: StoreContext) => {
|
||||
const {
|
||||
rowChangeCache,
|
||||
inProgressChanges,
|
||||
|
@ -733,7 +845,9 @@ export const initialise = context => {
|
|||
if (!id) {
|
||||
return
|
||||
}
|
||||
const { rowId, field } = parseCellID(id)
|
||||
let { rowId, field } = parseCellID(id)
|
||||
rowId = rowId!
|
||||
field = field!
|
||||
const hasChanges = field in (get(rowChangeCache)[rowId] || {})
|
||||
const hasErrors = validation.actions.rowHasErrors(rowId)
|
||||
const isSavingChanges = get(inProgressChanges)[rowId]
|
|
@ -40,12 +40,21 @@ export interface UIDerivedStore {
|
|||
compact: Readable<boolean>
|
||||
selectedRowCount: Readable<number>
|
||||
isSelectingCells: Readable<boolean>
|
||||
selectedCells: Readable<string[][]> & { actions: any }
|
||||
selectedCells: Readable<string[][]>
|
||||
selectedCellMap: Readable<Record<string, boolean>>
|
||||
selectedCellCount: Readable<number>
|
||||
}
|
||||
|
||||
export type Store = UIStore & UIDerivedStore
|
||||
interface UIActionStore {
|
||||
selectedCells: UIDerivedStore["selectedCells"] & {
|
||||
actions: {
|
||||
clear: () => void
|
||||
selectRange: (source: string | null, target: string | null) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = UIStore & UIDerivedStore & UIActionStore
|
||||
|
||||
export const createStores = (context: StoreContext): UIStore => {
|
||||
const { props } = context
|
||||
|
@ -82,7 +91,7 @@ export const createStores = (context: StoreContext): UIStore => {
|
|||
}
|
||||
}
|
||||
|
||||
export const deriveStores = (context: StoreContext) => {
|
||||
export const deriveStores = (context: StoreContext): UIDerivedStore => {
|
||||
const {
|
||||
focusedCellId,
|
||||
rows,
|
||||
|
@ -97,14 +106,14 @@ export const deriveStores = (context: StoreContext) => {
|
|||
|
||||
// Derive the current focused row ID
|
||||
const focusedRowId = derived(focusedCellId, $focusedCellId => {
|
||||
return parseCellID($focusedCellId).rowId
|
||||
return parseCellID($focusedCellId).rowId ?? null
|
||||
})
|
||||
|
||||
// Derive the row that contains the selected cell
|
||||
const focusedRow = derived(
|
||||
[focusedRowId, rowLookupMap],
|
||||
([$focusedRowId, $rowLookupMap]) => {
|
||||
if ($focusedRowId === undefined) {
|
||||
if ($focusedRowId === null) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,17 @@ interface DerivedValidationStore {
|
|||
validationRowLookupMap: Readable<Record<string, string[]>>
|
||||
}
|
||||
|
||||
export type Store = ValidationStore & DerivedValidationStore
|
||||
interface ValidationActions {
|
||||
validation: ValidationStore["validation"] & {
|
||||
actions: {
|
||||
setError: (cellId: string | undefined, error: string) => void
|
||||
rowHasErrors: (rowId: string) => boolean
|
||||
focusFirstRowError: (rowId: string) => void
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type Store = ValidationStore & DerivedValidationStore & ValidationActions
|
||||
|
||||
// Normally we would break out actions into the explicit "createActions"
|
||||
// function, but for validation all these actions are pure so can go into
|
||||
|
@ -49,7 +59,7 @@ export const deriveStores = (context: StoreContext): DerivedValidationStore => {
|
|||
}
|
||||
}
|
||||
|
||||
export const createActions = (context: StoreContext) => {
|
||||
export const createActions = (context: StoreContext): ValidationActions => {
|
||||
const { validation, focusedCellId, validationRowLookupMap } = context
|
||||
|
||||
const setError = (cellId: string | undefined, error: string) => {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"extends": "../../tsconfig.build.json",
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"lib": ["ESNext"],
|
||||
"module": "preserve",
|
||||
"moduleResolution": "bundler",
|
||||
"outDir": "./dist",
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import {
|
||||
Row,
|
||||
SortOrder,
|
||||
UIDatasource,
|
||||
UILegacyFilter,
|
||||
UISearchFilter,
|
||||
} from "@budibase/types"
|
||||
|
||||
export interface UIFetchAPI {
|
||||
definition: UIDatasource
|
||||
|
||||
getInitialData: () => Promise<void>
|
||||
loading: any
|
||||
loaded: boolean
|
||||
|
||||
resetKey: string | null
|
||||
error: any
|
||||
|
||||
hasNextPage: boolean
|
||||
nextPage: () => Promise<void>
|
||||
|
||||
rows: Row[]
|
||||
|
||||
options?: {
|
||||
datasource?: {
|
||||
tableId: string
|
||||
id: string
|
||||
}
|
||||
}
|
||||
update: ({
|
||||
sortOrder,
|
||||
sortColumn,
|
||||
}: {
|
||||
sortOrder?: SortOrder
|
||||
sortColumn?: string
|
||||
filter?: UILegacyFilter[] | UISearchFilter
|
||||
}) => any
|
||||
}
|
|
@ -4,3 +4,5 @@ export * from "./table"
|
|||
export * from "./view"
|
||||
export * from "./user"
|
||||
export * from "./filters"
|
||||
export * from "./rows"
|
||||
export * from "./fetch"
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { Row } from "@budibase/types"
|
||||
|
||||
export type UIRow = Row & {
|
||||
_id: string
|
||||
}
|
Loading…
Reference in New Issue