1
0
Fork 0
mirror of synced 2024-10-04 03:54:37 +13:00

Add multiple validation improvements

This commit is contained in:
Andrew Kingston 2023-04-11 16:34:13 +01:00
parent 6c203a1e66
commit 4a6713e9d3
6 changed files with 117 additions and 60 deletions

View file

@ -24,6 +24,12 @@
let lastSearchId
let results
$: {
if (focused) {
console.log(value)
}
}
$: oneRowOnly = schema?.relationshipType === "one-to-many"
$: editable = focused && !readonly
$: results = getResults(searchResults, value)
@ -52,6 +58,7 @@
if (!row?._id) {
return false
}
console.log(lookupMap)
return lookupMap?.[row._id] === true
}
@ -192,11 +199,7 @@
}
candidateIndex = null
}
// Clear search state to allow finding a new row again
searchString = null
searchResults = []
lastSearchString = null
close()
}
onMount(() => {

View file

@ -3,7 +3,7 @@
import { debounce } from "../../../utils/utils"
const {
rows,
enrichedRows,
focusedCellId,
visibleColumns,
focusedRow,
@ -63,7 +63,7 @@
}
const selectFirstCell = () => {
const firstRow = $rows[0]
const firstRow = $enrichedRows[0]
if (!firstRow) {
return
}
@ -102,7 +102,7 @@
if (!$focusedRow) {
return
}
const newRow = $rows[$focusedRow.__idx + delta]
const newRow = $enrichedRows[$focusedRow.__idx + delta]
if (newRow) {
const split = $focusedCellId.split("-")
$focusedCellId = `${newRow._id}-${split[1]}`

View file

@ -13,18 +13,8 @@ export const createStores = () => {
const filter = writable([])
const loaded = writable(false)
const sort = writable(initialSortState)
// Enrich rows with an index property
const enrichedRows = derived(
rows,
$rows => {
return $rows.map((row, idx) => ({
...row,
__idx: idx,
}))
},
[]
)
const rowChangeCache = writable({})
const inProgressChanges = writable({})
// Generate a lookup map to quick find a row by ID
const rowLookupMap = derived(
@ -40,15 +30,14 @@ export const createStores = () => {
)
return {
rows: {
...rows,
subscribe: enrichedRows.subscribe,
},
rows,
rowLookupMap,
table,
filter,
loaded,
sort,
rowChangeCache,
inProgressChanges,
}
}
@ -66,6 +55,9 @@ export const deriveStores = context => {
validation,
focusedCellId,
columns,
rowChangeCache,
inProgressChanges,
previousFocusedRowId,
} = context
const instanceLoaded = writable(false)
const fetch = writable(null)
@ -73,6 +65,19 @@ export const deriveStores = context => {
// Local cache of row IDs to speed up checking if a row exists
let rowCacheMap = {}
// Enrich rows with an index property and any pending changes
const enrichedRows = derived(
[rows, rowChangeCache],
([$rows, $rowChangeCache]) => {
return $rows.map((row, idx) => ({
...row,
...$rowChangeCache[row._id],
__idx: idx,
}))
},
[]
)
// Reset everything when table ID changes
let unsubscribe = null
tableId.subscribe($tableId => {
@ -153,6 +158,7 @@ export const deriveStores = context => {
// state, storing error messages against relevant cells
const handleValidationError = (rowId, error) => {
if (error?.json?.validationErrors) {
// Normal validation error
const keys = Object.keys(error.json.validationErrors)
const $columns = get(columns)
for (let column of keys) {
@ -173,7 +179,8 @@ export const deriveStores = context => {
// Focus the first cell with an error
focusedCellId.set(`${rowId}-${keys[0]}`)
} else {
notifications.error(`Error saving row: ${error?.message}`)
// Some other error - just update the current cell
validation.actions.setError(get(focusedCellId), error?.message || "Error")
}
}
@ -254,25 +261,42 @@ export const deriveStores = context => {
}
// Immediately update state so that the change is reflected
let newRow = { ...row, [column]: value }
rows.update(state => {
state[index] = { ...newRow }
return state
})
rowChangeCache.update(state => ({
...state,
[rowId]: {
...state[rowId],
[column]: value,
},
}))
// Save change
delete newRow.__idx
try {
await API.saveRow(newRow)
} catch (error) {
handleValidationError(newRow._id, error)
inProgressChanges.update(state => ({
...state,
[rowId]: true,
}))
const newRow = { ...row, ...get(rowChangeCache)[rowId] }
const saved = await API.saveRow(newRow)
// Revert change
// Update state after a successful change
rows.update(state => {
state[index] = row
return state
state[index] = {
...newRow,
_rev: saved._rev,
}
return state.slice()
})
rowChangeCache.update(state => ({
...state,
[rowId]: null,
}))
} catch (error) {
handleValidationError(rowId, error)
}
inProgressChanges.update(state => ({
...state,
[rowId]: false,
}))
}
// Deletes an array of rows
@ -338,7 +362,18 @@ export const deriveStores = context => {
return get(rowLookupMap)[id] != null
}
// Wipe the row change cache when changing row
previousFocusedRowId.subscribe(id => {
if (!get(inProgressChanges)[id]) {
rowChangeCache.update(state => ({
...state,
[id]: null,
}))
}
})
return {
enrichedRows,
rows: {
...rows,
actions: {

View file

@ -6,9 +6,29 @@ export const createStores = () => {
const selectedRows = writable({})
const hoveredRowId = writable(null)
const rowHeight = writable(36)
const previousFocusedRowId = writable(null)
// Derive the current focused row ID
const focusedRowId = derived(
focusedCellId,
$focusedCellId => {
return $focusedCellId?.split("-")[0]
},
null
)
// Remember the last focused row ID so that we can store the previous one
let lastFocusedRowId = null
focusedRowId.subscribe(id => {
previousFocusedRowId.set(lastFocusedRowId)
lastFocusedRowId = id
})
return {
focusedCellId,
focusedCellAPI,
focusedRowId,
previousFocusedRowId,
selectedRows,
hoveredRowId,
rowHeight,
@ -16,13 +36,19 @@ export const createStores = () => {
}
export const deriveStores = context => {
const { focusedCellId, selectedRows, hoveredRowId, rows, rowLookupMap } =
context
const {
rows,
focusedCellId,
selectedRows,
hoveredRowId,
enrichedRows,
rowLookupMap,
} = context
// Derive the row that contains the selected cell
const focusedRow = derived(
[focusedCellId, rowLookupMap, rows],
([$focusedCellId, $rowLookupMap, $rows]) => {
[focusedCellId, rowLookupMap, enrichedRows],
([$focusedCellId, $rowLookupMap, $enrichedRows]) => {
const rowId = $focusedCellId?.split("-")[0]
if (rowId === "new") {
@ -31,7 +57,7 @@ export const deriveStores = context => {
} else {
// All normal rows
const index = $rowLookupMap[rowId]
return $rows[index]
return $enrichedRows[index]
}
},
null

View file

@ -1,4 +1,4 @@
import { writable, get, derived } from "svelte/store"
import { writable, get } from "svelte/store"
export const createStores = () => {
const validation = writable({})
@ -24,29 +24,22 @@ export const createStores = () => {
}
export const deriveStores = context => {
const { validation, focusedRow, columns, stickyColumn } = context
const focusedRowId = derived(focusedRow, $focusedRow => $focusedRow?._id)
const { validation, previousFocusedRowId, columns, stickyColumn } = context
// Store the row ID that was previously focused, so we can remove errors from
// it when we focus a new row
let previousFocusedRowId = null
focusedRowId.subscribe(id => {
// Remove validation errors from previous focused row
if (previousFocusedRowId) {
// Remove validation errors from previous focused row
previousFocusedRowId.subscribe(id => {
if (id) {
const $columns = get(columns)
const $stickyColumn = get(stickyColumn)
validation.update(state => {
$columns.forEach(column => {
state[`${previousFocusedRowId}-${column.name}`] = null
state[`${id}-${column.name}`] = null
})
if ($stickyColumn) {
state[`${previousFocusedRowId}-${$stickyColumn.name}`] = null
state[`${id}-${$stickyColumn.name}`] = null
}
return state
})
}
// Store row ID
previousFocusedRowId = id
})
}

View file

@ -4,7 +4,7 @@ export const deriveStores = context => {
const {
rowHeight,
visibleColumns,
rows,
enrichedRows,
scrollTop,
scrollLeft,
width,
@ -29,9 +29,9 @@ export const deriveStores = context => {
0
)
const renderedRows = derived(
[rows, scrolledRowCount, visualRowCapacity],
([$rows, $scrolledRowCount, $visualRowCapacity]) => {
return $rows.slice(
[enrichedRows, scrolledRowCount, visualRowCapacity],
([$enrichedRows, $scrolledRowCount, $visualRowCapacity]) => {
return $enrichedRows.slice(
$scrolledRowCount,
$scrolledRowCount + $visualRowCapacity
)