diff --git a/packages/frontend-core/src/components/sheet/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/sheet/cells/RelationshipCell.svelte index 56953b02f3..d5a0bf1fa6 100644 --- a/packages/frontend-core/src/components/sheet/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/sheet/cells/RelationshipCell.svelte @@ -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(() => { diff --git a/packages/frontend-core/src/components/sheet/overlays/KeyboardManager.svelte b/packages/frontend-core/src/components/sheet/overlays/KeyboardManager.svelte index d2d06e2643..4fe8611127 100644 --- a/packages/frontend-core/src/components/sheet/overlays/KeyboardManager.svelte +++ b/packages/frontend-core/src/components/sheet/overlays/KeyboardManager.svelte @@ -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]}` diff --git a/packages/frontend-core/src/components/sheet/stores/rows.js b/packages/frontend-core/src/components/sheet/stores/rows.js index ff83b5ce8a..ffc5de90ba 100644 --- a/packages/frontend-core/src/components/sheet/stores/rows.js +++ b/packages/frontend-core/src/components/sheet/stores/rows.js @@ -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: { diff --git a/packages/frontend-core/src/components/sheet/stores/ui.js b/packages/frontend-core/src/components/sheet/stores/ui.js index 75ebeb8735..12044470b8 100644 --- a/packages/frontend-core/src/components/sheet/stores/ui.js +++ b/packages/frontend-core/src/components/sheet/stores/ui.js @@ -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 diff --git a/packages/frontend-core/src/components/sheet/stores/validation.js b/packages/frontend-core/src/components/sheet/stores/validation.js index afa2ae3033..3ece8240eb 100644 --- a/packages/frontend-core/src/components/sheet/stores/validation.js +++ b/packages/frontend-core/src/components/sheet/stores/validation.js @@ -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 }) } diff --git a/packages/frontend-core/src/components/sheet/stores/viewport.js b/packages/frontend-core/src/components/sheet/stores/viewport.js index 679e32ad29..c5fd8e47f2 100644 --- a/packages/frontend-core/src/components/sheet/stores/viewport.js +++ b/packages/frontend-core/src/components/sheet/stores/viewport.js @@ -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 )