diff --git a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue index dab5863a68..59a89bfea8 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue @@ -364,7 +364,7 @@ defineExpose({
- +
@@ -372,7 +372,7 @@ defineExpose({
- +
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 }) }