| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- // @ts-nocheck
- import { isPendingAddRow, PendingAddRow, SupaRow } from '../types'
- import { isTableLike, type Entity } from '@/data/table-editor/table-editor-types'
- import {
- EditCellContentOperation,
- NewQueuedOperation,
- QueuedOperation,
- QueuedOperationType,
- } from '@/state/table-editor-operation-queue.types'
- import type { Dictionary } from '@/types'
- interface EditCellKeyOperation extends Omit<
- EditCellContentOperation,
- 'payload' | 'id' | 'timestamp'
- > {
- type: QueuedOperationType.EDIT_CELL_CONTENT
- tableId: number
- payload: {
- columnName: string
- rowIdentifiers: Dictionary<unknown>
- }
- }
- export function generateTableChangeKey(
- operation: NewQueuedOperation | EditCellKeyOperation
- ): string {
- if (operation.type === QueuedOperationType.EDIT_CELL_CONTENT) {
- const { columnName, rowIdentifiers } = operation.payload
- const rowIdentifiersKey = Object.entries(rowIdentifiers)
- .sort(([a], [b]) => a.localeCompare(b))
- .map(([key, value]) => `${key}:${value}`)
- .join('|')
- return `${operation.type}:${operation.tableId}:${columnName}:${rowIdentifiersKey}`
- }
- if (operation.type === QueuedOperationType.ADD_ROW) {
- return `${operation.type}:${operation.tableId}:${operation.payload.tempId}`
- }
- if (operation.type === QueuedOperationType.DELETE_ROW) {
- const { rowIdentifiers } = operation.payload
- const rowIdentifiersKey = Object.entries(rowIdentifiers)
- .sort(([a], [b]) => a.localeCompare(b))
- .map(([key, value]) => `${key}:${value}`)
- .join('|')
- return `${operation.type}:${operation.tableId}:${rowIdentifiersKey}`
- }
- // Exhaustive check - TypeScript will error if we miss a case
- const _exhaustiveCheck: never = operation
- throw new Error(`Unknown operation type: ${(_exhaustiveCheck as { type: string }).type}`)
- }
- export function rowMatchesIdentifiers(
- row: Dictionary<unknown>,
- rowIdentifiers: Dictionary<unknown>
- ): boolean {
- const identifierEntries = Object.entries(rowIdentifiers)
- if (identifierEntries.length === 0) return false
- return identifierEntries.every(([key, value]) => row[key] === value)
- }
- export function removeRow(rows: SupaRow[], rowIdentifiers: Dictionary<unknown>): SupaRow[] {
- return rows.filter((row) => !rowMatchesIdentifiers(row, rowIdentifiers))
- }
- interface QueueCellEditParams {
- queueOperation: (operation: NewQueuedOperation) => void
- tableId: number
- table: Entity
- row: SupaRow
- rowIdentifiers: Dictionary<unknown>
- columnName: string
- oldValue: unknown
- newValue: unknown
- enumArrayColumns?: string[]
- }
- export function queueCellEditWithOptimisticUpdate({
- queueOperation,
- tableId,
- table,
- row,
- rowIdentifiers: callerRowIdentifiers,
- columnName,
- oldValue,
- newValue,
- enumArrayColumns,
- }: QueueCellEditParams) {
- // Updated row identifiers to include __tempId for pending add rows so edits merge into ADD_ROW operation
- const rowIdentifiers: Dictionary<unknown> = { ...callerRowIdentifiers }
- if (isPendingAddRow(row)) {
- rowIdentifiers.__tempId = row.__tempId
- }
- // Queue the operation
- queueOperation({
- type: QueuedOperationType.EDIT_CELL_CONTENT,
- tableId,
- payload: {
- rowIdentifiers,
- columnName,
- oldValue,
- newValue,
- table,
- enumArrayColumns,
- },
- })
- }
- interface QueueRowAddParams {
- queueOperation: (operation: NewQueuedOperation) => void
- tableId: number
- table: Entity
- rowData: PendingAddRow
- enumArrayColumns?: string[]
- }
- export function queueRowAddWithOptimisticUpdate({
- queueOperation,
- tableId,
- table,
- rowData,
- enumArrayColumns,
- }: QueueRowAddParams) {
- // Generate unique idx and tempId for this pending row
- const idx = -Date.now()
- const tempId = String(idx)
- // Queue the operation
- queueOperation({
- type: QueuedOperationType.ADD_ROW,
- tableId,
- payload: {
- tempId,
- rowData,
- table,
- enumArrayColumns,
- },
- })
- }
- export const formatGridDataWithOperationValues = ({
- operations,
- rows,
- }: {
- operations: QueuedOperation[]
- rows: SupaRow[]
- }) => {
- const formattedRows = rows.slice()
- operations.forEach((op) => {
- if (op.type === QueuedOperationType.EDIT_CELL_CONTENT) {
- const { rowIdentifiers, columnName, newValue } = op.payload
- const rowIdx = formattedRows.findIndex((row) => rowMatchesIdentifiers(row, rowIdentifiers))
- if (rowIdx !== -1) {
- formattedRows[rowIdx] = { ...formattedRows[rowIdx], [columnName]: newValue }
- }
- } else if (op.type === QueuedOperationType.ADD_ROW) {
- const { tempId, rowData } = op.payload
- const idx = Number(tempId)
- // Check if row with this tempId already exists
- const existingIndex = formattedRows.findIndex(
- (row) => isPendingAddRow(row) && row.__tempId === tempId
- )
- if (existingIndex >= 0) {
- // Update existing row in place
- formattedRows[existingIndex] = {
- ...formattedRows[existingIndex],
- ...rowData,
- __tempId: tempId,
- }
- } else {
- const newRow: PendingAddRow = { ...rowData, idx, __tempId: tempId }
- formattedRows.unshift(newRow)
- }
- } else if (op.type === QueuedOperationType.DELETE_ROW) {
- const { rowIdentifiers } = op.payload
- const rowIdx = formattedRows.findIndex((row) => rowMatchesIdentifiers(row, rowIdentifiers))
- if (rowIdx !== -1) {
- formattedRows[rowIdx] = { ...formattedRows[rowIdx], __isDeleted: true }
- }
- }
- })
- return formattedRows
- }
- interface QueueRowDeletesParams {
- rows: SupaRow[]
- table: Entity
- queueOperation: (operation: NewQueuedOperation) => void
- projectRef: string | undefined
- }
- /**
- * Queue multiple row delete operations with optimistic updates.
- * Caller is responsible for checking if queue mode is enabled before calling.
- */
- export function queueRowDeletesWithOptimisticUpdate({
- rows,
- table,
- queueOperation,
- projectRef,
- }: QueueRowDeletesParams): void {
- // [Ali] We can handle these better in the future
- // right now this is a pretty abnormal case of this occurring
- if (!projectRef) {
- console.error('Cannot queue row deletes: projectRef is required')
- return
- }
- if (!isTableLike(table)) {
- console.error('Cannot queue row deletes: table must be a TableLike entity')
- return
- }
- if (table.primary_keys.length === 0) {
- console.error('Cannot queue row deletes: table has no primary keys')
- return
- }
- for (const row of rows) {
- const rowIdentifiers: Record<string, unknown> = {}
- table.primary_keys.forEach((pk) => {
- rowIdentifiers[pk.name] = row[pk.name]
- })
- queueOperation({
- type: QueuedOperationType.DELETE_ROW,
- tableId: table.id,
- payload: {
- rowIdentifiers,
- originalRow: row,
- table,
- },
- })
- }
- }
|