Browse Source

feat: keep row indexes on state to recover on undo/redo

Signed-off-by: mertmit <mertmit99@gmail.com>
pull/5332/head
mertmit 2 years ago
parent
commit
6eac6adcac
  1. 16
      packages/nc-gui/components/smartsheet/Kanban.vue
  2. 101
      packages/nc-gui/composables/useKanbanViewStore.ts

16
packages/nc-gui/components/smartsheet/Kanban.vue

@ -12,6 +12,7 @@ import {
IsPublicInj, IsPublicInj,
MetaInj, MetaInj,
OpenNewRecordFormHookInj, OpenNewRecordFormHookInj,
extractPkFromRow,
iconMap, iconMap,
inject, inject,
isImage, isImage,
@ -76,6 +77,7 @@ const {
deleteStack, deleteStack,
shouldScrollToRight, shouldScrollToRight,
deleteRow, deleteRow,
moveHistory,
} = useKanbanViewStoreOrThrow() } = useKanbanViewStoreOrThrow()
const { isUIAllowed } = useUIPermission() const { isUIAllowed } = useUIPermission()
@ -227,11 +229,25 @@ async function onMoveStack(event: any) {
async function onMove(event: any, stackKey: string) { async function onMove(event: any, stackKey: string) {
if (event.added) { if (event.added) {
const ele = event.added.element const ele = event.added.element
moveHistory.value.unshift({
op: 'added',
pk: extractPkFromRow(event.added.element.row, meta.value!.columns!),
index: event.added.newIndex,
stack: stackKey,
})
ele.row[groupingField.value] = stackKey ele.row[groupingField.value] = stackKey
countByStack.value.set(stackKey, countByStack.value.get(stackKey)! + 1) countByStack.value.set(stackKey, countByStack.value.get(stackKey)! + 1)
await updateOrSaveRow(ele) await updateOrSaveRow(ele)
} else if (event.removed) { } else if (event.removed) {
countByStack.value.set(stackKey, countByStack.value.get(stackKey)! - 1) countByStack.value.set(stackKey, countByStack.value.get(stackKey)! - 1)
moveHistory.value.unshift({
op: 'removed',
pk: extractPkFromRow(event.removed.element.row, meta.value!.columns!),
index: event.removed.oldIndex,
stack: stackKey,
})
} }
} }

101
packages/nc-gui/composables/useKanbanViewStore.ts

@ -62,6 +62,9 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
const { addUndo, clone } = useUndoRedo() const { addUndo, clone } = useUndoRedo()
// save history of stack changes for undo/redo
const moveHistory = ref<{ op: 'added' | 'removed'; pk: string; stack: string; index: number }[]>([])
const sqlUi = ref( const sqlUi = ref(
(meta.value as TableType)?.base_id ? sqlUis.value[(meta.value as TableType).base_id!] : Object.values(sqlUis.value)[0], (meta.value as TableType)?.base_id ? sqlUis.value[(meta.value as TableType).base_id!] : Object.values(sqlUis.value)[0],
) )
@ -315,6 +318,17 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
await $api.dbView.kanbanUpdate(viewMeta.value.id, updateObj) await $api.dbView.kanbanUpdate(viewMeta.value.id, updateObj)
} }
function findRowInState(rowData: Record<string, any>) {
const pk: Record<string, string> = rowPkData(rowData, meta?.value?.columns as ColumnType[])
for (const rows of formattedData.value.values()) {
for (const row of rows) {
if (Object.keys(pk).every((k) => pk[k] === row.row[k])) {
return row
}
}
}
}
async function insertRow(row: Record<string, any>, rowIndex = formattedData.value.get(null)!.length, undo = false) { async function insertRow(row: Record<string, any>, rowIndex = formattedData.value.get(null)!.length, undo = false) {
try { try {
const insertObj = (meta?.value?.columns as ColumnType[]).reduce((o: Record<string, any>, col) => { const insertObj = (meta?.value?.columns as ColumnType[]).reduce((o: Record<string, any>, col) => {
@ -341,28 +355,27 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
const pkData = rowPkData(row.row, meta.value?.columns as ColumnType[]) const pkData = rowPkData(row.row, meta.value?.columns as ColumnType[])
row.row = { ...pkData, ...row.row } row.row = { ...pkData, ...row.row }
await insertRow(row, rowIndex, true) await insertRow(row, rowIndex, true)
await loadKanbanMeta() addOrEditStackRow(row, true)
await loadKanbanData()
}, },
args: [clone(row), rowIndex], args: [clone(row), rowIndex],
}, },
undo: { undo: {
fn: async function undo(this: UndoRedoAction, id: string) { fn: async function undo(this: UndoRedoAction, id: string) {
await deleteRowById(id) await deleteRowById(id)
await loadKanbanMeta() const row = findRowInState(insertedData)
await loadKanbanData() if (row) removeRowFromTargetStack(row)
}, },
args: [id], args: [id],
}, },
scope: viewMeta.value?.title, scope: viewMeta.value?.title,
}) })
}
formattedData.value.get(null)?.splice(rowIndex ?? 0, 1, { formattedData.value.get(null)?.splice(rowIndex ?? 0, 1, {
row: insertedData, row: insertedData,
rowMeta: {}, rowMeta: {},
oldRow: { ...insertedData }, oldRow: { ...insertedData },
}) })
}
return insertedData return insertedData
} catch (error: any) { } catch (error: any) {
@ -398,16 +411,36 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
}) })
if (!undo) { if (!undo) {
const oldRowIndex = moveHistory.value.find((ele) => ele.op === 'removed' && ele.pk === id)
const nextRowIndex = moveHistory.value.find((ele) => ele.op === 'added' && ele.pk === id)
addUndo({ addUndo({
redo: { redo: {
fn: async function redo(toUpdate: Row, property: string) { fn: async function redo(toUpdate: Row, property: string) {
await updateRowProperty(toUpdate, property, true) const updatedData = await updateRowProperty(toUpdate, property, true)
const row = findRowInState(toUpdate.row)
if (row) {
Object.assign(row.row, updatedData)
if (row.row[groupingField.value] !== row.oldRow[groupingField.value])
addOrEditStackRow(row, false, nextRowIndex?.index)
Object.assign(row.oldRow, updatedData)
}
}, },
args: [clone(toUpdate), property], args: [clone(toUpdate), property],
}, },
undo: { undo: {
fn: async function undo(toUpdate: Row, property: string) { fn: async function undo(toUpdate: Row, property: string) {
await updateRowProperty({ row: toUpdate.oldRow, oldRow: toUpdate.row, rowMeta: toUpdate.rowMeta }, property, true) const updatedData = await updateRowProperty(
{ row: toUpdate.oldRow, oldRow: toUpdate.row, rowMeta: toUpdate.rowMeta },
property,
true,
)
const row = findRowInState(toUpdate.row)
if (row) {
Object.assign(row.row, updatedData)
if (row.row[groupingField.value] !== row.oldRow[groupingField.value])
addOrEditStackRow(row, false, oldRowIndex?.index)
Object.assign(row.oldRow, updatedData)
}
}, },
args: [clone(toUpdate), property], args: [clone(toUpdate), property],
}, },
@ -417,19 +450,9 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
/** update row data(to sync formula and other related columns) */ /** update row data(to sync formula and other related columns) */
Object.assign(toUpdate.row, updatedRowData) Object.assign(toUpdate.row, updatedRowData)
Object.assign(toUpdate.oldRow, updatedRowData) Object.assign(toUpdate.oldRow, updatedRowData)
} else {
const pk: Record<string, string> = rowPkData(toUpdate.row, meta?.value?.columns as ColumnType[])
for (const rows of formattedData.value.values()) {
for (const row of rows) {
if (Object.keys(pk).every((k) => pk[k] === row.row[k])) {
Object.assign(row.row, updatedRowData)
addOrEditStackRow(row, false)
Object.assign(row.oldRow, updatedRowData)
break
}
}
}
} }
return updatedRowData
} catch (e: any) { } catch (e: any) {
message.error(`${t('msg.error.rowUpdateFailed')} ${await extractSdkResponseErrorMsg(e)}`) message.error(`${t('msg.error.rowUpdateFailed')} ${await extractSdkResponseErrorMsg(e)}`)
} }
@ -523,7 +546,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
return formattedData.value.get(null)![addAfter] return formattedData.value.get(null)![addAfter]
} }
function addOrEditStackRow(row: Row, isNewRow: boolean) { function addOrEditStackRow(row: Row, isNewRow: boolean, rowIndex?: number) {
const stackTitle = row.row[groupingField.value] const stackTitle = row.row[groupingField.value]
const oldStackTitle = row.oldRow[groupingField.value] const oldStackTitle = row.oldRow[groupingField.value]
@ -531,7 +554,11 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// add a new record // add a new record
if (stackTitle) { if (stackTitle) {
// push the row to target stack // push the row to target stack
formattedData.value.get(stackTitle)!.push(row) if (rowIndex !== undefined) {
formattedData.value.get(stackTitle)!.splice(rowIndex, 0, row)
} else {
formattedData.value.get(stackTitle)!.push(row)
}
// increase the current count in the target stack by 1 // increase the current count in the target stack by 1
countByStack.value.set(stackTitle, countByStack.value.get(stackTitle)! + 1) countByStack.value.set(stackTitle, countByStack.value.get(stackTitle)! + 1)
// clear the one under uncategorized since we don't reload the view // clear the one under uncategorized since we don't reload the view
@ -556,11 +583,25 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
// add new row to countByStack & formattedData // add new row to countByStack & formattedData
countByStack.value.set(stackTitle, countByStack.value.get(stackTitle)! + 1) countByStack.value.set(stackTitle, countByStack.value.get(stackTitle)! + 1)
formattedData.value.set(stackTitle, [...formattedData.value.get(stackTitle)!, row])
if (rowIndex !== undefined) {
const targetStack = formattedData.value.get(stackTitle)!
targetStack.splice(rowIndex, 0, row)
formattedData.value.set(stackTitle, targetStack)
} else {
formattedData.value.set(stackTitle, [...formattedData.value.get(stackTitle)!, row])
}
} else { } else {
// update the row in formattedData // update the row in formattedData
const updatedRow = formattedData.value.get(stackTitle)! const updatedRow = formattedData.value.get(stackTitle)!
updatedRow[idxToUpdateOrDelete] = row
if (rowIndex !== undefined) {
updatedRow.splice(idxToUpdateOrDelete, 1)
updatedRow.splice(rowIndex, 0, row)
} else {
updatedRow[idxToUpdateOrDelete] = row
}
formattedData.value.set(oldStackTitle, updatedRow) formattedData.value.set(oldStackTitle, updatedRow)
} }
} }
@ -605,8 +646,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
const pkData = rowPkData(row.row, meta.value?.columns as ColumnType[]) const pkData = rowPkData(row.row, meta.value?.columns as ColumnType[])
row.row = { ...pkData, ...row.row } row.row = { ...pkData, ...row.row }
await insertRow(row.row, undefined, true) await insertRow(row.row, undefined, true)
await loadKanbanMeta() addOrEditStackRow(row, true)
await loadKanbanData()
}, },
args: [clone(row)], args: [clone(row)],
}, },
@ -676,6 +716,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState(
removeRowFromUncategorizedStack, removeRowFromUncategorizedStack,
shouldScrollToRight, shouldScrollToRight,
deleteRow, deleteRow,
moveHistory,
} }
}, },
'kanban-view-store', 'kanban-view-store',

Loading…
Cancel
Save