diff --git a/packages/nc-gui/composables/useViewFilters.ts b/packages/nc-gui/composables/useViewFilters.ts
index 486ed25b8d..b63de50cac 100644
--- a/packages/nc-gui/composables/useViewFilters.ts
+++ b/packages/nc-gui/composables/useViewFilters.ts
@@ -20,7 +20,7 @@ import {
watch,
} from '#imports'
import { TabMetaInj } from '~/context'
-import type { Filter, TabItem } from '~/lib'
+import type { Filter, TabItem, UndoRedoAction } from '~/lib'
export function useViewFilters(
view: Ref,
@@ -46,6 +46,8 @@ export function useViewFilters(
const { metas } = useMetas()
+ const { addUndo, clone } = useUndoRedo()
+
const _filters = ref([])
const nestedMode = computed(() => isPublic.value || !isUIAllowed('filterSync') || !isUIAllowed('filterChildrenRead'))
@@ -107,6 +109,19 @@ export function useViewFilters(
}, {})
})
+ const lastFilters = ref([])
+
+ watchOnce(filters, (filters: Filter[]) => {
+ lastFilters.value = clone(filters)
+ })
+
+ // get delta between two objects and return the changed fields (value is from b)
+ const getFieldDelta = (a: any, b: any) => {
+ return Object.entries(b)
+ .filter(([key, val]) => a[key] !== val && key in a)
+ .reduce((a, [key, v]) => ({ ...a, [key]: v }), {})
+ }
+
const isComparisonOpAllowed = (
filter: FilterType,
compOp: {
@@ -230,39 +245,39 @@ export function useViewFilters(
}
}
- const deleteFilter = async (filter: Filter, i: number) => {
- // if shared or sync permission not allowed simply remove it from array
- if (nestedMode.value) {
- filters.value.splice(i, 1)
- filters.value = [...filters.value]
- reloadData?.()
- } else {
- if (filter.id) {
- // if auto-apply disabled mark it as disabled
- if (!autoApply?.value) {
- filter.status = 'delete'
- // if auto-apply enabled invoke delete api and remove from array
- // no splice is required here
- } else {
- try {
- await $api.dbTableFilter.delete(filter.id)
- reloadData?.()
- filters.value.splice(i, 1)
- } catch (e: any) {
- console.log(e)
- message.error(await extractSdkResponseErrorMsg(e))
- }
+ const saveOrUpdate = async (filter: Filter, i: number, force = false, undo = false) => {
+ if (!view.value) return
+
+ if (!undo) {
+ const lastFilter = lastFilters.value[i]
+ if (lastFilter) {
+ const delta = clone(getFieldDelta(filter, lastFilter))
+ if (Object.keys(delta).length > 0) {
+ addUndo({
+ undo: {
+ fn: (prop: string, data: any) => {
+ const f = filters.value[i]
+ if (f) {
+ f[prop as keyof Filter] = data
+ saveOrUpdate(f, i, force, true)
+ }
+ },
+ args: [Object.keys(delta)[0], Object.values(delta)[0]],
+ },
+ redo: {
+ fn: (prop: string, data: any) => {
+ const f = filters.value[i]
+ if (f) {
+ f[prop as keyof Filter] = data
+ saveOrUpdate(f, i, force, true)
+ }
+ },
+ args: [Object.keys(delta)[0], filter[Object.keys(delta)[0] as keyof Filter]],
+ },
+ })
}
- // if not synced yet remove it from array
- } else {
- filters.value.splice(i, 1)
}
- $e('a:filter:delete', { length: nonDeletedFilters.value.length })
}
- }
-
- const saveOrUpdate = async (filter: Filter, i: number, force = false) => {
- if (!view.value) return
try {
if (nestedMode.value) {
@@ -270,7 +285,7 @@ export function useViewFilters(
filters.value = [...filters.value]
} else if (!autoApply?.value && !force) {
filter.status = filter.id ? 'update' : 'create'
- } else if (filter.id) {
+ } else if (filter.id && filter.status !== 'create') {
await $api.dbTableFilter.update(filter.id, {
...filter,
fk_parent_id: parentId,
@@ -290,13 +305,86 @@ export function useViewFilters(
message.error(await extractSdkResponseErrorMsg(e))
}
+ lastFilters.value = clone(filters.value)
+
reloadData?.()
}
+ const deleteFilter = async (filter: Filter, i: number, undo = false) => {
+ if (!undo && !filter.is_group) {
+ addUndo({
+ undo: {
+ fn: async (fl: Filter) => {
+ fl.status = 'create'
+ filters.value.splice(i, 0, fl)
+ await saveOrUpdate(fl, i, false, true)
+ },
+ args: [clone(filter)],
+ },
+ redo: {
+ fn: async (index: number) => {
+ await deleteFilter(filters.value[index], index, true)
+ },
+ args: [i],
+ },
+ })
+ }
+ // if shared or sync permission not allowed simply remove it from array
+ if (nestedMode.value) {
+ filters.value.splice(i, 1)
+ filters.value = [...filters.value]
+ reloadData?.()
+ } else {
+ if (filter.id) {
+ // if auto-apply disabled mark it as disabled
+ if (!autoApply?.value) {
+ filter.status = 'delete'
+ // if auto-apply enabled invoke delete api and remove from array
+ // no splice is required here
+ } else {
+ try {
+ await $api.dbTableFilter.delete(filter.id)
+ reloadData?.()
+ filters.value.splice(i, 1)
+ } catch (e: any) {
+ console.log(e)
+ message.error(await extractSdkResponseErrorMsg(e))
+ }
+ }
+ // if not synced yet remove it from array
+ } else {
+ filters.value.splice(i, 1)
+ }
+ $e('a:filter:delete', { length: nonDeletedFilters.value.length })
+ }
+ }
+
const saveOrUpdateDebounced = useDebounceFn(saveOrUpdate, 500)
- const addFilter = () => {
+ const addFilter = async (undo = false) => {
filters.value.push(placeholderFilter())
+ if (!undo) {
+ addUndo({
+ undo: {
+ fn: async function undo(this: UndoRedoAction, i: number) {
+ this.redo.args = [i, clone(filters.value[i])]
+ await deleteFilter(filters.value[i], i, true)
+ },
+ args: [filters.value.length - 1],
+ },
+ redo: {
+ fn: async (i: number, fl: Filter) => {
+ fl.status = 'create'
+ filters.value.splice(i, 0, fl)
+ await saveOrUpdate(fl, i, false, true)
+ },
+ args: [],
+ },
+ })
+ }
+
+ lastFilters.value = clone(filters.value)
+
$e('a:filter:add', { length: filters.value.length })
}
@@ -317,6 +405,8 @@ export function useViewFilters(
await saveOrUpdate(filters.value[index], index, true)
+ lastFilters.value = clone(filters.value)
+
$e('a:filter:add', { length: filters.value.length, group: true })
}