mirror of https://github.com/nocodb/nocodb
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
502 lines
15 KiB
502 lines
15 KiB
import { |
|
type ColumnType, |
|
CommonAggregations, |
|
type GridColumnReqType, |
|
type GridColumnType, |
|
type MapType, |
|
type TableType, |
|
type ViewType, |
|
} from 'nocodb-sdk' |
|
import { ViewTypes, isHiddenCol, isSystemColumn } from 'nocodb-sdk' |
|
import type { ComputedRef, Ref } from 'vue' |
|
|
|
const [useProvideViewColumns, useViewColumns] = useInjectionState( |
|
( |
|
view: Ref<ViewType | undefined>, |
|
meta: Ref<TableType | undefined> | ComputedRef<TableType | undefined>, |
|
reloadData?: (params?: { shouldShowLoading?: boolean }) => void, |
|
isPublic = false, |
|
) => { |
|
const rootFields = ref<ColumnType[]>([]) |
|
|
|
const fields = ref<Field[]>() |
|
|
|
const fieldsMap = computed(() => |
|
(fields.value || []).reduce<Record<string, any>>((acc, curr) => { |
|
if (curr.fk_column_id) { |
|
acc[curr.fk_column_id] = curr |
|
} |
|
return acc |
|
}, {}), |
|
) |
|
|
|
const filterQuery = ref('') |
|
|
|
const { $api, $e } = useNuxtApp() |
|
|
|
const { isUIAllowed } = useRoles() |
|
|
|
const { isSharedBase } = storeToRefs(useBase()) |
|
|
|
const isViewColumnsLoading = ref(false) |
|
|
|
const { addUndo, defineViewScope } = useUndoRedo() |
|
|
|
const isLocalMode = computed(() => isPublic || !isUIAllowed('viewFieldEdit') || isSharedBase.value) |
|
|
|
const localChanges = ref<Record<string, Field>>({}) |
|
|
|
const isColumnViewEssential = (column: ColumnType) => { |
|
// TODO: consider at some point ti delegate this via a cleaner design pattern to view specific check logic |
|
// which could be inside of a view specific helper class (and generalized via an interface) |
|
// (on the other hand, the logic complexity is still very low atm - might be overkill) |
|
return view.value?.type === ViewTypes.MAP && (view.value?.view as MapType)?.fk_geo_data_col_id === column.id |
|
} |
|
|
|
const metaColumnById = computed<Record<string, ColumnType>>(() => { |
|
if (!meta.value?.columns) return {} |
|
|
|
return (meta.value.columns as ColumnType[]).reduce( |
|
(acc, curr) => ({ |
|
...acc, |
|
[curr.id!]: curr, |
|
}), |
|
{}, |
|
) as Record<string, ColumnType> |
|
}) |
|
|
|
const gridViewCols = ref<Record<string, GridColumnType>>({}) |
|
|
|
const loadViewColumns = async () => { |
|
if (!meta || !view) return |
|
|
|
let order = 1 |
|
|
|
if (view.value?.id) { |
|
const data = (isPublic ? meta.value?.columns : (await $api.dbViewColumn.list(view.value.id)).list) as any[] |
|
|
|
const fieldById = data.reduce<Record<string, any>>((acc, curr) => { |
|
curr.show = !!curr.show |
|
|
|
return { |
|
...acc, |
|
[curr.fk_column_id]: curr, |
|
} |
|
}, {}) |
|
|
|
fields.value = meta.value?.columns |
|
?.filter((column: ColumnType) => { |
|
// filter created by and last modified by system columns |
|
if (isHiddenCol(column, meta.value)) return false |
|
return true |
|
}) |
|
.map((column: ColumnType) => { |
|
const currentColumnField = fieldById[column.id!] || {} |
|
|
|
return { |
|
title: column.title, |
|
fk_column_id: column.id, |
|
...currentColumnField, |
|
show: currentColumnField.show || isColumnViewEssential(currentColumnField), |
|
order: currentColumnField.order || order++, |
|
aggregation: currentColumnField?.aggregation ?? CommonAggregations.None, |
|
system: isSystemColumn(metaColumnById?.value?.[currentColumnField.fk_column_id!]), |
|
isViewEssentialField: isColumnViewEssential(column), |
|
initialShow: |
|
currentColumnField.show || |
|
isColumnViewEssential(currentColumnField) || |
|
(currentColumnField as GridColumnType)?.group_by, |
|
} |
|
}) |
|
.sort((a: Field, b: Field) => a.order - b.order) |
|
|
|
if (isLocalMode.value && fields.value) { |
|
for (const key in localChanges.value) { |
|
const fieldIndex = fields.value.findIndex((f) => f.fk_column_id === key) |
|
if (fieldIndex !== undefined && fieldIndex > -1) { |
|
fields.value[fieldIndex] = localChanges.value[key] |
|
fields.value = fields.value.sort((a: Field, b: Field) => a.order - b.order) |
|
} |
|
} |
|
} |
|
|
|
const colsData: GridColumnType[] = (isPublic.value ? view.value?.columns : fields.value) ?? [] |
|
|
|
gridViewCols.value = colsData.reduce<Record<string, GridColumnType>>( |
|
(o, col) => ({ |
|
...o, |
|
[col.fk_column_id as string]: col, |
|
}), |
|
{}, |
|
) |
|
} |
|
} |
|
|
|
const showAll = async (ignoreIds?: any) => { |
|
if (isLocalMode.value) { |
|
const fieldById = (fields.value || []).reduce<Record<string, any>>((acc, curr) => { |
|
if (curr.fk_column_id) { |
|
curr.show = !!curr.initialShow |
|
acc[curr.fk_column_id] = curr |
|
} |
|
return acc |
|
}, {}) |
|
|
|
fields.value = (fields.value || [])?.map((field: Field) => { |
|
const updateField = { |
|
...field, |
|
show: fieldById[field.fk_column_id!]?.show, |
|
} |
|
localChanges.value[field.fk_column_id!] = field |
|
return updateField |
|
}) |
|
|
|
meta.value!.columns = meta.value!.columns?.map((column: ColumnType) => { |
|
if (fieldById[column.id!]) { |
|
return { |
|
...column, |
|
...fieldById[column.id!], |
|
id: fieldById[column.id!].fk_column_id, |
|
} |
|
} |
|
return column |
|
}) |
|
|
|
reloadData?.() |
|
return |
|
} |
|
|
|
if (view?.value?.id) { |
|
if (ignoreIds) { |
|
await $api.dbView.showAllColumn(view.value.id, { |
|
ignoreIds, |
|
}) |
|
} else { |
|
await $api.dbView.showAllColumn(view.value.id) |
|
} |
|
} |
|
|
|
await loadViewColumns() |
|
reloadData?.() |
|
$e('a:fields:show-all') |
|
} |
|
const hideAll = async (ignoreIds?: any) => { |
|
if (isLocalMode.value) { |
|
const fieldById = (fields.value || []).reduce<Record<string, any>>((acc, curr) => { |
|
if (curr.fk_column_id) { |
|
curr.show = !!metaColumnById?.value?.[curr.fk_column_id!]?.pv || !!curr.isViewEssentialField |
|
acc[curr.fk_column_id] = curr |
|
} |
|
return acc |
|
}, {}) |
|
|
|
fields.value = (fields.value || [])?.map((field: Field) => { |
|
const updateField = { |
|
...field, |
|
show: fieldById[field.fk_column_id!]?.show, |
|
} |
|
localChanges.value[field.fk_column_id!] = field |
|
return updateField |
|
}) |
|
|
|
meta.value!.columns = meta.value!.columns?.map((column: ColumnType) => { |
|
if (fieldById[column.id!]) { |
|
return { |
|
...column, |
|
...fieldById[column.id!], |
|
id: fieldById[column.id!].fk_column_id, |
|
} |
|
} |
|
return column |
|
}) |
|
|
|
reloadData?.() |
|
return |
|
} |
|
if (view?.value?.id) { |
|
if (ignoreIds) { |
|
await $api.dbView.hideAllColumn(view.value.id, { |
|
ignoreIds, |
|
}) |
|
} else { |
|
await $api.dbView.hideAllColumn(view.value.id) |
|
} |
|
} |
|
|
|
await loadViewColumns() |
|
reloadData?.() |
|
$e('a:fields:show-all') |
|
} |
|
|
|
const saveOrUpdate = async ( |
|
field: any, |
|
index: number, |
|
disableDataReload: boolean = false, |
|
updateDefaultViewColumnOrder: boolean = false, |
|
) => { |
|
if (isLocalMode.value && fields.value) { |
|
fields.value[index] = field |
|
meta.value!.columns = meta.value!.columns?.map((column: ColumnType) => { |
|
if (column.id === field.fk_column_id) { |
|
return { |
|
...column, |
|
...field, |
|
id: field.fk_column_id, |
|
...(updateDefaultViewColumnOrder ? { meta: { ...parseProp(column.meta), defaultViewColOrder: field.order } } : {}), |
|
} |
|
} |
|
return column |
|
}) |
|
|
|
localChanges.value[field.fk_column_id] = field |
|
} |
|
|
|
if (isUIAllowed('viewFieldEdit')) { |
|
if (field.id && view?.value?.id) { |
|
await $api.dbViewColumn.update(view.value.id, field.id, field) |
|
} else if (view.value?.id) { |
|
const insertedField = (await $api.dbViewColumn.create(view.value.id, field)) as any |
|
|
|
/** update the field in fields if defined */ |
|
if (fields.value) fields.value[index] = insertedField |
|
|
|
return insertedField |
|
} |
|
} |
|
|
|
if (!disableDataReload) { |
|
await loadViewColumns() |
|
reloadData?.({ shouldShowLoading: false }) |
|
} |
|
} |
|
|
|
const showSystemFields = computed({ |
|
get() { |
|
return (view.value?.show_system_fields as boolean) || false |
|
}, |
|
set(v: boolean) { |
|
if (view?.value?.id) { |
|
if (!isLocalMode.value) { |
|
$api.dbView |
|
.update(view.value.id, { |
|
show_system_fields: v, |
|
}) |
|
.finally(() => { |
|
loadViewColumns() |
|
reloadData?.() |
|
}) |
|
} |
|
view.value.show_system_fields = v |
|
} |
|
$e('a:fields:system-fields') |
|
}, |
|
}) |
|
|
|
const filteredFieldList = computed(() => { |
|
return ( |
|
fields.value?.filter((field: Field) => { |
|
if (!field.initialShow && isLocalMode.value) { |
|
return false |
|
} |
|
|
|
if ( |
|
metaColumnById?.value?.[field.fk_column_id!]?.pv && |
|
(!filterQuery.value || field.title.toLowerCase().includes(filterQuery.value.toLowerCase())) |
|
) { |
|
return true |
|
} |
|
|
|
// hide system columns if not enabled |
|
if (!showSystemFields.value && isSystemColumn(metaColumnById?.value?.[field.fk_column_id!])) { |
|
return false |
|
} |
|
|
|
if (filterQuery.value === '') { |
|
return true |
|
} else { |
|
return field.title.toLowerCase().includes(filterQuery.value.toLowerCase()) |
|
} |
|
}) || [] |
|
) |
|
}) |
|
|
|
const numberOfHiddenFields = computed(() => { |
|
return (fields.value || []) |
|
?.filter((field: Field) => { |
|
if (!field.initialShow && isLocalMode.value) { |
|
return false |
|
} |
|
|
|
if (metaColumnById?.value?.[field.fk_column_id!]?.pv) { |
|
return true |
|
} |
|
|
|
// hide system columns if not enabled |
|
if (!showSystemFields.value && isSystemColumn(metaColumnById?.value?.[field.fk_column_id!])) { |
|
return false |
|
} |
|
|
|
return true |
|
}) |
|
.filter((field) => !field.show)?.length |
|
}) |
|
|
|
const sortedAndFilteredFields = computed<ColumnType[]>(() => { |
|
return (fields?.value |
|
?.filter((field: Field) => { |
|
// hide system columns if not enabled |
|
if ( |
|
!showSystemFields.value && |
|
metaColumnById.value && |
|
metaColumnById?.value?.[field.fk_column_id!] && |
|
isSystemColumn(metaColumnById.value?.[field.fk_column_id!]) && |
|
!metaColumnById.value?.[field.fk_column_id!]?.pv |
|
) { |
|
return false |
|
} |
|
return field.show && metaColumnById?.value?.[field.fk_column_id!] |
|
}) |
|
?.sort((a: Field, b: Field) => a.order - b.order) |
|
?.map((field: Field) => metaColumnById?.value?.[field.fk_column_id!]) || []) as ColumnType[] |
|
}) |
|
|
|
const toggleFieldVisibility = (checked: boolean, field: any) => { |
|
const fieldIndex = fields.value?.findIndex((f) => f.fk_column_id === field.fk_column_id) |
|
|
|
if (!fieldIndex && fieldIndex !== 0) return |
|
addUndo({ |
|
undo: { |
|
fn: (v: boolean) => { |
|
field.show = !v |
|
saveOrUpdate(field, fieldIndex) |
|
}, |
|
args: [checked], |
|
}, |
|
redo: { |
|
fn: (v: boolean) => { |
|
field.show = v |
|
saveOrUpdate(field, fieldIndex) |
|
}, |
|
args: [checked], |
|
}, |
|
scope: defineViewScope({ view: view.value }), |
|
}) |
|
saveOrUpdate(field, fieldIndex, !checked) |
|
} |
|
|
|
const toggleFieldStyles = (field: any, style: 'underline' | 'bold' | 'italic', status: boolean) => { |
|
const fieldIndex = fields.value?.findIndex((f) => f.fk_column_id === field.fk_column_id) |
|
if (!fieldIndex && fieldIndex !== 0) return |
|
field[style] = status |
|
$e('a:fields:style', { style, status }) |
|
saveOrUpdate(field, fieldIndex, true) |
|
} |
|
|
|
// reload view columns when active view changes |
|
// or when columns changes(delete/add) |
|
watch( |
|
[() => view?.value?.id, () => meta.value?.columns], |
|
async ([newViewId]) => { |
|
// reload only if view belongs to current table |
|
if (newViewId && view.value?.fk_model_id === meta.value?.id) { |
|
isViewColumnsLoading.value = true |
|
try { |
|
await loadViewColumns() |
|
} catch (e) { |
|
console.error(e) |
|
} |
|
isViewColumnsLoading.value = false |
|
} |
|
}, |
|
{ immediate: true }, |
|
) |
|
|
|
const resizingColOldWith = ref('180px') |
|
|
|
const updateGridViewColumn = async (id: string, props: Partial<GridColumnReqType>, undo = false) => { |
|
if (!undo) { |
|
const oldProps = Object.keys(props).reduce<Partial<GridColumnReqType>>((o: any, k) => { |
|
if (gridViewCols.value[id][k as keyof GridColumnType]) { |
|
if (k === 'width') o[k] = `${resizingColOldWith.value}px` |
|
else o[k] = gridViewCols.value[id][k as keyof GridColumnType] |
|
} |
|
return o |
|
}, {}) |
|
addUndo({ |
|
redo: { |
|
fn: (w: Partial<GridColumnReqType>) => updateGridViewColumn(id, w, true), |
|
args: [props], |
|
}, |
|
undo: { |
|
fn: (w: Partial<GridColumnReqType>) => updateGridViewColumn(id, w, true), |
|
args: [oldProps], |
|
}, |
|
scope: defineViewScope({ view: view.value }), |
|
}) |
|
} |
|
try { |
|
// sync with server if allowed |
|
if (!isPublic.value && isUIAllowed('viewFieldEdit') && gridViewCols.value[id]?.id) { |
|
await $api.dbView.gridColumnUpdate(gridViewCols.value[id].id as string, { |
|
...props, |
|
}) |
|
} |
|
|
|
if (gridViewCols.value?.[id]) { |
|
Object.assign(gridViewCols.value[id], { |
|
...gridViewCols.value[id], |
|
...props, |
|
}) |
|
} else { |
|
// fallback to reload |
|
await loadViewColumns() |
|
} |
|
} catch (e) { |
|
// this could happen if user doesn't have permission to update view columns |
|
// todo: find out root cause and handle with isUIAllowed |
|
console.error(e) |
|
} |
|
} |
|
|
|
watch( |
|
sortedAndFilteredFields, |
|
(v) => { |
|
if (rootFields) rootFields.value = v || [] |
|
}, |
|
{ immediate: true }, |
|
) |
|
|
|
provide(FieldsInj, rootFields) |
|
|
|
return { |
|
fields, |
|
fieldsMap, |
|
loadViewColumns, |
|
filteredFieldList, |
|
numberOfHiddenFields, |
|
filterQuery, |
|
showAll, |
|
hideAll, |
|
saveOrUpdate, |
|
sortedAndFilteredFields, |
|
showSystemFields, |
|
metaColumnById, |
|
toggleFieldVisibility, |
|
toggleFieldStyles, |
|
isViewColumnsLoading, |
|
updateGridViewColumn, |
|
gridViewCols, |
|
resizingColOldWith, |
|
isLocalMode, |
|
} |
|
}, |
|
'useViewColumnsOrThrow', |
|
) |
|
|
|
export { useProvideViewColumns } |
|
|
|
export function useViewColumnsOrThrow() { |
|
const viewColumns = useViewColumns() |
|
if (viewColumns == null) throw new Error('Please call `useProvideViewColumns` on the appropriate parent component') |
|
return viewColumns |
|
}
|
|
|