import type { ColumnType, SortType, ViewType } from 'nocodb-sdk' import type { Ref } from 'vue' import type { UndoRedoAction } from 'lib' import type { EventHook } from '@vueuse/core' import { IsPublicInj, ReloadViewDataHookInj, extractSdkResponseErrorMsg, inject, message, ref, storeToRefs, useNuxtApp, useProject, useSharedView, useSmartsheetStoreOrThrow, useUIPermission, } from '#imports' export function useViewSorts(view: Ref, reloadData?: () => void) { const { sharedView } = useSharedView() const { sorts, eventBus } = useSmartsheetStoreOrThrow() const { $api, $e } = useNuxtApp() const { isUIAllowed } = useUIPermission() const { isSharedBase } = storeToRefs(useProject()) const { addUndo, clone, defineViewScope } = useUndoRedo() const reloadHook = inject(ReloadViewDataHookInj) const isPublic = inject(IsPublicInj, ref(false)) const lastSorts = ref([]) watchOnce(sorts, (sorts: SortType[]) => { lastSorts.value = clone(sorts) }) const loadSorts = async () => { if (isPublic.value) { // todo: sorts missing on `ViewType` const sharedSorts = (sharedView.value as any)?.sorts || [] sorts.value = [...sharedSorts] return } try { if (!isUIAllowed('sortSync')) { return } if (!view?.value) return sorts.value = (await $api.dbTableSort.list(view.value!.id!)).list as SortType[] } catch (e: any) { console.error(e) message.error(await extractSdkResponseErrorMsg(e)) } } // get delta between two objects and return the changed fields (value is from b) const getDelta = (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 saveOrUpdate = async (sort: SortType, i: number, undo = false) => { if (!undo) { const lastSort = lastSorts.value[i] if (lastSort) { const delta = clone(getDelta(sort, lastSort)) if (Object.keys(delta).length > 0) { addUndo({ undo: { fn: (prop: string, data: any) => { const f = sorts.value[i] if (f) { f[prop] = data saveOrUpdate(f, i, true) } }, args: [Object.keys(delta)[0], Object.values(delta)[0]], }, redo: { fn: (prop: string, data: any) => { const f = sorts.value[i] if (f) { f[prop] = data saveOrUpdate(f, i, true) } }, args: [Object.keys(delta)[0], sort[Object.keys(delta)[0]]], }, scope: defineViewScope({ view: view.value }), }) } } } if (isPublic.value || isSharedBase.value) { sorts.value[i] = sort sorts.value = [...sorts.value] reloadHook?.trigger() return } try { if (isUIAllowed('sortSync')) { if (sort.id) { await $api.dbTableSort.update(sort.id, sort) $e('sort-updated') } else { sorts.value[i] = (await $api.dbTableSort.create(view.value?.id as string, sort)) as unknown as SortType } } reloadData?.() $e('a:sort:dir', { direction: sort.direction }) } catch (e: any) { console.error(e) message.error(await extractSdkResponseErrorMsg(e)) } lastSorts.value = clone(sorts.value) } const insertSort = async ({ direction, column, reloadDataHook, }: { direction: 'asc' | 'desc' column: ColumnType reloadDataHook?: EventHook | undefined }) => { try { $e('a:sort:add', { from: 'column-menu' }) const existingSortIndex = sorts.value.findIndex((s) => s.fk_column_id === column.id) const existingSort = existingSortIndex > -1 ? sorts.value[existingSortIndex] : undefined // Delete existing sort and not update the state as sort count in UI will change for a sec if (existingSort) { await $api.dbTableSort.delete(existingSort.id!) $e('a:sort:delete') } const data: any = await $api.dbTableSort.create(view.value?.id as string, { fk_column_id: column!.id, direction, push_to_top: true, }) sorts.value = [...sorts.value.filter((_, index) => index !== existingSortIndex), data as SortType] addUndo({ redo: { fn: async function redo(this: UndoRedoAction) { const data: any = await $api.dbTableSort.create(view.value?.id as string, { fk_column_id: column!.id, direction, push_to_top: true, }) this.undo.args = [data.id] eventBus.emit(SmartsheetStoreEvents.SORT_RELOAD) reloadDataHook?.trigger() }, args: [], }, undo: { fn: async function undo(id: string) { await $api.dbTableSort.delete(id) eventBus.emit(SmartsheetStoreEvents.SORT_RELOAD) reloadDataHook?.trigger() }, args: [data.id], }, scope: defineViewScope({ view: view.value }), }) eventBus.emit(SmartsheetStoreEvents.SORT_RELOAD) reloadDataHook?.trigger() } catch (e: any) { console.error(e) message.error(await extractSdkResponseErrorMsg(e)) } } async function deleteSort(sort: SortType, i: number, undo = false) { try { if (isUIAllowed('sortSync') && sort.id && !isPublic.value && !isSharedBase.value) { await $api.dbTableSort.delete(sort.id) } sorts.value.splice(i, 1) sorts.value = [...sorts.value] if (!undo) { addUndo({ redo: { fn: async () => { await deleteSort(sort, i, true) }, args: [], }, undo: { fn: () => { sorts.value.splice(i, 0, sort) saveOrUpdate(sort, i, true) }, args: [clone(sort), i], }, scope: defineViewScope({ view: view.value }), }) } lastSorts.value = clone(sorts.value) reloadHook?.trigger() $e('a:sort:delete') } catch (e: any) { console.error(e) message.error(await extractSdkResponseErrorMsg(e)) } } const addSort = (undo = false, column?: ColumnType) => { sorts.value = [ ...sorts.value, { fk_column_id: column?.id, direction: 'asc', }, ] $e('a:sort:add', { length: sorts?.value?.length }) if (!undo) { addUndo({ undo: { fn: async () => { await deleteSort(sorts.value[sorts.value.length - 1], sorts.value.length - 1, true) }, args: [], }, redo: { fn: () => { addSort(true) }, args: [], }, scope: defineViewScope({ view: view.value }), }) } lastSorts.value = clone(sorts.value) } return { sorts, loadSorts, addSort, deleteSort, saveOrUpdate, insertSort } }