diff --git a/packages/nc-gui/components/dlg/TableRename.vue b/packages/nc-gui/components/dlg/TableRename.vue index e7b6f63920..4aa78d6760 100644 --- a/packages/nc-gui/components/dlg/TableRename.vue +++ b/packages/nc-gui/components/dlg/TableRename.vue @@ -44,7 +44,7 @@ const projectStore = useProject() const { loadTables, isMysql, isMssql, isPg } = projectStore const { tables, project } = storeToRefs(projectStore) -const { addUndo } = useUndoRedo() +const { addUndo, defineProjectScope } = useUndoRedo() const inputEl = $ref() @@ -145,7 +145,7 @@ const renameTable = async (undo = false) => { }, args: [tableMeta.title], }, - scope: tableMeta.project_id, + scope: defineProjectScope({ model: tableMeta }), }) } diff --git a/packages/nc-gui/components/smartsheet/Kanban.vue b/packages/nc-gui/components/smartsheet/Kanban.vue index 2ce52b749c..29eef66177 100644 --- a/packages/nc-gui/components/smartsheet/Kanban.vue +++ b/packages/nc-gui/components/smartsheet/Kanban.vue @@ -85,7 +85,7 @@ const { isUIAllowed } = useUIPermission() const { appInfo } = $(useGlobal()) -const { addUndo } = useUndoRedo() +const { addUndo, defineViewScope } = useUndoRedo() provide(IsFormInj, ref(false)) @@ -244,7 +244,7 @@ async function onMoveStack(event: any, undo = false) { }, args: [{ moved: { oldIndex, newIndex } }, true], }, - scope: view.value?.title, + scope: defineViewScope({ view: view.value }), }) } } diff --git a/packages/nc-gui/components/smartsheet/header/Menu.vue b/packages/nc-gui/components/smartsheet/header/Menu.vue index 0de1a9a24a..9f1f909ca0 100644 --- a/packages/nc-gui/components/smartsheet/header/Menu.vue +++ b/packages/nc-gui/components/smartsheet/header/Menu.vue @@ -20,7 +20,7 @@ import { useSmartsheetStoreOrThrow, useUndoRedo, } from '#imports' -import { UndoRedoAction } from '~~/lib'; +import type { UndoRedoAction } from '~~/lib' const { virtual = false } = defineProps<{ virtual?: boolean }>() @@ -44,7 +44,7 @@ const { t } = useI18n() const { getMeta } = useMetas() -const { addUndo } = useUndoRedo() +const { addUndo, defineModelScope, defineViewScope } = useUndoRedo() const deleteColumn = () => Modal.confirm({ @@ -113,7 +113,7 @@ const setAsDisplayValue = async () => { }, args: [currentDisplayValue?.id], }, - scope: meta.value?.id, + scope: defineModelScope({ model: meta.value }), }) } catch (e) { message.error(t('msg.error.primaryColumnUpdateFailed')) @@ -151,7 +151,7 @@ const sortByColumn = async (direction: 'asc' | 'desc') => { }, args: [data.id], }, - scope: view.value?.title, + scope: defineViewScope({ view: view.value }), }) eventBus.emit(SmartsheetStoreEvents.SORT_RELOAD) @@ -287,7 +287,7 @@ const hideField = async () => { }, args: [currentColumn!.id], }, - scope: view.value?.title, + scope: defineViewScope({ view: view.value }), }) } diff --git a/packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue b/packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue index 29c61534a2..af114f3cf2 100644 --- a/packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue +++ b/packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue @@ -47,7 +47,7 @@ const { api } = useApi() const router = useRouter() -const { addUndo } = useUndoRedo() +const { addUndo, defineModelScope } = useUndoRedo() /** Selected view(s) for menu */ const selected = ref([]) @@ -87,6 +87,8 @@ function validate(view: ViewType) { return true } +let sortable: Sortable + function onSortStart(evt: SortableEvent) { evt.stopImmediatePropagation() evt.preventDefault() @@ -138,8 +140,6 @@ async function onSortEnd(evt: SortableEvent) { $e('a:view:reorder') } -let sortable: Sortable - const initSortable = (el: HTMLElement) => { if (sortable) sortable.destroy() @@ -199,7 +199,7 @@ async function onRename(view: ViewType, originalTitle?: string, undo = false) { }, args: [view, originalTitle], }, - scope: activeView.value?.fk_model_id, + scope: defineModelScope({ view: activeView.value }), }) } diff --git a/packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue b/packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue index 0e2fd63cbd..d3d6e32024 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue @@ -57,7 +57,7 @@ const { const { eventBus } = useSmartsheetStoreOrThrow() -const { addUndo } = useUndoRedo() +const { addUndo, defineViewScope } = useUndoRedo() eventBus.on((event) => { if (event === SmartsheetStoreEvents.FIELD_RELOAD) { @@ -117,7 +117,7 @@ const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }, undo }, args: [], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) } @@ -220,7 +220,7 @@ const toggleFieldVisibility = (e: CheckboxChangeEvent, field: any, index: number }, args: [e.target.checked], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) saveOrUpdate(field, index) } @@ -239,7 +239,7 @@ const toggleSystemFields = (e: CheckboxChangeEvent) => { }, args: [e.target.checked], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) } @@ -257,7 +257,7 @@ const onShowAll = () => { }, args: [], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) showAll() } @@ -276,7 +276,7 @@ const onHideAll = () => { }, args: [], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) hideAll() } diff --git a/packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue b/packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue index 1595d667e2..24f65295d4 100644 --- a/packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue +++ b/packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue @@ -12,7 +12,7 @@ const isLocked = inject(IsLockedInj, ref(false)) const { $api } = useNuxtApp() -const { addUndo } = useUndoRedo() +const { addUndo, defineViewScope } = useUndoRedo() const open = ref(false) @@ -30,7 +30,7 @@ const updateRowHeight = async (rh: number, undo = false) => { fn: (r: number) => updateRowHeight(r, true), args: [(view.value.view as GridType).row_height || 0], }, - scope: view.value?.title, + scope: defineViewScope({ view: view.value }), }) } diff --git a/packages/nc-gui/composables/useExpandedFormStore.ts b/packages/nc-gui/composables/useExpandedFormStore.ts index e60e296898..4b521ed111 100644 --- a/packages/nc-gui/composables/useExpandedFormStore.ts +++ b/packages/nc-gui/composables/useExpandedFormStore.ts @@ -52,7 +52,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m const { sharedView } = useSharedView() - const { addUndo, clone } = useUndoRedo() + const { addUndo, clone, defineViewScope } = useUndoRedo() const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) @@ -198,7 +198,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m }, args: [id], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) } @@ -250,7 +250,7 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m }, args: [id, clone(undoObject)], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) } diff --git a/packages/nc-gui/composables/useGridViewColumnWidth.ts b/packages/nc-gui/composables/useGridViewColumnWidth.ts index 2cf7809353..4e7786c250 100644 --- a/packages/nc-gui/composables/useGridViewColumnWidth.ts +++ b/packages/nc-gui/composables/useGridViewColumnWidth.ts @@ -22,7 +22,7 @@ export function useGridViewColumnWidth(view: Ref) { const { metas } = useMetas() - const { addUndo } = useUndoRedo() + const { addUndo, defineViewScope } = useUndoRedo() const gridViewCols = ref>({}) const resizingCol = ref('') @@ -78,7 +78,7 @@ export function useGridViewColumnWidth(view: Ref) { fn: (w: string) => updateWidth(id, w, true), args: [gridViewCols.value[id].width], }, - scope: view.value?.title, + scope: defineViewScope({ view: view.value }), }) } diff --git a/packages/nc-gui/composables/useKanbanViewStore.ts b/packages/nc-gui/composables/useKanbanViewStore.ts index 06399e981f..e2fa224ac0 100644 --- a/packages/nc-gui/composables/useKanbanViewStore.ts +++ b/packages/nc-gui/composables/useKanbanViewStore.ts @@ -60,7 +60,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( const { search } = useFieldQuery() - const { addUndo, clone } = useUndoRedo() + const { addUndo, clone, defineViewScope } = useUndoRedo() // save history of stack changes for undo/redo const moveHistory = ref<{ op: 'added' | 'removed'; pk: string; stack: string; index: number }[]>([]) @@ -367,7 +367,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( }, args: [id], }, - scope: viewMeta.value?.title, + scope: defineViewScope({ view: viewMeta.value as ViewType }), }) formattedData.value.get(null)?.splice(rowIndex ?? 0, 1, { @@ -444,7 +444,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( }, args: [clone(toUpdate), property], }, - scope: viewMeta.value?.title, + scope: defineViewScope({ view: viewMeta.value as ViewType }), }) /** update row data(to sync formula and other related columns) */ @@ -650,7 +650,7 @@ const [useProvideKanbanViewStore, useKanbanViewStore] = useInjectionState( }, args: [clone(row)], }, - scope: viewMeta.value?.title, + scope: defineViewScope({ view: viewMeta.value as ViewType }), }) } diff --git a/packages/nc-gui/composables/useLTARStore.ts b/packages/nc-gui/composables/useLTARStore.ts index f0d19a82bf..bdf47af6f9 100644 --- a/packages/nc-gui/composables/useLTARStore.ts +++ b/packages/nc-gui/composables/useLTARStore.ts @@ -45,7 +45,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState( const activeView = inject(ActiveViewInj, ref()) - const { addUndo, clone } = useUndoRedo() + const { addUndo, clone, defineViewScope } = useUndoRedo() const sharedViewPassword = inject(SharedViewPasswordInj, ref(null)) @@ -286,7 +286,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState( fn: (row: Record) => link(row, {}, true), args: [clone(row)], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) } } catch (e: any) { @@ -332,7 +332,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState( fn: (row: Record) => unlink(row, {}, true), args: [clone(row)], }, - scope: activeView.value?.title, + scope: defineViewScope({ view: activeView.value }), }) } } catch (e: any) { diff --git a/packages/nc-gui/composables/useUndoRedo.ts b/packages/nc-gui/composables/useUndoRedo.ts index b88b6c8eea..08e22e3a0a 100644 --- a/packages/nc-gui/composables/useUndoRedo.ts +++ b/packages/nc-gui/composables/useUndoRedo.ts @@ -1,5 +1,6 @@ import type { Ref } from 'vue' import rfdc from 'rfdc' +import type { ProjectType, TableType, ViewType } from 'nocodb-sdk' import { createSharedComposable, ref, useRouter } from '#imports' import type { UndoRedoAction } from '~/lib' @@ -10,33 +11,37 @@ export const useUndoRedo = createSharedComposable(() => { const route = $(router.currentRoute) - const activeView = inject(ActiveViewInj, ref()) - - const scope = computed(() => { - let tempScope = ['root'] - for (const param of Object.values(route.params)) { + // keys: projectType | projectId | type | title | viewTitle + const scope = computed<{ key: string; param: string }[]>(() => { + const tempScope: { key: string; param: string }[] = [{ key: 'root', param: 'root' }] + for (const [key, param] of Object.entries(route.params)) { if (Array.isArray(param)) { - tempScope = tempScope.concat(param) + tempScope.push({ key, param: param.join(',') }) } else { - tempScope.push(param) + tempScope.push({ key, param }) } } - // if the current view is the default view, add it to the scope (as viewTitle might be missing) - if (activeView.value?.is_default) { - tempScope.push(activeView.value.title) - } - return tempScope }) + const isSameScope = (sc: { key: string; param: string }[]) => { + return sc.every((s) => { + return scope.value.some( + // viewTitle is optional for default view + (s2) => + (s.key === 'viewTitle' && s2.key === 'viewTitle' && s2.param === '') || (s.key === s2.key && s.param === s2.param), + ) + }) + } + const undoQueue: Ref = ref([]) const redoQueue: Ref = ref([]) const addUndo = (action: UndoRedoAction, fromRedo = false) => { // remove all redo actions that are in the same scope - if (!fromRedo) redoQueue.value = redoQueue.value.filter((a) => a.scope !== action.scope) + if (!fromRedo) redoQueue.value = redoQueue.value.filter((a) => !isSameScope(a.scope || [])) undoQueue.value.push(action) } @@ -47,16 +52,10 @@ export const useUndoRedo = createSharedComposable(() => { const undo = async () => { let actionIndex = -1 for (let i = undoQueue.value.length - 1; i >= 0; i--) { - if (Array.isArray(undoQueue.value[i].scope)) { - if (scope.value.some((s) => undoQueue.value[i].scope?.includes(s))) { - actionIndex = i - break - } - } else { - if (scope.value.includes((undoQueue.value[i].scope as string) || 'root')) { - actionIndex = i - break - } + const elScope = undoQueue.value[i].scope || [{ key: 'root', param: 'root' }] + if (isSameScope(elScope)) { + actionIndex = i + break } } @@ -76,16 +75,10 @@ export const useUndoRedo = createSharedComposable(() => { const redo = async () => { let actionIndex = -1 for (let i = redoQueue.value.length - 1; i >= 0; i--) { - if (Array.isArray(redoQueue.value[i].scope)) { - if (scope.value.some((s) => redoQueue.value[i].scope?.includes(s))) { - actionIndex = i - break - } - } else { - if (scope.value.includes((redoQueue.value[i].scope as string) || 'root')) { - actionIndex = i - break - } + const elScope = redoQueue.value[i].scope || [{ key: 'root', param: 'root' }] + if (isSameScope(elScope)) { + actionIndex = i + break } } @@ -102,6 +95,57 @@ export const useUndoRedo = createSharedComposable(() => { } } + const defineRootScope = () => { + return [{ key: 'root', param: 'root' }] + } + + const defineProjectScope = (param: { project?: ProjectType; model?: TableType; view?: ViewType; project_id?: string }) => { + if (param.project) { + return [{ key: 'projectId', param: param.project.id! }] + } else if (param.model) { + return [{ key: 'projectId', param: param.model.project_id! }] + } else if (param.view) { + return [{ key: 'projectId', param: param.view.project_id! }] + } else { + return [{ key: 'projectId', param: param.project_id! }] + } + } + + const defineModelScope = (param: { model?: TableType; view?: ViewType; project_id?: string; model_id?: string }) => { + if (param.model) { + return [ + { key: 'projectId', param: param.model.project_id! }, + { key: 'title', param: param.model.id! }, + ] + } else if (param.view) { + return [ + { key: 'projectId', param: param.view.project_id! }, + { key: 'title', param: param.view.fk_model_id! }, + ] + } else { + return [ + { key: 'projectId', param: param.project_id! }, + { key: 'title', param: param.model_id! }, + ] + } + } + + const defineViewScope = (param: { view?: ViewType; project_id?: string; model_id?: string; title?: string }) => { + if (param.view) { + return [ + { key: 'projectId', param: param.view.project_id! }, + { key: 'title', param: param.view.fk_model_id! }, + { key: 'viewTitle', param: param.view.title! }, + ] + } else { + return [ + { key: 'projectId', param: param.project_id! }, + { key: 'title', param: param.model_id! }, + { key: 'viewTitle', param: param.title! }, + ] + } + } + useEventListener(document, 'keydown', async (e: KeyboardEvent) => { const cmdOrCtrl = isMac() ? e.metaKey : e.ctrlKey if (cmdOrCtrl && !e.altKey) { @@ -128,5 +172,9 @@ export const useUndoRedo = createSharedComposable(() => { addUndo, undo, clone, + defineRootScope, + defineProjectScope, + defineModelScope, + defineViewScope, } }) diff --git a/packages/nc-gui/composables/useViewData.ts b/packages/nc-gui/composables/useViewData.ts index e230380d69..0776603b5c 100644 --- a/packages/nc-gui/composables/useViewData.ts +++ b/packages/nc-gui/composables/useViewData.ts @@ -56,7 +56,7 @@ export function useViewData( const { getMeta } = useMetas() - const { addUndo, clone } = useUndoRedo() + const { addUndo, clone, defineViewScope } = useUndoRedo() const appInfoDefaultLimit = appInfo.defaultLimit || 25 @@ -303,7 +303,7 @@ export function useViewData( }, args: [id], }, - scope: viewMeta.value?.title, + scope: defineViewScope({ view: viewMeta.value }), }) Object.assign(currentRow, { @@ -402,7 +402,7 @@ export function useViewData( }, args: [clone(toUpdate), property, { page: paginationData.value.page, pageSize: paginationData.value.pageSize }], }, - scope: viewMeta.value?.title, + scope: defineViewScope({ view: viewMeta.value }), }) /** update row data(to sync formula and other related columns) @@ -534,7 +534,7 @@ export function useViewData( }, args: [clone(row), {}, { page: paginationData.value.page, pageSize: paginationData.value.pageSize }], }, - scope: viewMeta.value?.title, + scope: defineViewScope({ view: viewMeta.value }), }) } diff --git a/packages/nc-gui/lib/types.ts b/packages/nc-gui/lib/types.ts index daf81406e4..d2c06f44d1 100644 --- a/packages/nc-gui/lib/types.ts +++ b/packages/nc-gui/lib/types.ts @@ -108,5 +108,5 @@ export type Nullable = { [K in keyof T]: T[K] | null } export interface UndoRedoAction { undo: { fn: Function; args: any[] } redo: { fn: Function; args: any[] } - scope?: string | string[] + scope?: { key: string; param: string }[] }