Browse Source

Merge pull request #4504 from nocodb/feat/improved-field-menu

Feat: Grid - Add advanced options in column header menu
pull/4545/head
navi 2 years ago committed by GitHub
parent
commit
e0e84987fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      packages/nc-gui/assets/style.scss
  2. 5
      packages/nc-gui/components.d.ts
  3. 2
      packages/nc-gui/components/account/SignupSettings.vue
  4. 26
      packages/nc-gui/components/smartsheet/Grid.vue
  5. 9
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  6. 5
      packages/nc-gui/components/smartsheet/column/EditOrAddProvider.vue
  7. 36
      packages/nc-gui/components/smartsheet/header/Cell.vue
  8. 191
      packages/nc-gui/components/smartsheet/header/Menu.vue
  9. 29
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  10. 10
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  11. 4
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue
  12. 16
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  13. 11
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  14. 6
      packages/nc-gui/composables/useColumnCreateStore.ts
  15. 6
      packages/nc-gui/composables/useSmartsheetStore.ts
  16. 1
      packages/nc-gui/lang/en.json
  17. 8
      packages/nc-gui/lib/enums.ts
  18. 16
      packages/nc-gui/utils/columnUtils.ts
  19. 196
      packages/nocodb-sdk/src/lib/Api.ts
  20. 50
      packages/nocodb/src/lib/meta/api/columnApis.ts
  21. 6
      packages/nocodb/src/lib/meta/api/sortApis.ts
  22. 7
      packages/nocodb/src/lib/models/Column.ts
  23. 8
      packages/nocodb/src/lib/models/GridViewColumn.ts
  24. 71
      packages/nocodb/src/lib/models/Sort.ts
  25. 42
      packages/nocodb/src/lib/models/View.ts
  26. 437
      scripts/sdk/swagger.json

5
packages/nc-gui/assets/style.scss

@ -86,6 +86,11 @@ a {
@apply relative after:(absolute top-[-2px] right-[-2px] w-[8px] h-[8px] rounded-full bg-primary content-[''] !z-20);
}
// badge with count
.nc-count-badge {
@apply absolute flex items-center top-[-6px] right-[-6px] px-1 min-w-[14px] h-[14px] rounded-full bg-primary bg-opacity-100 text-white !text-[9px] !z-21;
}
// for highlighting toolbar menu item
.nc-active-btn > .ant-btn {
@apply bg-primary bg-opacity-20 hover:(bg-primary bg-opacity-20);

5
packages/nc-gui/components.d.ts vendored

@ -168,6 +168,7 @@ declare module '@vue/runtime-core' {
MdiFileEyeOutline: typeof import('~icons/mdi/file-eye-outline')['default']
MdiFileImageBox: typeof import('~icons/mdi/file-image-box')['default']
MdiFilePlusOutline: typeof import('~icons/mdi/file-plus-outline')['default']
MdiFileReplaceOutline: typeof import('~icons/mdi/file-replace-outline')['default']
MdiFileUploadOutline: typeof import('~icons/mdi/file-upload-outline')['default']
MdiFilterOutline: typeof import('~icons/mdi/filter-outline')['default']
MdiFlag: typeof import('~icons/mdi/flag')['default']
@ -210,11 +211,15 @@ declare module '@vue/runtime-core' {
MdiShieldKeyOutline: typeof import('~icons/mdi/shield-key-outline')['default']
MdiSlack: typeof import('~icons/mdi/slack')['default']
MdiSort: typeof import('~icons/mdi/sort')['default']
MdiSortAscending: typeof import('~icons/mdi/sort-ascending')['default']
MdiSortDescending: typeof import('~icons/mdi/sort-descending')['default']
MdiStar: typeof import('~icons/mdi/star')['default']
MdiStarOutline: typeof import('~icons/mdi/star-outline')['default']
MdiStorefrontOutline: typeof import('~icons/mdi/storefront-outline')['default']
MdiTable: typeof import('~icons/mdi/table')['default']
MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default']
MdiTableColumnPlusAfter: typeof import('~icons/mdi/table-column-plus-after')['default']
MdiTableColumnPlusBefore: typeof import('~icons/mdi/table-column-plus-before')['default']
MdiTableLarge: typeof import('~icons/mdi/table-large')['default']
MdiText: typeof import('~icons/mdi/text')['default']
MdiThumbUp: typeof import('~icons/mdi/thumb-up')['default']

2
packages/nc-gui/components/account/SignupSettings.vue

@ -51,6 +51,6 @@ loadSettings()
<style scoped>
:deep(.ant-checkbox-wrapper) {
@apply !flex-row-reverse !flex !justify-start gap-4;
justify-content: start;
justify-content: flex-start;
}
</style>

26
packages/nc-gui/components/smartsheet/Grid.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { ColumnType, TableType, ViewType } from 'nocodb-sdk'
import type { ColumnReqType, ColumnType, TableType, ViewType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import {
ActiveViewInj,
@ -11,11 +11,13 @@ import {
IsGridInj,
IsLockedInj,
MetaInj,
NavigateDir,
OpenNewRecordFormHookInj,
PaginationDataInj,
ReadonlyInj,
ReloadRowDataHookInj,
ReloadViewDataHookInj,
SmartsheetStoreEvents,
computed,
createEventHook,
enumColor,
@ -42,7 +44,6 @@ import {
watch,
} from '#imports'
import type { Row } from '~/lib'
import { NavigateDir } from '~/lib'
const { t } = useI18n()
@ -71,7 +72,7 @@ const isView = false
let editEnabled = $ref(false)
const { xWhere, isPkAvail, isSqlView } = useSmartsheetStoreOrThrow()
const { xWhere, isPkAvail, isSqlView, eventBus } = useSmartsheetStoreOrThrow()
const visibleColLength = $computed(() => fields.value?.length)
@ -544,6 +545,20 @@ watch(
},
{ immediate: true },
)
const columnOrder = ref<Pick<ColumnReqType, 'column_order'> | null>(null)
eventBus.on(async (event, payload) => {
if (event === SmartsheetStoreEvents.FIELD_ADD) {
columnOrder.value = payload
addColumnDropdown.value = true
}
})
const closeAddColumnDropdown = () => {
columnOrder.value = null
addColumnDropdown.value = false
}
</script>
<template>
@ -619,8 +634,9 @@ watch(
<template #overlay>
<SmartsheetColumnEditOrAddProvider
v-if="addColumnDropdown"
@submit="addColumnDropdown = false"
@cancel="addColumnDropdown = false"
:column-position="columnOrder"
@submit="closeAddColumnDropdown"
@cancel="closeAddColumnDropdown"
@click.stop
@keydown.stop
/>

9
packages/nc-gui/components/smartsheet/column/EditOrAdd.vue

@ -1,5 +1,5 @@
<script lang="ts" setup>
import { useEventListener } from '@vueuse/core'
import type { ColumnReqType } from 'nocodb-sdk'
import { UITypes, isVirtualCol } from 'nocodb-sdk'
import {
IsFormInj,
@ -13,6 +13,7 @@ import {
ref,
uiTypes,
useColumnCreateStoreOrThrow,
useEventListener,
useI18n,
useMetas,
useNuxtApp,
@ -22,6 +23,10 @@ import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
import MdiIdentifierIcon from '~icons/mdi/identifier'
const props = defineProps<{
columnPosition?: Pick<ColumnReqType, 'column_order'>
}>()
const emit = defineEmits(['submit', 'cancel'])
const { formState, generateNewColumnMeta, addOrUpdate, onAlter, onUidtOrIdTypeChange, validateInfos, isEdit } =
@ -71,7 +76,7 @@ const reloadMetaAndData = async () => {
}
async function onSubmit() {
const saved = await addOrUpdate(reloadMetaAndData)
const saved = await addOrUpdate(reloadMetaAndData, props.columnPosition)
if (!saved) return

5
packages/nc-gui/components/smartsheet/column/EditOrAddProvider.vue

@ -1,10 +1,11 @@
<script lang="ts" setup>
// todo: Remove this "Provider" component and use the "EditOrAdd" component directly
import type { ColumnType } from 'nocodb-sdk'
import type { ColumnReqType, ColumnType } from 'nocodb-sdk'
import { MetaInj, inject, ref, toRef, useProvideColumnCreateStore } from '#imports'
interface Props {
column?: ColumnType & { meta: any }
columnPosition?: Pick<ColumnReqType, 'column_order'>
}
const props = defineProps<Props>()
@ -19,5 +20,5 @@ useProvideColumnCreateStore(meta, column)
</script>
<template>
<SmartsheetColumnEditOrAdd @submit="emit('submit')" @cancel="emit('cancel')" />
<SmartsheetColumnEditOrAdd :column-position="props.columnPosition" @submit="emit('submit')" @cancel="emit('cancel')" />
</template>

36
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import type { ColumnReqType, ColumnType } from 'nocodb-sdk'
import { ColumnInj, IsFormInj, IsKanbanInj, inject, provide, ref, toRef, useUIPermission } from '#imports'
const props = defineProps<{ column: ColumnType & { meta: any }; required?: boolean | number; hideMenu?: boolean }>()
@ -17,6 +17,18 @@ const { isUIAllowed } = useUIPermission()
provide(ColumnInj, column)
const editColumnDropdown = ref(false)
const columnOrder = ref<Pick<ColumnReqType, 'column_order'> | null>(null)
const addField = async (payload) => {
columnOrder.value = payload
editColumnDropdown.value = true
}
const closeAddColumnDropdown = () => {
columnOrder.value = null
editColumnDropdown.value = false
}
</script>
<template>
@ -25,14 +37,25 @@ const editColumnDropdown = ref(false)
:class="{ 'h-full': column, '!text-gray-400': isKanban }"
>
<SmartsheetHeaderCellIcon v-if="column" />
<span v-if="column" class="name" style="white-space: nowrap" :title="column.title">{{ column.title }}</span>
<span
v-if="column"
class="name cursor-pointer"
style="white-space: nowrap"
:title="column.title"
@dblclick="editColumnDropdown = true"
>{{ column.title }}</span
>
<span v-if="(column.rqd && !column.cdf) || required" class="text-red-500">&nbsp;*</span>
<template v-if="!hideMenu">
<div class="flex-1" />
<LazySmartsheetHeaderMenu v-if="!isForm && isUIAllowed('edit-column')" @edit="editColumnDropdown = true" />
<LazySmartsheetHeaderMenu
v-if="!isForm && isUIAllowed('edit-column')"
@add-column="addField"
@edit="editColumnDropdown = true"
/>
</template>
<a-dropdown
@ -47,10 +70,11 @@ const editColumnDropdown = ref(false)
<template #overlay>
<SmartsheetColumnEditOrAddProvider
v-if="editColumnDropdown"
:column="column"
:column="columnOrder ? null : column"
:column-position="columnOrder"
class="w-full"
@submit="editColumnDropdown = false"
@cancel="editColumnDropdown = false"
@submit="closeAddColumnDropdown"
@cancel="closeAddColumnDropdown"
@click.stop
@keydown.stop
/>

191
packages/nc-gui/components/smartsheet/header/Menu.vue

@ -1,27 +1,40 @@
<script lang="ts" setup>
import type { LinkToAnotherRecordType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import {
ActiveViewInj,
ColumnInj,
IsLockedInj,
MetaInj,
Modal,
ReloadViewDataHookInj,
SmartsheetStoreEvents,
defineEmits,
defineProps,
extractSdkResponseErrorMsg,
getUniqueColumnName,
inject,
message,
useI18n,
useMetas,
useNuxtApp,
useSmartsheetStoreOrThrow,
} from '#imports'
const { virtual = false } = defineProps<{ virtual?: boolean }>()
const emit = defineEmits(['edit'])
const emit = defineEmits(['edit', 'addColumn'])
const { eventBus } = useSmartsheetStoreOrThrow()
const column = inject(ColumnInj)
const reloadDataHook = inject(ReloadViewDataHookInj)
const meta = inject(MetaInj, ref())
const view = inject(ActiveViewInj, ref())
const isLocked = inject(IsLockedInj)
const { $api, $e } = useNuxtApp()
@ -49,7 +62,7 @@ const deleteColumn = () =>
}
$e('a:column:delete')
} catch (e: any) {
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
},
@ -69,6 +82,131 @@ const setAsPrimaryValue = async () => {
message.error(t('msg.error.primaryColumnUpdateFailed'))
}
}
const sortByColumn = async (direction: 'asc' | 'desc') => {
try {
$e('a:sort:add', { from: 'column-menu' })
await $api.dbTableSort.create(view.value?.id as string, {
fk_column_id: column!.value.id,
direction,
push_to_top: true,
})
eventBus.emit(SmartsheetStoreEvents.SORT_RELOAD)
reloadDataHook?.trigger()
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const duplicateColumn = async () => {
let columnCreatePayload = {}
// generate duplicate column name
const duplicateColumnName = getUniqueColumnName(`${column!.value.title}_copy`, meta!.value!.columns!)
// construct column create payload
switch (column.value.uidt) {
case UITypes.LinkToAnotherRecord:
case UITypes.Lookup:
case UITypes.Rollup:
case UITypes.Formula:
return message.info('Not available at the moment')
case UITypes.SingleSelect:
case UITypes.MultiSelect:
columnCreatePayload = {
...column!.value!,
title: duplicateColumnName,
column_name: duplicateColumnName,
id: undefined,
order: undefined,
colOptions: {
options:
column.value.colOptions?.options?.map((option: Record<string, any>) => ({
...option,
id: undefined,
})) ?? [],
},
}
break
default:
columnCreatePayload = {
...column!.value!,
...(column!.value.colOptions ?? {}),
title: duplicateColumnName,
column_name: duplicateColumnName,
id: undefined,
colOptions: undefined,
order: undefined,
}
break
}
try {
const gridViewColumnList = await $api.dbViewColumn.list(view.value?.id as string)
const currentColumnIndex = gridViewColumnList.findIndex((f) => f.fk_column_id === column!.value.id)
let newColumnOrder
if (currentColumnIndex === gridViewColumnList.length - 1) {
newColumnOrder = gridViewColumnList[currentColumnIndex].order + 1
} else {
newColumnOrder = (gridViewColumnList[currentColumnIndex].order! + gridViewColumnList[currentColumnIndex + 1]?.order) / 2
}
await $api.dbTableColumn.create(meta!.value!.id!, {
...columnCreatePayload,
column_order: {
order: newColumnOrder,
view_id: view.value?.id as string,
},
})
await getMeta(meta!.value!.id!, true)
eventBus.emit(SmartsheetStoreEvents.FIELD_RELOAD)
message.success(t('msg.success.columnDuplicated'))
} catch (e) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
// add column before or after current column
const addColumn = async (before = false) => {
const gridViewColumnList = await $api.dbViewColumn.list(view.value?.id as string)
const currentColumnIndex = gridViewColumnList.findIndex((f) => f.fk_column_id === column!.value.id)
let newColumnOrder
if (before) {
if (currentColumnIndex === 0) {
newColumnOrder = gridViewColumnList[currentColumnIndex].order / 2
} else {
newColumnOrder = (gridViewColumnList[currentColumnIndex].order! + gridViewColumnList[currentColumnIndex - 1]?.order) / 2
}
} else {
if (currentColumnIndex === gridViewColumnList.length - 1) {
newColumnOrder = gridViewColumnList[currentColumnIndex].order + 1
} else {
newColumnOrder = (gridViewColumnList[currentColumnIndex].order! + gridViewColumnList[currentColumnIndex + 1]?.order) / 2
}
}
emit('addColumn', {
column_order: {
order: newColumnOrder,
view_id: view.value?.id as string,
},
})
}
// hide the field in view
const hideField = async () => {
const gridViewColumnList = await $api.dbViewColumn.list(view.value?.id as string)
const currentColumn = gridViewColumnList.find((f) => f.fk_column_id === column!.value.id)
await $api.dbViewColumn.update(view.value.id, currentColumn.id, { show: false })
eventBus.emit(SmartsheetStoreEvents.FIELD_RELOAD)
}
</script>
<template>
@ -84,6 +222,53 @@ const setAsPrimaryValue = async () => {
{{ $t('general.edit') }}
</div>
</a-menu-item>
<template v-if="column.uidt !== UITypes.LinkToAnotherRecord || column.colOptions.type !== RelationTypes.BELONGS_TO">
<a-divider class="!my-0" />
<a-menu-item @click="sortByColumn('asc')">
<div class="nc-column-insert-after nc-header-menu-item">
<MdiSortAscending class="text-primary" />
Sort Ascending
</div>
</a-menu-item>
<a-menu-item @click="sortByColumn('desc')">
<div class="nc-column-insert-before nc-header-menu-item">
<MdiSortDescending class="text-primary" />
Sort Descending
</div>
</a-menu-item>
</template>
<a-divider class="!my-0" />
<a-menu-item @click="hideField">
<div class="nc-column-insert-before nc-header-menu-item">
<MdiEyeOffOutline class="text-primary" />
Hide Field
</div>
</a-menu-item>
<a-divider class="!my-0" />
<a-menu-item
v-if="column.uidt !== UITypes.LinkToAnotherRecord && column.uidt !== UITypes.Lookup && !column.pk"
@click="duplicateColumn"
>
<div class="nc-column-duplicate nc-header-menu-item">
<MdiFileReplaceOutline class="text-primary" />
Duplicate
</div>
</a-menu-item>
<a-menu-item @click="addColumn()">
<div class="nc-column-insert-after nc-header-menu-item">
<MdiTableColumnPlusAfter class="text-primary" />
Insert After
</div>
</a-menu-item>
<a-menu-item @click="addColumn(true)">
<div class="nc-column-insert-before nc-header-menu-item">
<MdiTableColumnPlusBefore class="text-primary" />
Insert before
</div>
</a-menu-item>
<a-divider class="!my-0" />
<a-menu-item v-if="!virtual" @click="setAsPrimaryValue">
<div class="nc-column-set-primary nc-header-menu-item">

29
packages/nc-gui/components/smartsheet/header/VirtualCell.vue

@ -1,5 +1,5 @@
<script setup lang="ts">
import type { ColumnType, FormulaType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk'
import type { ColumnReqType, ColumnType, FormulaType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk'
import { substituteColumnIdWithAliasInFormula } from 'nocodb-sdk'
import {
ColumnInj,
@ -99,6 +99,18 @@ const tooltipMsg = computed(() => {
}
return ''
})
const columnOrder = ref<Pick<ColumnReqType, 'column_order'> | null>(null)
const addField = async (payload) => {
columnOrder.value = payload
editColumnDropdown.value = true
}
const closeAddColumnDropdown = () => {
columnOrder.value = null
editColumnDropdown.value = false
}
</script>
<template>
@ -117,7 +129,12 @@ const tooltipMsg = computed(() => {
<template v-if="!hideMenu">
<div class="flex-1" />
<LazySmartsheetHeaderMenu v-if="!isForm && isUIAllowed('edit-column')" :virtual="true" @edit="editColumnDropdown = true" />
<LazySmartsheetHeaderMenu
v-if="!isForm && isUIAllowed('edit-column')"
:virtual="true"
@add-column="addField"
@edit="editColumnDropdown = true"
/>
</template>
<a-dropdown
@ -128,14 +145,14 @@ const tooltipMsg = computed(() => {
overlay-class-name="nc-dropdown-edit-column"
>
<div />
<template #overlay>
<SmartsheetColumnEditOrAddProvider
v-if="editColumnDropdown"
:column="column"
:column="columnOrder ? null : column"
:column-position="columnOrder"
class="w-full"
@submit="editColumnDropdown = false"
@cancel="editColumnDropdown = false"
@submit="closeAddColumnDropdown"
@cancel="closeAddColumnDropdown"
@click.stop
@keydown.stop
/>

10
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -12,7 +12,7 @@ import QrCodeScan from '~icons/mdi/qrcode-scan'
import RollupIcon from '~icons/mdi/movie-roll'
import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before'
import MdiTextSearchVariant from '~icons/mdi/text-search-variant'
const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
switch (column.uidt) {
@ -35,13 +35,13 @@ const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
case UITypes.Lookup:
switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY:
return { icon: TableColumnPlusBefore, color: 'text-accent' }
return { icon: MdiTextSearchVariant, color: 'text-accent' }
case RelationTypes.HAS_MANY:
return { icon: TableColumnPlusBefore, color: 'text-yellow-500' }
return { icon: MdiTextSearchVariant, color: 'text-yellow-500' }
case RelationTypes.BELONGS_TO:
return { icon: TableColumnPlusBefore, color: 'text-sky-500' }
return { icon: MdiTextSearchVariant, color: 'text-sky-500' }
}
return { icon: TableColumnPlusBefore, color: 'text-grey' }
return { icon: MdiTextSearchVariant, color: 'text-grey' }
case UITypes.Rollup:
switch ((relationColumn?.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY:

4
packages/nc-gui/components/smartsheet/toolbar/ColumnFilterMenu.vue

@ -71,13 +71,15 @@ useMenuCloseOnEsc(open)
<template>
<a-dropdown v-model:visible="open" :trigger="['click']" overlay-class-name="nc-dropdown-filter-menu">
<div :class="{ 'nc-badge nc-active-btn': filtersLength }">
<div :class="{ 'nc-active-btn': filtersLength }">
<a-button v-e="['c:filter']" class="nc-filter-menu-btn nc-toolbar-btn txt-sm" :disabled="isLocked">
<div class="flex items-center gap-1">
<MdiFilterOutline />
<!-- Filter -->
<span class="text-capitalize !text-sm font-weight-normal">{{ $t('activity.filter') }}</span>
<MdiMenuDown class="text-grey" />
<span v-if="filtersLength" class="nc-count-badge">{{ filtersLength }}</span>
</div>
</a-button>
</div>

16
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -16,6 +16,7 @@ import {
resolveComponent,
useMenuCloseOnEsc,
useNuxtApp,
useSmartsheetStoreOrThrow,
useViewColumns,
watch,
} from '#imports'
@ -46,8 +47,17 @@ const {
hideAll,
saveOrUpdate,
metaColumnById,
loadViewColumns,
} = useViewColumns(activeView, meta, () => reloadDataHook.trigger())
const { eventBus } = useSmartsheetStoreOrThrow()
eventBus.on((event) => {
if (event === SmartsheetStoreEvents.FIELD_RELOAD) {
loadViewColumns()
}
})
watch(
sortedAndFilteredFields,
(v) => {
@ -56,7 +66,7 @@ watch(
{ immediate: true },
)
const isAnyFieldHidden = computed(() => filteredFieldList.value?.some((field) => !field.show))
const numberOfHiddenFields = computed(() => filteredFieldList.value?.filter((field) => !field.show)?.length)
const onMove = (_event: { moved: { newIndex: number } }) => {
// todo : sync with server
@ -128,7 +138,7 @@ useMenuCloseOnEsc(open)
<template>
<a-dropdown v-model:visible="open" :trigger="['click']" overlay-class-name="nc-dropdown-fields-menu">
<div :class="{ 'nc-badge nc-active-btn': isAnyFieldHidden }">
<div :class="{ 'nc-active-btn': numberOfHiddenFields }">
<a-button v-e="['c:fields']" class="nc-fields-menu-btn nc-toolbar-btn" :disabled="isLocked">
<div class="flex items-center gap-1">
<MdiEyeOffOutline />
@ -137,6 +147,8 @@ useMenuCloseOnEsc(open)
<span class="text-capitalize !text-sm font-weight-normal">{{ $t('objects.fields') }}</span>
<MdiMenuDown class="text-grey" />
<span v-if="numberOfHiddenFields" class="nc-count-badge">{{ numberOfHiddenFields }}</span>
</div>
</a-button>
</div>

11
packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue

@ -10,6 +10,7 @@ import {
inject,
ref,
useMenuCloseOnEsc,
useSmartsheetStoreOrThrow,
useViewSorts,
watch,
} from '#imports'
@ -19,8 +20,16 @@ const view = inject(ActiveViewInj, ref())
const isLocked = inject(IsLockedInj, ref(false))
const reloadDataHook = inject(ReloadViewDataHookInj)
const { eventBus } = useSmartsheetStoreOrThrow()
const { sorts, saveOrUpdate, loadSorts, addSort, deleteSort } = useViewSorts(view, () => reloadDataHook?.trigger())
eventBus.on((event) => {
if (event === SmartsheetStoreEvents.SORT_RELOAD) {
loadSorts()
}
})
const columns = computed(() => meta.value?.columns || [])
const columnByID = computed(() =>
@ -54,6 +63,8 @@ useMenuCloseOnEsc(open)
<!-- Sort -->
<span class="text-capitalize !text-sm font-weight-normal">{{ $t('activity.sort') }}</span>
<MdiMenuDown class="text-grey" />
<span v-if="sorts?.length" class="nc-count-badge">{{ sorts.length }}</span>
</div>
</a-button>
</div>

6
packages/nc-gui/composables/useColumnCreateStore.ts

@ -1,5 +1,5 @@
import clone from 'just-clone'
import type { ColumnType, TableType } from 'nocodb-sdk'
import type { ColumnReqType, ColumnType, TableType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import {
@ -191,7 +191,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
if (cdf) formState.value.cdf = formState.value.cdf || null
}
const addOrUpdate = async (onSuccess: () => void) => {
const addOrUpdate = async (onSuccess: () => void, columnPosition?: Pick<ColumnReqType, 'column_order'>) => {
try {
if (!(await validate())) return
} catch (e) {
@ -228,7 +228,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
// };
// }
}
await $api.dbTableColumn.create(meta.value?.id as string, formState.value)
await $api.dbTableColumn.create(meta.value?.id as string, { ...formState.value, ...columnPosition })
/** if LTAR column then force reload related table meta */
if (formState.value.uidt === UITypes.LinkToAnotherRecord && meta.value?.id !== formState.value.childId) {

6
packages/nc-gui/composables/useSmartsheetStore.ts

@ -1,7 +1,8 @@
import { ViewTypes } from 'nocodb-sdk'
import type { FilterType, KanbanType, SortType, TableType, ViewType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { computed, ref, unref, useFieldQuery, useInjectionState, useNuxtApp, useProject } from '#imports'
import { computed, ref, unref, useEventBus, useFieldQuery, useInjectionState, useNuxtApp, useProject } from '#imports'
import type { SmartsheetStoreEvents } from '~/lib'
const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
(
@ -19,6 +20,8 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
const { search } = useFieldQuery(view)
const eventBus = useEventBus<SmartsheetStoreEvents>(Symbol('SmartsheetStore'))
// getters
const isLocked = computed(() => view.value?.lock_type === 'locked')
const isPkAvail = computed(() => (meta.value as TableType)?.columns?.some((c) => c.pk))
@ -63,6 +66,7 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
sorts,
nestedFilters,
isSqlView,
eventBus,
}
},
'smartsheet-store',

1
packages/nc-gui/lang/en.json

@ -695,6 +695,7 @@
"futureRelease": "Coming soon!"
},
"success": {
"columnDuplicated": "Column duplicated successfully",
"updatedUIACL": "Updated UI ACL for tables successfully",
"pluginUninstalled": "Plugin uninstalled successfully",
"pluginSettingsSaved": "Plugin settings saved successfully",

8
packages/nc-gui/lib/enums.ts

@ -77,3 +77,11 @@ export enum TabType {
VIEW = 'view',
AUTH = 'auth',
}
export enum SmartsheetStoreEvents {
SORT_RELOAD = 'sort-reload',
FILTER_RELOAD = 'filter-reload',
DATA_RELOAD = 'data-reload',
FIELD_RELOAD = 'field-reload',
FIELD_ADD = 'field-add',
}

16
packages/nc-gui/utils/columnUtils.ts

@ -1,7 +1,6 @@
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import LinkVariant from '~icons/mdi/link-variant'
import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before'
import QrCodeScan from '~icons/mdi/qrcode-scan'
import FormatColorText from '~icons/mdi/format-color-text'
import TextSubject from '~icons/mdi/text-subject'
@ -28,6 +27,7 @@ import MovieRoll from '~icons/mdi/movie-roll'
import CalendarClock from '~icons/mdi/calendar-clock'
import ID from '~icons/mdi/identifier'
import RulerSquareCompass from '~icons/mdi/ruler-square-compass'
import MdiTextSearchVariant from '~icons/mdi/text-search-variant'
const uiTypes = [
{
@ -37,7 +37,7 @@ const uiTypes = [
},
{
name: UITypes.Lookup,
icon: TableColumnPlusBefore,
icon: MdiTextSearchVariant,
virtual: 1,
},
{
@ -180,4 +180,14 @@ const isColumnRequiredAndNull = (col: ColumnType, row: Record<string, any>) => {
return isColumnRequired(col) && (row[col.title!] === undefined || row[col.title!] === null)
}
export { uiTypes, getUIDTIcon, isColumnRequiredAndNull, isColumnRequired, isVirtualColRequired }
const getUniqueColumnName = (initName: string, columns: ColumnType[]) => {
let name = initName
let i = 1
while (columns.find((c) => c.title === name)) {
name = `${initName}_${i}`
i++
}
return name
}
export { uiTypes, getUIDTIcon, getUniqueColumnName, isColumnRequiredAndNull, isColumnRequired, isVirtualColRequired }

196
packages/nocodb-sdk/src/lib/Api.ts

@ -571,91 +571,109 @@ export interface HookLogType {
updated_at?: string;
}
export type ColumnReqType =
| {
uidt?:
| 'ID'
| 'SingleLineText'
| 'LongText'
| 'Attachment'
| 'Checkbox'
| 'MultiSelect'
| 'SingleSelect'
| 'Collaborator'
| 'Date'
| 'Year'
| 'Time'
| 'PhoneNumber'
| 'Email'
| 'URL'
| 'Number'
| 'Decimal'
| 'Currency'
| 'Percent'
| 'Duration'
| 'Rating'
| 'Count'
| 'DateTime'
| 'CreateTime'
| 'LastModifiedTime'
| 'AutoNumber'
| 'Geometry'
| 'JSON'
| 'SpecificDBType'
| 'Barcode'
| 'Button';
id?: string;
base_id?: string;
fk_model_id?: string;
title?: string;
dt?: string;
np?: string;
ns?: string;
clen?: string | number;
cop?: string;
pk?: boolean;
pv?: boolean;
rqd?: boolean;
column_name?: string;
un?: boolean;
ct?: string;
ai?: boolean;
unique?: boolean;
cdf?: string;
cc?: string;
csn?: string;
dtx?: string;
dtxp?: string;
dtxs?: string;
au?: boolean;
''?: string;
}
| {
uidt: 'LinkToAnotherRecord';
title: string;
parentId: string;
childId: string;
type: 'hm' | 'bt' | 'mm';
}
| {
uidt?: 'Rollup';
title?: string;
fk_relation_column_id?: string;
fk_rollup_column_id?: string;
rollup_function?: string;
}
| {
uidt?: 'Lookup';
title?: string;
fk_relation_column_id?: string;
fk_lookup_column_id?: string;
}
| {
uidt?: string;
formula_raw?: string;
formula?: string;
title?: string;
};
export interface NormalColumnRequestType {
uidt?:
| 'ID'
| 'SingleLineText'
| 'LongText'
| 'Attachment'
| 'Checkbox'
| 'MultiSelect'
| 'SingleSelect'
| 'Collaborator'
| 'Date'
| 'Year'
| 'Time'
| 'PhoneNumber'
| 'Email'
| 'URL'
| 'Number'
| 'Decimal'
| 'Currency'
| 'Percent'
| 'Duration'
| 'Rating'
| 'Count'
| 'DateTime'
| 'CreateTime'
| 'LastModifiedTime'
| 'AutoNumber'
| 'Geometry'
| 'JSON'
| 'SpecificDBType'
| 'Barcode'
| 'Button';
id?: string;
base_id?: string;
fk_model_id?: string;
title?: string;
dt?: string;
np?: string;
ns?: string;
clen?: string | number;
cop?: string;
pk?: boolean;
pv?: boolean;
rqd?: boolean;
column_name?: string;
un?: boolean;
ct?: string;
ai?: boolean;
unique?: boolean;
cdf?: string;
cc?: string;
csn?: string;
dtx?: string;
dtxp?: string;
dtxs?: string;
au?: boolean;
}
export interface LinkToAnotherColumnReqType {
uidt: 'LinkToAnotherRecord';
title: string;
virtual?: boolean;
parentId: string;
childId: string;
type: 'hm' | 'bt' | 'mm';
}
export interface RollupColumnReqType {
uidt?: 'Rollup';
title?: string;
fk_relation_column_id?: string;
fk_rollup_column_id?: string;
rollup_function?: string;
}
export interface LookupColumnReqType {
uidt?: 'Lookup';
title?: string;
fk_relation_column_id?: string;
fk_lookup_column_id?: string;
}
export interface FormulaColumnReqType {
uidt?: string;
formula_raw?: string;
formula?: string;
title?: string;
}
export type ColumnReqType = (
| NormalColumnRequestType
| LinkToAnotherColumnReqType
| RollupColumnReqType
| FormulaColumnReqType
| LookupColumnReqType
) & {
column_name?: string;
title?: string;
column_order?: {
view_id?: string;
order?: number;
};
};
export interface UserInfoType {
id?: string;
@ -2600,7 +2618,13 @@ export class Api<
* @request POST:/api/v1/db/meta/views/{viewId}/sorts
* @response `200` `void` OK
*/
create: (viewId: string, data: SortType, params: RequestParams = {}) =>
create: (
viewId: string,
data: SortType & {
push_to_top?: boolean;
},
params: RequestParams = {}
) =>
this.request<void, any>({
path: `/api/v1/db/meta/views/${viewId}/sorts`,
method: 'POST',

50
packages/nocodb/src/lib/meta/api/columnApis.ts

@ -15,9 +15,13 @@ import {
import {
AuditOperationSubTypes,
AuditOperationTypes,
ColumnReqType,
isVirtualCol,
LinkToAnotherColumnReqType,
LinkToAnotherRecordType,
LookupColumnReqType,
RelationTypes,
RollupColumnReqType,
substituteColumnAliasWithIdInFormula,
substituteColumnIdWithAliasInFormula,
TableType,
@ -96,7 +100,10 @@ async function createHmAndBtColumn(
}
}
export async function columnAdd(req: Request, res: Response<TableType>) {
export async function columnAdd(
req: Request<any, any, ColumnReqType & { uidt: UITypes }>,
res: Response<TableType>
) {
const table = await Model.getWithInfo({
id: req.params.tableId,
});
@ -121,7 +128,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
NcError.badRequest('Duplicate column alias');
}
let colBody = req.body;
let colBody: any = req.body;
switch (colBody.uidt) {
case UITypes.Rollup:
{
@ -137,7 +144,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
const relation = await (
await Column.get({
colId: req.body.fk_relation_column_id,
colId: (req.body as RollupColumnReqType).fk_relation_column_id,
})
).getColOptions<LinkToAnotherRecordType>();
@ -163,7 +170,8 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
const relatedTable = await relatedColumn.getModel();
if (
!(await relatedTable.getColumns()).find(
(c) => c.id === req.body.fk_rollup_column_id
(c) =>
c.id === (req.body as RollupColumnReqType).fk_rollup_column_id
)
)
throw new Error('Rollup column not found in related table');
@ -183,7 +191,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
const relation = await (
await Column.get({
colId: req.body.fk_relation_column_id,
colId: (req.body as LookupColumnReqType).fk_relation_column_id,
})
).getColOptions<LinkToAnotherRecordType>();
@ -209,7 +217,8 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
const relatedTable = await relatedColumn.getModel();
if (
!(await relatedTable.getColumns()).find(
(c) => c.id === req.body.fk_lookup_column_id
(c) =>
c.id === (req.body as LookupColumnReqType).fk_lookup_column_id
)
)
throw new Error('Lookup column not found in related table');
@ -227,14 +236,21 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
validateParams(['parentId', 'childId', 'type'], req.body);
// get parent and child models
const parent = await Model.getWithInfo({ id: req.body.parentId });
const child = await Model.getWithInfo({ id: req.body.childId });
const parent = await Model.getWithInfo({
id: (req.body as LinkToAnotherColumnReqType).parentId,
});
const child = await Model.getWithInfo({
id: (req.body as LinkToAnotherColumnReqType).childId,
});
let childColumn: Column;
const sqlMgr = await ProjectMgrv2.getSqlMgr({
id: base.project_id,
});
if (req.body.type === 'hm' || req.body.type === 'bt') {
if (
(req.body as LinkToAnotherColumnReqType).type === 'hm' ||
(req.body as LinkToAnotherColumnReqType).type === 'bt'
) {
// populate fk column name
const fkColName = getUniqueColumnName(
await child.getColumns(),
@ -285,7 +301,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
childColumn = await Column.get({ colId: id });
// ignore relation creation if virtual
if (!req.body.virtual) {
if (!(req.body as LinkToAnotherColumnReqType).virtual) {
// create relation
await sqlMgr.sqlOpPlus(base, 'relationCreate', {
childColumn: fkColName,
@ -315,11 +331,11 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
child,
parent,
childColumn,
req.body.type,
req.body.title,
req.body.virtual
(req.body as LinkToAnotherColumnReqType).type as RelationTypes,
(req.body as LinkToAnotherColumnReqType).title,
(req.body as LinkToAnotherColumnReqType).virtual
);
} else if (req.body.type === 'mm') {
} else if ((req.body as LinkToAnotherColumnReqType).type === 'mm') {
const aTn = `${project?.prefix ?? ''}_nc_m2m_${randomID()}`;
const aTnAlias = aTn;
@ -378,7 +394,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
columns: associateTableCols,
});
if (!req.body.virtual) {
if (!(req.body as LinkToAnotherColumnReqType).virtual) {
const rel1Args = {
...req.body,
childTable: aTn,
@ -412,7 +428,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
childCol,
null,
null,
req.body.virtual,
(req.body as LinkToAnotherColumnReqType).virtual,
true
);
await createHmAndBtColumn(
@ -421,7 +437,7 @@ export async function columnAdd(req: Request, res: Response<TableType>) {
parentCol,
null,
null,
req.body.virtual,
(req.body as LinkToAnotherColumnReqType).virtual,
true
);

6
packages/nocodb/src/lib/meta/api/sortApis.ts

@ -4,7 +4,7 @@ import Model from '../../models/Model';
import { Tele } from 'nc-help';
// @ts-ignore
import { PagedResponseImpl } from '../helpers/PagedResponse';
import { SortListType, TableReqType, TableType } from 'nocodb-sdk';
import { SortListType, SortType, TableType } from 'nocodb-sdk';
// @ts-ignore
import ProjectMgrv2 from '../../db/sql-mgr/v2/ProjectMgrv2';
// @ts-ignore
@ -28,11 +28,11 @@ export async function sortList(
}
// @ts-ignore
export async function sortCreate(req: Request<any, any, TableReqType>, res) {
export async function sortCreate(req: Request<any, any, SortType>, res) {
const sort = await Sort.insert({
...req.body,
fk_view_id: req.params.viewId,
});
} as Sort);
Tele.emit('evt', { evt_type: 'sort:created' });
res.json(sort);
}

7
packages/nocodb/src/lib/models/Column.ts

@ -5,7 +5,7 @@ import RollupColumn from './RollupColumn';
import SelectOption from './SelectOption';
import Model from './Model';
import NocoCache from '../cache/NocoCache';
import { AllowedColumnTypesForQrCode, ColumnType, UITypes } from 'nocodb-sdk';
import { ColumnReqType, AllowedColumnTypesForQrCode, ColumnType, UITypes } from 'nocodb-sdk';
import {
CacheDelDirection,
CacheGetType,
@ -74,7 +74,7 @@ export default class Column<T = any> implements ColumnType {
[key: string]: any;
fk_model_id: string;
uidt: UITypes | string;
},
} & Pick<ColumnReqType, 'column_order'>,
ncMeta = Noco.ncMeta
) {
if (!column.fk_model_id) NcError.badRequest('Missing model id');
@ -130,7 +130,6 @@ export default class Column<T = any> implements ColumnType {
}
if (!column.uidt) throw new Error('UI Datatype not found');
const order = 1;
const row = await ncMeta.metaInsert2(
null, //column.project_id || column.base_id,
null, //column.db_alias,
@ -152,8 +151,8 @@ export default class Column<T = any> implements ColumnType {
{
fk_column_id: row.id,
fk_model_id: column.fk_model_id,
order,
show: true,
column_order: column.column_order,
},
ncMeta
);

8
packages/nocodb/src/lib/models/GridViewColumn.ts

@ -70,9 +70,11 @@ export default class GridViewColumn implements GridColumnType {
const insertObj = {
fk_view_id: column.fk_view_id,
fk_column_id: column.fk_column_id,
order: await ncMeta.metaGetNextOrder(MetaTable.GRID_VIEW_COLUMNS, {
fk_view_id: column.fk_view_id,
}),
order:
column?.order ??
(await ncMeta.metaGetNextOrder(MetaTable.GRID_VIEW_COLUMNS, {
fk_view_id: column.fk_view_id,
})),
show: column.show,
project_id: column.project_id,
base_id: column.base_id,

71
packages/nocodb/src/lib/models/Sort.ts

@ -34,18 +34,22 @@ export default class Sort {
});
}
public static async insert(sortObj: Partial<Sort>, ncMeta = Noco.ncMeta) {
public static async insert(
sortObj: Partial<Sort> & { push_to_top?: boolean },
ncMeta = Noco.ncMeta
) {
// todo: implement a generic function
const order =
(+(
await ncMeta
.knex(MetaTable.SORT)
.max('order', { as: 'order' })
.where({
fk_view_id: sortObj.fk_view_id,
})
.first()
)?.order || 0) + 1;
const order = sortObj.push_to_top
? 1
: (+(
await ncMeta
.knex(MetaTable.SORT)
.max('order', { as: 'order' })
.where({
fk_view_id: sortObj.fk_view_id,
})
.first()
)?.order || 0) + 1;
const insertObj = {
id: sortObj.id,
@ -62,20 +66,39 @@ export default class Sort {
insertObj.base_id = model.base_id;
}
const row = await ncMeta.metaInsert2(null, null, MetaTable.SORT, insertObj);
await NocoCache.appendToList(
CacheScope.SORT,
[sortObj.fk_view_id],
`${CacheScope.SORT}:${row.id}`
);
await NocoCache.appendToList(
CacheScope.SORT,
[sortObj.fk_column_id],
`${CacheScope.SORT}:${row.id}`
);
// increment existing order
if (sortObj.push_to_top) {
await ncMeta
.knex(MetaTable.SORT)
.where({
fk_view_id: sortObj.fk_view_id,
})
.increment('order', 1);
}
const row = await ncMeta.metaInsert2(null, null, MetaTable.SORT, insertObj);
if (sortObj.push_to_top) {
// todo: delete cache
const sortList = await ncMeta.metaList2(null, null, MetaTable.SORT, {
condition: { fk_view_id: sortObj.fk_view_id },
orderBy: {
order: 'asc',
},
});
await NocoCache.setList(CacheScope.SORT, [sortObj.fk_view_id], sortList);
} else {
await NocoCache.appendToList(
CacheScope.SORT,
[sortObj.fk_view_id],
`${CacheScope.SORT}:${row.id}`
);
await NocoCache.appendToList(
CacheScope.SORT,
[sortObj.fk_column_id],
`${CacheScope.SORT}:${row.id}`
);
}
return this.get(row.id, ncMeta);
}

42
packages/nocodb/src/lib/models/View.ts

@ -13,7 +13,13 @@ import GalleryView from './GalleryView';
import GridViewColumn from './GridViewColumn';
import Sort from './Sort';
import Filter from './Filter';
import { isSystemColumn, UITypes, ViewType, ViewTypes } from 'nocodb-sdk';
import {
ColumnReqType,
isSystemColumn,
UITypes,
ViewType,
ViewTypes,
} from 'nocodb-sdk';
import GalleryViewColumn from './GalleryViewColumn';
import FormViewColumn from './FormViewColumn';
import KanbanViewColumn from './KanbanViewColumn';
@ -442,9 +448,9 @@ export default class View implements ViewType {
param: {
fk_column_id: any;
fk_model_id: any;
order;
order?: number;
show;
},
} & Pick<ColumnReqType, 'column_order'>,
ncMeta = Noco.ncMeta
) {
const insertObj = {
@ -456,33 +462,21 @@ export default class View implements ViewType {
const views = await this.list(param.fk_model_id, ncMeta);
for (const view of views) {
const modifiedInsertObj = { ...insertObj, fk_view_id: view.id };
if (param.column_order?.view_id === view.id) {
modifiedInsertObj.order = param.column_order?.order;
}
switch (view.type) {
case ViewTypes.GRID:
await GridViewColumn.insert(
{
...insertObj,
fk_view_id: view.id,
},
ncMeta
);
await GridViewColumn.insert(modifiedInsertObj, ncMeta);
break;
case ViewTypes.GALLERY:
await GalleryViewColumn.insert(
{
...insertObj,
fk_view_id: view.id,
},
ncMeta
);
await GalleryViewColumn.insert(modifiedInsertObj, ncMeta);
break;
case ViewTypes.KANBAN:
await KanbanViewColumn.insert(
{
...insertObj,
fk_view_id: view.id,
},
ncMeta
);
await KanbanViewColumn.insert(modifiedInsertObj, ncMeta);
break;
}
}

437
scripts/sdk/swagger.json

@ -2286,7 +2286,19 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Sort"
"allOf": [
{
"$ref": "#/components/schemas/Sort"
},
{
"type": "object",
"properties": {
"push_to_top": {
"type": "boolean"
}
}
}
]
}
}
}
@ -8957,213 +8969,254 @@
}
}
},
"ColumnReq": {
"oneOf": [
{
"properties": {
"uidt": {
"type": "string",
"enum": [
"ID",
"SingleLineText",
"LongText",
"Attachment",
"Checkbox",
"MultiSelect",
"SingleSelect",
"Collaborator",
"Date",
"Year",
"Time",
"PhoneNumber",
"Email",
"URL",
"Number",
"Decimal",
"Currency",
"Percent",
"Duration",
"Rating",
"Count",
"DateTime",
"CreateTime",
"LastModifiedTime",
"AutoNumber",
"Geometry",
"JSON",
"SpecificDBType",
"Barcode",
"Button"
]
},
"id": {
"type": "string"
},
"base_id": {
"type": "string"
},
"fk_model_id": {
"type": "string"
},
"title": {
"type": "string"
},
"dt": {
"type": "string"
},
"np": {
"type": "string"
},
"ns": {
"type": "string"
},
"clen": {
"type": [
"string",
"integer"
]
},
"cop": {
"type": "string"
},
"pk": {
"type": "boolean"
},
"pv": {
"type": "boolean"
},
"rqd": {
"type": "boolean"
},
"column_name": {
"type": "string"
},
"un": {
"type": "boolean"
},
"ct": {
"type": "string"
},
"ai": {
"type": "boolean"
},
"unique": {
"type": "boolean"
},
"cdf": {
"type": "string"
},
"cc": {
"type": "string"
},
"csn": {
"type": "string"
},
"dtx": {
"type": "string"
},
"dtxp": {
"type": "string"
},
"dtxs": {
"type": "string"
},
"au": {
"type": "boolean"
},
"": {
"type": "string"
}
}
"NormalColumnRequest": {
"properties": {
"uidt": {
"type": "string",
"enum": [
"ID",
"SingleLineText",
"LongText",
"Attachment",
"Checkbox",
"MultiSelect",
"SingleSelect",
"Collaborator",
"Date",
"Year",
"Time",
"PhoneNumber",
"Email",
"URL",
"Number",
"Decimal",
"Currency",
"Percent",
"Duration",
"Rating",
"Count",
"DateTime",
"CreateTime",
"LastModifiedTime",
"AutoNumber",
"Geometry",
"JSON",
"SpecificDBType",
"Barcode",
"Button"
]
},
{
"properties": {
"uidt": {
"type": "string",
"enum": [
"LinkToAnotherRecord"
]
},
"title": {
"type": "string"
},
"parentId": {
"type": "string"
},
"childId": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"hm",
"bt",
"mm"
]
}
},
"required": [
"uidt",
"title",
"parentId",
"childId",
"type"
"id": {
"type": "string"
},
"base_id": {
"type": "string"
},
"fk_model_id": {
"type": "string"
},
"title": {
"type": "string"
},
"dt": {
"type": "string"
},
"np": {
"type": "string"
},
"ns": {
"type": "string"
},
"clen": {
"type": [
"string",
"integer"
]
},
{
"properties": {
"uidt": {
"type": "string",
"enum": [
"Rollup"
]
},
"title": {
"type": "string"
},
"fk_relation_column_id": {
"type": "string"
},
"fk_rollup_column_id": {
"type": "string"
},
"rollup_function": {
"type": "string"
}
}
"cop": {
"type": "string"
},
"pk": {
"type": "boolean"
},
"pv": {
"type": "boolean"
},
"rqd": {
"type": "boolean"
},
"column_name": {
"type": "string"
},
"un": {
"type": "boolean"
},
"ct": {
"type": "string"
},
"ai": {
"type": "boolean"
},
"unique": {
"type": "boolean"
},
"cdf": {
"type": "string"
},
"cc": {
"type": "string"
},
"csn": {
"type": "string"
},
"dtx": {
"type": "string"
},
"dtxp": {
"type": "string"
},
"dtxs": {
"type": "string"
},
"au": {
"type": "boolean"
}
}
},
"LinkToAnotherColumnReq": {
"properties": {
"uidt": {
"type": "string",
"enum": [
"LinkToAnotherRecord"
]
},
"title": {
"type": "string"
},
"virtual": {
"type": "boolean"
},
"parentId": {
"type": "string"
},
"childId": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"hm",
"bt",
"mm"
]
}
},
"required": [
"uidt",
"title",
"parentId",
"childId",
"type"
]
},
"RollupColumnReq": {
"properties": {
"uidt": {
"type": "string",
"enum": [
"Rollup"
]
},
"title": {
"type": "string"
},
"fk_relation_column_id": {
"type": "string"
},
"fk_rollup_column_id": {
"type": "string"
},
"rollup_function": {
"type": "string"
}
}
},
"LookupColumnReq": {
"properties": {
"uidt": {
"type": "string",
"enum": [
"Lookup"
]
},
"title": {
"type": "string"
},
"fk_relation_column_id": {
"type": "string"
},
"fk_lookup_column_id": {
"type": "string"
}
}
},
"FormulaColumnReq": {
"properties": {
"uidt": {
"type": "string"
},
"formula_raw": {
"type": "string"
},
"formula": {
"type": "string"
},
"title": {
"type": "string"
}
}
},
"ColumnReq": {
"allOf": [
{
"properties": {
"uidt": {
"type": "string",
"enum": [
"Lookup"
]
"oneOf": [
{
"$ref": "#/components/schemas/NormalColumnRequest"
},
"title": {
"type": "string"
{
"$ref": "#/components/schemas/LinkToAnotherColumnReq"
},
"fk_relation_column_id": {
"type": "string"
{
"$ref": "#/components/schemas/RollupColumnReq"
},
"fk_lookup_column_id": {
"type": "string"
{
"$ref": "#/components/schemas/FormulaColumnReq"
},
{
"$ref": "#/components/schemas/LookupColumnReq"
}
}
]
},
{
"type": "object",
"properties": {
"uidt": {
"type": "string"
},
"formula_raw": {
"type": "string"
},
"formula": {
"column_name": {
"type": "string"
},
"title": {
"type": "string"
},
"column_order": {
"type": "object",
"properties": {
"view_id": {
"type": "string"
},
"order": {
"type": "integer"
}
}
}
}
}

Loading…
Cancel
Save