Files
nocodb/packages/nc-gui/composables/useViewSorts.ts

355 lines
9.6 KiB
TypeScript

import type { ColumnType, SortType, ViewType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import type { EventHook } from '@vueuse/core'
import type { UndoRedoAction } from '~/lib/types'
export function useViewSorts(view: Ref<ViewType | undefined>, reloadData?: () => void) {
const { sorts, eventBus } = useSmartsheetStoreOrThrow()
const { $api, $e, $eventBus } = useNuxtApp()
const { isSharedBase } = storeToRefs(useBase())
const { hasPersonalViewPermission } = usePersonalViewPermissions(view)
const { addUndo, clone, defineViewScope } = useUndoRedo()
const canSyncSort = hasPersonalViewPermission('sortSync')
const canListSort = hasPersonalViewPermission('sortList')
const reloadHook = inject(ReloadViewDataHookInj)
const isPublic = inject(IsPublicInj, ref(false))
const meta = inject(MetaInj, ref())
const lastSorts = ref<SortType[]>([])
watchOnce(sorts, (sorts: SortType[]) => {
lastSorts.value = clone(sorts)
})
const loadSorts = async () => {
if (isPublic.value) {
sorts.value = []
return
}
// Wait for meta to be available before loading sorts (up to 5 seconds)
if (!meta.value && view?.value) {
await until(meta).toBeTruthy({ timeout: 5000 })
}
try {
if (!canListSort.value) {
return
}
if (!view?.value || !meta.value) return
sorts.value = (
await $api.internal.getOperation(meta.value.fk_workspace_id!, meta.value.base_id!, {
operation: 'sortList',
viewId: 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 (canSyncSort.value) {
if (sort.id) {
await $api.internal.postOperation(
meta.value!.fk_workspace_id!,
meta.value!.base_id!,
{
operation: 'sortUpdate',
sortId: sort.id,
},
sort,
)
$e('sort-updated')
} else {
sorts.value[i] = (await $api.internal.postOperation(
meta.value!.fk_workspace_id!,
meta.value!.base_id!,
{
operation: 'sortCreate',
viewId: 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<boolean | void> | 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
const isLocalMode = isPublic.value || isSharedBase.value || !canSyncSort.value
// Delete existing sort and not update the state as sort count in UI will change for a sec
if (existingSort && !isLocalMode) {
await $api.internal.postOperation(
meta.value!.fk_workspace_id!,
meta.value!.base_id!,
{
operation: 'sortDelete',
sortId: existingSort.id!,
},
{},
)
$e('a:sort:delete')
}
let data: any
if (isLocalMode) {
data = {
fk_column_id: column!.id,
direction,
}
} else {
data = await $api.internal.postOperation(
meta.value!.fk_workspace_id!,
meta.value!.base_id!,
{
operation: 'sortCreate',
viewId: 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]
if (!isLocalMode) {
addUndo({
redo: {
fn: async function redo(this: UndoRedoAction) {
const data: any = await $api.internal.postOperation(
meta.value!.fk_workspace_id!,
meta.value!.base_id!,
{
operation: 'sortCreate',
viewId: 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.internal.postOperation(
meta.value!.fk_workspace_id!,
meta.value!.base_id!,
{
operation: 'sortDelete',
sortId: 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 {
const isLocalMode = isPublic.value || isSharedBase.value || !canSyncSort.value
if (sort.id && !isLocalMode) {
await $api.internal.postOperation(
meta.value!.fk_workspace_id!,
meta.value!.base_id!,
{
operation: 'sortDelete',
sortId: 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)
}
const evtListener = (evt: string, payload: any) => {
if (payload.fk_view_id !== view.value?.id) return
if (evt === 'sort_create') {
sorts.value.push(payload)
reloadHook?.trigger()
} else if (evt === 'sort_update') {
const index = sorts.value.findIndex((s) => s.id === payload.id)
if (index !== -1) {
sorts.value[index] = payload
}
reloadHook?.trigger()
} else if (evt === 'sort_delete') {
sorts.value = sorts.value.filter((s) => s.id !== payload.id)
reloadHook?.trigger()
}
}
onMounted(() => {
$eventBus.realtimeViewMetaEventBus.on(evtListener)
})
onBeforeUnmount(() => {
$eventBus.realtimeViewMetaEventBus.off(evtListener)
})
return { sorts, loadSorts, addSort, deleteSort, saveOrUpdate, insertSort, canSyncSort }
}