Browse Source

Merge pull request #7406 from nocodb/develop

pull/7407/head 0.204.0
github-actions[bot] 11 months ago committed by GitHub
parent
commit
c3fc91116b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      packages/nc-cli/package-lock.json
  2. 43
      packages/nc-gui/components/account/UserList.vue
  3. 79
      packages/nc-gui/components/account/UserMenu.vue
  4. 6
      packages/nc-gui/components/cell/DatePicker.vue
  5. 7
      packages/nc-gui/components/cell/DateTimePicker.vue
  6. 2
      packages/nc-gui/components/cell/Decimal.vue
  7. 2
      packages/nc-gui/components/cell/Duration.vue
  8. 2
      packages/nc-gui/components/cell/MultiSelect.vue
  9. 2
      packages/nc-gui/components/cell/Percent.vue
  10. 22
      packages/nc-gui/components/cell/ReadOnlyDateTimePicker.vue
  11. 22
      packages/nc-gui/components/cell/ReadOnlyUser.vue
  12. 10
      packages/nc-gui/components/cell/SingleSelect.vue
  13. 6
      packages/nc-gui/components/cell/TimePicker.vue
  14. 2
      packages/nc-gui/components/cell/Url.vue
  15. 67
      packages/nc-gui/components/cell/User.vue
  16. 6
      packages/nc-gui/components/cell/YearPicker.vue
  17. 7
      packages/nc-gui/components/erd/utils.ts
  18. 66
      packages/nc-gui/components/project/AccessSettings.vue
  19. 13
      packages/nc-gui/components/smartsheet/Form.vue
  20. 4
      packages/nc-gui/components/smartsheet/Gallery.vue
  21. 4
      packages/nc-gui/components/smartsheet/Kanban.vue
  22. 5
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  23. 12
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  24. 14
      packages/nc-gui/components/smartsheet/column/FormulaOptions.vue
  25. 16
      packages/nc-gui/components/smartsheet/column/LookupOptions.vue
  26. 17
      packages/nc-gui/components/smartsheet/column/RollupOptions.vue
  27. 3
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  28. 147
      packages/nc-gui/components/smartsheet/details/Fields.vue
  29. 28
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  30. 41
      packages/nc-gui/components/smartsheet/grid/GroupBy.vue
  31. 12
      packages/nc-gui/components/smartsheet/grid/GroupByLabel.vue
  32. 22
      packages/nc-gui/components/smartsheet/grid/Table.vue
  33. 25
      packages/nc-gui/components/smartsheet/header/Menu.vue
  34. 6
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  35. 18
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  36. 8
      packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue
  37. 7
      packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue
  38. 17
      packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue
  39. 20
      packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue
  40. 2
      packages/nc-gui/components/tabs/Smartsheet.vue
  41. 13
      packages/nc-gui/components/template/Editor.vue
  42. 3
      packages/nc-gui/components/virtual-cell/Formula.vue
  43. 6
      packages/nc-gui/components/virtual-cell/Lookup.vue
  44. 7
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  45. 22
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  46. 6
      packages/nc-gui/components/virtual-cell/components/ListItems.vue
  47. 35
      packages/nc-gui/components/workspace/CollaboratorsList.vue
  48. 6
      packages/nc-gui/composables/useApi/interceptors.ts
  49. 12
      packages/nc-gui/composables/useColumnCreateStore.ts
  50. 63
      packages/nc-gui/composables/useData.ts
  51. 19
      packages/nc-gui/composables/useExpandedFormStore.ts
  52. 2
      packages/nc-gui/composables/useGlobal/index.ts
  53. 57
      packages/nc-gui/composables/useLTARStore.ts
  54. 4
      packages/nc-gui/composables/useMultiSelect/convertCellData.ts
  55. 20
      packages/nc-gui/composables/useMultiSelect/index.ts
  56. 2
      packages/nc-gui/composables/useSmartsheetStore.ts
  57. 177
      packages/nc-gui/composables/useUserSorts.ts
  58. 9
      packages/nc-gui/composables/useViewColumns.ts
  59. 14
      packages/nc-gui/composables/useViewData.ts
  60. 8
      packages/nc-gui/composables/useViewFilters.ts
  61. 12
      packages/nc-gui/composables/useViewGroupBy.ts
  62. 1
      packages/nc-gui/context/index.ts
  63. 3
      packages/nc-gui/lang/ar.json
  64. 3
      packages/nc-gui/lang/bn_IN.json
  65. 3
      packages/nc-gui/lang/cs.json
  66. 3
      packages/nc-gui/lang/da.json
  67. 209
      packages/nc-gui/lang/de.json
  68. 4
      packages/nc-gui/lang/en.json
  69. 3
      packages/nc-gui/lang/es.json
  70. 3
      packages/nc-gui/lang/eu.json
  71. 3
      packages/nc-gui/lang/fa.json
  72. 3
      packages/nc-gui/lang/fi.json
  73. 3
      packages/nc-gui/lang/fr.json
  74. 3
      packages/nc-gui/lang/he.json
  75. 3
      packages/nc-gui/lang/hi.json
  76. 3
      packages/nc-gui/lang/hr.json
  77. 3
      packages/nc-gui/lang/id.json
  78. 3
      packages/nc-gui/lang/it.json
  79. 3
      packages/nc-gui/lang/ja.json
  80. 3
      packages/nc-gui/lang/ko.json
  81. 3
      packages/nc-gui/lang/lv.json
  82. 3
      packages/nc-gui/lang/nl.json
  83. 3
      packages/nc-gui/lang/no.json
  84. 3
      packages/nc-gui/lang/pl.json
  85. 3
      packages/nc-gui/lang/pt.json
  86. 3
      packages/nc-gui/lang/pt_BR.json
  87. 165
      packages/nc-gui/lang/ru.json
  88. 3
      packages/nc-gui/lang/sk.json
  89. 3
      packages/nc-gui/lang/sl.json
  90. 3
      packages/nc-gui/lang/sv.json
  91. 3
      packages/nc-gui/lang/th.json
  92. 3
      packages/nc-gui/lang/tr.json
  93. 3
      packages/nc-gui/lang/uk.json
  94. 3
      packages/nc-gui/lang/vi.json
  95. 17
      packages/nc-gui/lang/zh-Hans.json
  96. 3
      packages/nc-gui/lang/zh-Hant.json
  97. 6
      packages/nc-gui/lib/types.ts
  98. 20
      packages/nc-gui/package.json
  99. 4
      packages/nc-gui/utils/cell.ts
  100. 21
      packages/nc-gui/utils/columnUtils.ts
  101. Some files were not shown because too many files have changed in this diff Show More

12
packages/nc-cli/package-lock.json generated

@ -6410,9 +6410,9 @@
} }
}, },
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.14.8", "version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==", "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@ -20501,9 +20501,9 @@
"integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==" "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q=="
}, },
"follow-redirects": { "follow-redirects": {
"version": "1.14.8", "version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==" "integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw=="
}, },
"for-in": { "for-in": {
"version": "1.0.2", "version": "1.0.2",

43
packages/nc-gui/components/account/UserList.vue

@ -2,7 +2,16 @@
import { OrgUserRoles } from 'nocodb-sdk' import { OrgUserRoles } from 'nocodb-sdk'
import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk' import type { OrgUserReqType, RequestParams, UserType } from 'nocodb-sdk'
import type { User } from '#imports' import type { User } from '#imports'
import { extractSdkResponseErrorMsg, iconMap, useApi, useCopy, useDashboard, useDebounceFn, useNuxtApp } from '#imports' import {
extractSdkResponseErrorMsg,
iconMap,
useApi,
useCopy,
useDashboard,
useDebounceFn,
useNuxtApp,
useUserSorts,
} from '#imports'
const { api, isLoading } = useApi() const { api, isLoading } = useApi()
@ -19,8 +28,14 @@ const { user: loggedInUser } = useGlobal()
const { copy } = useCopy() const { copy } = useCopy()
const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Org')
const users = ref<UserType[]>([]) const users = ref<UserType[]>([])
const sortedUsers = computed(() => {
return handleGetSortedData(users.value, sorts.value) as UserType[]
})
const currentPage = ref(1) const currentPage = ref(1)
const currentLimit = ref(10) const currentLimit = ref(10)
@ -64,6 +79,7 @@ const loadUsers = useDebounceFn(async (page = currentPage.value, limit = current
onMounted(() => { onMounted(() => {
loadUsers() loadUsers()
loadSorts()
}) })
const updateRole = async (userId: string, roles: string) => { const updateRole = async (userId: string, roles: string) => {
@ -73,6 +89,12 @@ const updateRole = async (userId: string, roles: string) => {
} as OrgUserReqType) } as OrgUserReqType)
message.success(t('msg.success.roleUpdated')) message.success(t('msg.success.roleUpdated'))
users.value.forEach((user) => {
if (user.id === userId) {
user.roles = roles
}
})
$e('a:org-user:role-updated', { role: roles }) $e('a:org-user:role-updated', { role: roles })
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -176,10 +198,21 @@ const openDeleteModal = (user: UserType) => {
</div> </div>
<div class="w-full rounded-md max-w-250 h-[calc(100%-12rem)] rounded-md overflow-hidden mt-5"> <div class="w-full rounded-md max-w-250 h-[calc(100%-12rem)] rounded-md overflow-hidden mt-5">
<div class="flex w-full bg-gray-50 border-1 rounded-t-md"> <div class="flex w-full bg-gray-50 border-1 rounded-t-md">
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-2/3 text-start pl-6" data-rec="true"> <div
{{ $t('labels.email') }} class="py-3.5 text-gray-500 font-medium text-3.5 w-2/3 text-start pl-6 flex items-center space-x-2"
data-rec="true"
>
<span>
{{ $t('labels.email') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.email" field="email" :handle-user-sort="saveOrUpdate" />
</div>
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start flex items-center space-x-2" data-rec="true">
<span>
{{ $t('objects.role') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.roles" field="roles" :handle-user-sort="saveOrUpdate" />
</div> </div>
<div class="py-3.5 text-gray-500 font-medium text-3.5 w-1/3 text-start" data-rec="true">{{ $t('objects.role') }}</div>
<div class="flex py-3.5 text-gray-500 font-medium text-3.5 w-28 justify-end mr-4" data-rec="true"> <div class="flex py-3.5 text-gray-500 font-medium text-3.5 w-28 justify-end mr-4" data-rec="true">
{{ $t('labels.action') }} {{ $t('labels.action') }}
</div> </div>
@ -193,7 +226,7 @@ const openDeleteModal = (user: UserType) => {
</div> </div>
<section v-else class="tbody h-[calc(100%-4rem)] nc-scrollbar-md border-t-0 !overflow-auto"> <section v-else class="tbody h-[calc(100%-4rem)] nc-scrollbar-md border-t-0 !overflow-auto">
<div <div
v-for="el of users" v-for="el of sortedUsers"
:key="el.id" :key="el.id"
data-testid="nc-token-list" data-testid="nc-token-list"
class="user flex py-3 justify-around px-1 border-b-1 border-l-1 border-r-1" class="user flex py-3 justify-around px-1 border-b-1 border-l-1 border-r-1"

79
packages/nc-gui/components/account/UserMenu.vue

@ -0,0 +1,79 @@
<script lang="ts" setup>
import { iconMap } from '#imports'
import type { UsersSortType } from '~/lib'
const { field, direction, handleUserSort } = defineProps<{
field: UsersSortType['field']
direction: UsersSortType['direction']
handleUserSort: Function
}>()
const isOpen = ref(false)
const sortUserBy = (direction?: UsersSortType['direction']) => {
handleUserSort({
field,
direction,
})
isOpen.value = false
}
</script>
<template>
<a-dropdown
v-model:visible="isOpen"
:trigger="['click']"
placement="bottomLeft"
overlay-class-name="nc-user-menu-column-operations !border-1 rounded-lg !shadow-xl"
@click.stop="isOpen = !isOpen"
>
<div>
<GeneralIcon
:icon="direction === 'asc' || direction === 'desc' ? 'sortDesc' : 'arrowDown'"
class="text-grey h-full text-grey nc-user-menu-trigger cursor-pointer outline-0 mr-2 transition-none"
:style="{ transform: direction === 'asc' ? 'rotate(180deg)' : undefined }"
/>
</div>
<template #overlay>
<NcMenu class="flex flex-col gap-1 border-gray-200 nc-user-menu-column-options">
<NcMenuItem @click="sortUserBy('asc')">
<div class="nc-column-insert-after nc-user-menu-item">
<component
:is="iconMap.sortDesc"
class="text-gray-700 !rotate-180 !w-4.25 !h-4.25"
:style="{
transform: 'rotate(180deg)',
}"
/>
<!-- Sort Ascending -->
{{ $t('general.sortAsc') }}
</div>
</NcMenuItem>
<NcMenuItem @click="sortUserBy('desc')">
<div class="nc-column-insert-before nc-user-menu-item">
<component :is="iconMap.sortDesc" class="text-gray-700 !w-4.25 !h-4.25 ml-0.5 mr-0.25" />
<!-- Sort Descending -->
{{ $t('general.sortDesc') }}
</div>
</NcMenuItem>
</NcMenu>
</template>
</a-dropdown>
</template>
<style scoped>
.nc-user-menu-item {
@apply flex items-center gap-2;
}
.nc-user-menu-column-options {
.nc-icons {
@apply !w-5 !h-5;
}
}
:deep(.ant-dropdown-menu-item) {
@apply !hover:text-black text-gray-700;
}
</style>

6
packages/nc-gui/components/cell/DatePicker.vue

@ -104,7 +104,7 @@ const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) { if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
} else if (modelValue === null && showNull.value) { } else if (modelValue === null && showNull.value) {
return t('general.null') return t('general.null').toUpperCase()
} else if (isDateInvalid.value) { } else if (isDateInvalid.value) {
return t('msg.invalidDate') return t('msg.invalidDate')
} else { } else {
@ -243,7 +243,7 @@ const clickHandler = () => {
:picker="picker" :picker="picker"
:tabindex="0" :tabindex="0"
:bordered="false" :bordered="false"
class="!w-full !py-1 !border-none" class="!w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }"
:format="dateFormat" :format="dateFormat"
:placeholder="placeholder" :placeholder="placeholder"
@ -260,7 +260,7 @@ const clickHandler = () => {
</template> </template>
<style scoped> <style scoped>
:deep(.ant-picker-input > input[disabled]) { :deep(.ant-picker-input > input) {
@apply !text-current; @apply !text-current;
} }
</style> </style>

7
packages/nc-gui/components/cell/DateTimePicker.vue

@ -157,7 +157,7 @@ const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) { if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
} else if (modelValue === null && showNull.value) { } else if (modelValue === null && showNull.value) {
return t('general.null') return t('general.null').toUpperCase()
} else if (isDateInvalid.value) { } else if (isDateInvalid.value) {
return t('msg.invalidDate') return t('msg.invalidDate')
} else { } else {
@ -253,6 +253,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
const cellClickHook = inject(CellClickHookInj, null) const cellClickHook = inject(CellClickHookInj, null)
const cellClickHandler = () => { const cellClickHandler = () => {
if (readOnly.value) return
open.value = (active.value || editable.value) && !open.value open.value = (active.value || editable.value) && !open.value
} }
@ -296,7 +297,7 @@ const isColDisabled = computed(() => {
:disabled="isColDisabled" :disabled="isColDisabled"
:show-time="true" :show-time="true"
:bordered="false" :bordered="false"
class="!w-full !py-1 !border-none" class="!w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }"
:format="dateTimeFormat" :format="dateTimeFormat"
:placeholder="placeholder" :placeholder="placeholder"
@ -313,7 +314,7 @@ const isColDisabled = computed(() => {
</template> </template>
<style scoped> <style scoped>
:deep(.ant-picker-input > input[disabled]) { :deep(.ant-picker-input > input) {
@apply !text-current; @apply !text-current;
} }
</style> </style>

2
packages/nc-gui/components/cell/Decimal.vue

@ -112,7 +112,7 @@ watch(isExpandedFormOpen, () => {
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null capitalize">{{ $t('general.null') }}</span> <span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<span v-else class="text-sm">{{ displayValue }}</span> <span v-else class="text-sm">{{ displayValue }}</span>
</template> </template>

2
packages/nc-gui/components/cell/Duration.vue

@ -111,7 +111,7 @@ const focus: VNodeRef = (el) =>
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="modelValue === null && showNull" class="nc-null capitalize">{{ $t('general.null') }}</span> <span v-else-if="modelValue === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<span v-else> {{ localState }}</span> <span v-else> {{ localState }}</span>

2
packages/nc-gui/components/cell/MultiSelect.vue

@ -567,7 +567,7 @@ const onFocus = () => {
} }
:deep(.ant-select-selector) { :deep(.ant-select-selector) {
@apply !px-0; @apply !pl-0;
} }
:deep(.ant-select-selection-search-input) { :deep(.ant-select-selection-search-input) {

2
packages/nc-gui/components/cell/Percent.vue

@ -143,7 +143,7 @@ const onTabPress = (e: KeyboardEvent) => {
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null capitalize">{{ $t('general.null') }}</span> <span v-else-if="vModel === null && showNull" class="nc-null uppercase">{{ $t('general.null') }}</span>
<div v-else-if="percentMeta.is_progress === true && vModel !== null && vModel !== undefined" class="px-2"> <div v-else-if="percentMeta.is_progress === true && vModel !== null && vModel !== undefined" class="px-2">
<a-progress <a-progress
:percent="Number(parseFloat(vModel.toString()).toFixed(2))" :percent="Number(parseFloat(vModel.toString()).toFixed(2))"

22
packages/nc-gui/components/cell/ReadOnlyDateTimePicker.vue

@ -0,0 +1,22 @@
<script setup lang="ts">
import { ActiveCellInj, EditModeInj, ReadonlyInj, provide, ref } from '#imports'
interface Props {
modelValue?: string | null
}
defineProps<Props>()
provide(ReadonlyInj, ref(true))
provide(EditModeInj, ref(true))
provide(ActiveCellInj, ref(true))
</script>
<template>
<div class="relative">
<LazyCellDateTimePicker class="z-0" :model-value="modelValue" :is-pk="false" />
<div class="w-full h-full z-1 absolute top-0 left-0"></div>
</div>
</template>

22
packages/nc-gui/components/cell/ReadOnlyUser.vue

@ -0,0 +1,22 @@
<script setup lang="ts">
import { ActiveCellInj, EditModeInj, ReadonlyInj, provide, ref } from '#imports'
interface Props {
modelValue?: string | null
}
defineProps<Props>()
provide(ReadonlyInj, ref(true))
provide(EditModeInj, ref(true))
provide(ActiveCellInj, ref(true))
</script>
<template>
<div class="relative">
<LazyCellUser class="z-0" :model-value="modelValue" />
<div class="w-full h-full z-1 absolute top-0 left-0"></div>
</div>
</template>

10
packages/nc-gui/components/cell/SingleSelect.vue

@ -322,7 +322,8 @@ const onFocus = () => {
:disabled="readOnly || !editAllowed" :disabled="readOnly || !editAllowed"
:show-search="!isMobileMode && isOpen && active" :show-search="!isMobileMode && isOpen && active"
:show-arrow="hasEditRoles && !readOnly && active && (vModel === null || vModel === undefined)" :show-arrow="hasEditRoles && !readOnly && active && (vModel === null || vModel === undefined)"
:dropdown-class-name="`nc-dropdown-single-select-cell ${isOpen && active ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-single-select-cell !min-w-200px ${isOpen && active ? 'active' : ''}`"
:dropdown-match-select-width="true"
@select="onSelect" @select="onSelect"
@keydown="onKeydown($event)" @keydown="onKeydown($event)"
@search="search" @search="search"
@ -399,7 +400,12 @@ const onFocus = () => {
} }
:deep(.ant-select-selector) { :deep(.ant-select-selector) {
@apply !px-0; @apply !pl-0 !pr-4;
}
:deep(.ant-select-selector .ant-select-selection-item) {
@apply flex items-center;
text-overflow: clip;
} }
:deep(.ant-select-selection-search-input) { :deep(.ant-select-selection-search-input) {

6
packages/nc-gui/components/cell/TimePicker.vue

@ -96,7 +96,7 @@ const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) { if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
} else if (modelValue === null && showNull.value) { } else if (modelValue === null && showNull.value) {
return t('general.null') return t('general.null').toUpperCase()
} else if (isTimeInvalid.value) { } else if (isTimeInvalid.value) {
return t('msg.invalidTime') return t('msg.invalidTime')
} else { } else {
@ -133,7 +133,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:bordered="false" :bordered="false"
use12-hours use12-hours
format="HH:mm" format="HH:mm"
class="!w-full !py-1 !border-none" class="!w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="!readOnly && !localState && !isPk" :allow-clear="!readOnly && !localState && !isPk"
@ -148,7 +148,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
</template> </template>
<style scoped> <style scoped>
:deep(.ant-picker-input > input[disabled]) { :deep(.ant-picker-input > input) {
@apply !text-current; @apply !text-current;
} }
</style> </style>

2
packages/nc-gui/components/cell/Url.vue

@ -108,7 +108,7 @@ watch(
@mousedown.stop @mousedown.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null uppercase"> $t('general.null')</span> <span v-else-if="vModel === null && showNull" class="nc-null uppercase"> {{ $t('general.null') }}</span>
<nuxt-link <nuxt-link
v-else-if="isValid && !cellUrlOptions?.overlay" v-else-if="isValid && !cellUrlOptions?.overlay"

67
packages/nc-gui/components/cell/User.vue

@ -27,7 +27,7 @@ import {
import MdiCloseCircle from '~icons/mdi/close-circle' import MdiCloseCircle from '~icons/mdi/close-circle'
interface Props { interface Props {
modelValue?: UserFieldRecordType[] | string | null modelValue?: UserFieldRecordType[] | UserFieldRecordType | string | null
rowIndex?: number rowIndex?: number
location?: 'cell' | 'filter' location?: 'cell' | 'filter'
forceMulti?: boolean forceMulti?: boolean
@ -113,17 +113,18 @@ const vModel = computed({
return acc return acc
}, [] as { label: string; value: string }[]) }, [] as { label: string; value: string }[])
} else { } else {
selected = selected = modelValue
modelValue?.reduce((acc, item) => { ? (Array.isArray(modelValue) ? modelValue : [modelValue]).reduce((acc, item) => {
const label = item?.display_name || item?.email const label = item?.display_name || item?.email
if (label) { if (label) {
acc.push({ acc.push({
label, label,
value: item.id, value: item.id,
}) })
} }
return acc return acc
}, [] as { label: string; value: string }[]) || [] }, [] as { label: string; value: string }[])
: []
} }
return selected return selected
@ -280,7 +281,7 @@ const filterOption = (input: string, option: any) => {
}" }"
> >
<template v-for="selectedOpt of vModel" :key="selectedOpt.value"> <template v-for="selectedOpt of vModel" :key="selectedOpt.value">
<a-tag class="rounded-tag" color="'#ccc'"> <a-tag class="rounded-tag max-w-full" color="'#ccc'">
<span <span
:style="{ :style="{
'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { level: 'AA', size: 'large' }) 'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { level: 'AA', size: 'large' })
@ -290,7 +291,21 @@ const filterOption = (input: string, option: any) => {
}" }"
:class="{ 'text-sm': isKanban }" :class="{ 'text-sm': isKanban }"
> >
{{ selectedOpt.label }} <NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ selectedOpt.label }}
</template>
<span
class="text-ellipsis overflow-hidden"
:style="{
wordBreak: 'keep-all',
whiteSpace: 'nowrap',
display: 'inline',
}"
>
{{ selectedOpt.label }}
</span>
</NcTooltip>
</span> </span>
</a-tag> </a-tag>
</template> </template>
@ -310,7 +325,7 @@ const filterOption = (input: string, option: any) => {
:open="isOpen && editAllowed" :open="isOpen && editAllowed"
:disabled="readOnly || !editAllowed" :disabled="readOnly || !editAllowed"
:class="{ 'caret-transparent': !hasEditRoles }" :class="{ 'caret-transparent': !hasEditRoles }"
:dropdown-class-name="`nc-dropdown-user-select-cell ${isOpen ? 'active' : ''}`" :dropdown-class-name="`nc-dropdown-user-select-cell !min-w-200px ${isOpen ? 'active' : ''}`"
:filter-option="filterOption" :filter-option="filterOption"
@search="search" @search="search"
@keydown.stop @keydown.stop
@ -326,7 +341,7 @@ const filterOption = (input: string, option: any) => {
:class="`nc-select-option-${column.title}-${op.email}`" :class="`nc-select-option-${column.title}-${op.email}`"
@click.stop @click.stop
> >
<a-tag class="rounded-tag" color="'#ccc'"> <a-tag class="rounded-tag max-w-full !pl-0" color="'#ccc'">
<span <span
:style="{ :style="{
'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { level: 'AA', size: 'large' }) 'color': tinycolor.isReadable('#ccc' || '#ccc', '#fff', { level: 'AA', size: 'large' })
@ -334,9 +349,25 @@ const filterOption = (input: string, option: any) => {
: tinycolor.mostReadable('#ccc' || '#ccc', ['#0b1d05', '#fff']).toHex8String(), : tinycolor.mostReadable('#ccc' || '#ccc', ['#0b1d05', '#fff']).toHex8String(),
'font-size': '13px', 'font-size': '13px',
}" }"
class="flex items-center gap-2"
:class="{ 'text-sm': isKanban }" :class="{ 'text-sm': isKanban }"
> >
{{ op.display_name?.length ? op.display_name : op.email }} <GeneralUserIcon size="medium" :email="op.email" />
<NcTooltip class="truncate max-w-full" show-on-truncate-only>
<template #title>
{{ op.display_name?.length ? op.display_name : op.email }}
</template>
<span
class="text-ellipsis overflow-hidden"
:style="{
wordBreak: 'keep-all',
whiteSpace: 'nowrap',
display: 'inline',
}"
>
{{ op.display_name?.length ? op.display_name : op.email }}
</span>
</NcTooltip>
</span> </span>
</a-tag> </a-tag>
</a-select-option> </a-select-option>
@ -433,7 +464,7 @@ const filterOption = (input: string, option: any) => {
} }
:deep(.ant-select-selector) { :deep(.ant-select-selector) {
@apply !px-0; @apply !pl-0;
} }
:deep(.ant-select-selection-search-input) { :deep(.ant-select-selection-search-input) {

6
packages/nc-gui/components/cell/YearPicker.vue

@ -83,7 +83,7 @@ const placeholder = computed(() => {
if (isEditColumn.value && (modelValue === '' || modelValue === null)) { if (isEditColumn.value && (modelValue === '' || modelValue === null)) {
return t('labels.optional') return t('labels.optional')
} else if (modelValue === null && showNull.value) { } else if (modelValue === null && showNull.value) {
return t('general.null') return t('general.null').toUpperCase()
} else if (isYearInvalid.value) { } else if (isYearInvalid.value) {
return t('msg.invalidTime') return t('msg.invalidTime')
} else { } else {
@ -119,7 +119,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
:tabindex="0" :tabindex="0"
picker="year" picker="year"
:bordered="false" :bordered="false"
class="!w-full !py-1 !border-none" class="!w-full !py-1 !border-none !text-current"
:class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }" :class="{ 'nc-null': modelValue === null && showNull, '!px-2': isExpandedFormOpen, '!px-0': !isExpandedFormOpen }"
:placeholder="placeholder" :placeholder="placeholder"
:allow-clear="(!readOnly && !localState && !isPk) || isEditColumn" :allow-clear="(!readOnly && !localState && !isPk) || isEditColumn"
@ -136,7 +136,7 @@ useSelectedCellKeyupListener(active, (e: KeyboardEvent) => {
</template> </template>
<style scoped> <style scoped>
:deep(.ant-picker-input > input[disabled]) { :deep(.ant-picker-input > input) {
@apply !text-current; @apply !text-current;
} }
</style> </style>

7
packages/nc-gui/components/erd/utils.ts

@ -175,9 +175,10 @@ export function useErdElements(tables: MaybeRef<TableType[]>, props: MaybeRef<ER
if (!table.id) return acc if (!table.id) return acc
const columns = const columns =
metasWithIdAsKey.value[table.id].columns?.filter( metasWithIdAsKey.value[table.id].columns?.filter((col) => {
(col) => config.value.showAllColumns || (!config.value.showAllColumns && isLinksOrLTAR(col)), if ([UITypes.CreatedBy, UITypes.LastModifiedBy].includes(col.uidt as UITypes) && col.system) return false
) || [] return config.value.showAllColumns || (!config.value.showAllColumns && isLinksOrLTAR(col))
}) || []
const pkAndFkColumns = columns const pkAndFkColumns = columns
.filter(() => config.value.showPkAndFk) .filter(() => config.value.showPkAndFk)

66
packages/nc-gui/components/project/AccessSettings.vue

@ -8,8 +8,9 @@ import {
parseStringDateTime, parseStringDateTime,
timeAgo, timeAgo,
} from 'nocodb-sdk' } from 'nocodb-sdk'
import type { WorkspaceUserRoles } from 'nocodb-sdk' import type { Roles, WorkspaceUserRoles } from 'nocodb-sdk'
import { isEeUI, storeToRefs } from '#imports' import { isEeUI, storeToRefs, useUserSorts } from '#imports'
import type { User } from '#imports'
const basesStore = useBases() const basesStore = useBases()
const { getBaseUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore const { getBaseUsers, createProjectUser, updateProjectUser, removeProjectUser } = basesStore
@ -17,6 +18,8 @@ const { activeProjectId } = storeToRefs(basesStore)
const { orgRoles, baseRoles } = useRoles() const { orgRoles, baseRoles } = useRoles()
const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Project')
const isSuper = computed(() => orgRoles.value?.[OrgUserRoles.SUPER_ADMIN]) const isSuper = computed(() => orgRoles.value?.[OrgUserRoles.SUPER_ADMIN])
interface Collaborators { interface Collaborators {
@ -24,6 +27,7 @@ interface Collaborators {
email: string email: string
main_roles: OrgUserRoles main_roles: OrgUserRoles
roles: ProjectRoles roles: ProjectRoles
base_roles: Roles
workspace_roles: WorkspaceUserRoles workspace_roles: WorkspaceUserRoles
created_at: string created_at: string
} }
@ -35,6 +39,14 @@ const isLoading = ref(false)
const isSearching = ref(false) const isSearching = ref(false)
const accessibleRoles = ref<(typeof ProjectRoles)[keyof typeof ProjectRoles][]>([]) const accessibleRoles = ref<(typeof ProjectRoles)[keyof typeof ProjectRoles][]>([])
const filteredCollaborators = computed(() =>
collaborators.value.filter((collab) => collab.email.toLowerCase().includes(userSearchText.value.toLowerCase())),
)
const sortedCollaborators = computed(() => {
return handleGetSortedData(filteredCollaborators.value, sorts.value)
})
const loadCollaborators = async () => { const loadCollaborators = async () => {
try { try {
const { users, totalRows } = await getBaseUsers({ const { users, totalRows } = await getBaseUsers({
@ -64,29 +76,33 @@ const loadCollaborators = async () => {
} }
const updateCollaborator = async (collab: any, roles: ProjectRoles) => { const updateCollaborator = async (collab: any, roles: ProjectRoles) => {
const currentCollaborator = collaborators.value.find((coll) => coll.id === collab.id)!
try { try {
if ( if (
!roles || !roles ||
(roles === ProjectRoles.NO_ACCESS && !isEeUI) || (roles === ProjectRoles.NO_ACCESS && !isEeUI) ||
(collab.workspace_roles && WorkspaceRolesToProjectRoles[collab.workspace_roles as WorkspaceUserRoles] === roles && isEeUI) (currentCollaborator.workspace_roles &&
WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] === roles &&
isEeUI)
) { ) {
await removeProjectUser(activeProjectId.value!, collab) await removeProjectUser(activeProjectId.value!, currentCollaborator as unknown as User)
if ( if (
collab.workspace_roles && currentCollaborator.workspace_roles &&
WorkspaceRolesToProjectRoles[collab.workspace_roles as WorkspaceUserRoles] === roles && WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles] === roles &&
isEeUI isEeUI
) { ) {
collab.roles = WorkspaceRolesToProjectRoles[collab.workspace_roles as WorkspaceUserRoles] currentCollaborator.roles = WorkspaceRolesToProjectRoles[currentCollaborator.workspace_roles as WorkspaceUserRoles]
} else { } else {
collab.roles = ProjectRoles.NO_ACCESS currentCollaborator.roles = ProjectRoles.NO_ACCESS
} }
} else if (collab.base_roles) { } else if (currentCollaborator.base_roles) {
collab.roles = roles currentCollaborator.roles = roles
await updateProjectUser(activeProjectId.value!, collab) await updateProjectUser(activeProjectId.value!, currentCollaborator as unknown as User)
} else { } else {
collab.roles = roles currentCollaborator.roles = roles
collab.base_roles = roles currentCollaborator.base_roles = roles
await createProjectUser(activeProjectId.value!, collab) await createProjectUser(activeProjectId.value!, currentCollaborator as unknown as User)
} }
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
@ -106,16 +122,13 @@ onMounted(async () => {
} else if (currentRoleIndex !== -1) { } else if (currentRoleIndex !== -1) {
accessibleRoles.value = OrderedProjectRoles.slice(currentRoleIndex + 1) accessibleRoles.value = OrderedProjectRoles.slice(currentRoleIndex + 1)
} }
loadSorts()
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} finally { } finally {
isLoading.value = false isLoading.value = false
} }
}) })
const filteredCollaborators = computed(() =>
collaborators.value.filter((collab) => collab.email.toLowerCase().includes(userSearchText.value.toLowerCase())),
)
</script> </script>
<template> <template>
@ -145,14 +158,25 @@ const filteredCollaborators = computed(() =>
<div v-else class="nc-collaborators-list mt-6 h-full"> <div v-else class="nc-collaborators-list mt-6 h-full">
<div class="flex flex-col rounded-lg overflow-hidden border-1 max-w-350 max-h-[calc(100%-8rem)]"> <div class="flex flex-col rounded-lg overflow-hidden border-1 max-w-350 max-h-[calc(100%-8rem)]">
<div class="flex flex-row bg-gray-50 min-h-12 items-center border-b-1"> <div class="flex flex-row bg-gray-50 min-h-12 items-center border-b-1">
<div class="text-gray-700 users-email-grid">{{ $t('objects.users') }}</div> <div class="text-gray-700 users-email-grid flex items-center space-x-2">
<div class="text-gray-700 user-access-grid">{{ $t('general.access') }}</div> <span>
{{ $t('objects.users') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.email" field="email" :handle-user-sort="saveOrUpdate" />
</div>
<div class="text-gray-700 user-access-grid flex items-center space-x-2">
<span>
{{ $t('general.access') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.roles" field="roles" :handle-user-sort="saveOrUpdate" />
</div>
<div class="text-gray-700 date-joined-grid">{{ $t('title.dateJoined') }}</div> <div class="text-gray-700 date-joined-grid">{{ $t('title.dateJoined') }}</div>
</div> </div>
<div class="flex flex-col nc-scrollbar-md"> <div class="flex flex-col nc-scrollbar-md">
<div <div
v-for="(collab, i) of filteredCollaborators" v-for="(collab, i) of sortedCollaborators"
:key="i" :key="i"
class="user-row flex flex-row border-b-1 py-1 min-h-14 items-center" class="user-row flex flex-row border-b-1 py-1 min-h-14 items-center"
> >

13
packages/nc-gui/components/smartsheet/Form.vue

@ -34,7 +34,18 @@ provide(IsGalleryInj, ref(false))
// todo: generate hideCols based on default values // todo: generate hideCols based on default values
const hiddenCols = ['created_at', 'updated_at'] const hiddenCols = ['created_at', 'updated_at']
const hiddenColTypes = [UITypes.Rollup, UITypes.Lookup, UITypes.Formula, UITypes.QrCode, UITypes.Barcode, UITypes.SpecificDBType] const hiddenColTypes = [
UITypes.Rollup,
UITypes.Lookup,
UITypes.Formula,
UITypes.QrCode,
UITypes.Barcode,
UITypes.SpecificDBType,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
]
const { isMobileMode, user } = useGlobal() const { isMobileMode, user } = useGlobal()

4
packages/nc-gui/components/smartsheet/Gallery.vue

@ -295,7 +295,7 @@ watch(
<LazySmartsheetVirtualCell <LazySmartsheetVirtualCell
v-if="isVirtualCol(displayField)" v-if="isVirtualCol(displayField)"
v-model="record.row[displayField.title]" v-model="record.row[displayField.title]"
class="!text-gray-600" class="!text-brand-500"
:column="displayField" :column="displayField"
:row="record" :row="record"
/> />
@ -303,7 +303,7 @@ watch(
<LazySmartsheetCell <LazySmartsheetCell
v-else v-else
v-model="record.row[displayField.title]" v-model="record.row[displayField.title]"
class="!text-gray-600" class="!text-brand-500"
:column="displayField" :column="displayField"
:edit-enabled="false" :edit-enabled="false"
:read-only="true" :read-only="true"

4
packages/nc-gui/components/smartsheet/Kanban.vue

@ -593,7 +593,7 @@ const getRowId = (row: RowType) => {
<LazySmartsheetVirtualCell <LazySmartsheetVirtualCell
v-if="isVirtualCol(displayField)" v-if="isVirtualCol(displayField)"
v-model="record.row[displayField.title]" v-model="record.row[displayField.title]"
class="!text-gray-600" class="!text-brand-500"
:column="displayField" :column="displayField"
:row="record" :row="record"
/> />
@ -601,7 +601,7 @@ const getRowId = (row: RowType) => {
<LazySmartsheetCell <LazySmartsheetCell
v-else v-else
v-model="record.row[displayField.title]" v-model="record.row[displayField.title]"
class="!text-gray-600" class="!text-brand-500"
:column="displayField" :column="displayField"
:edit-enabled="false" :edit-enabled="false"
:read-only="true" :read-only="true"

5
packages/nc-gui/components/smartsheet/VirtualCell.vue

@ -1,5 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { isCreatedOrLastModifiedByCol, isCreatedOrLastModifiedTimeCol } from 'nocodb-sdk'
import { import {
ActiveCellInj, ActiveCellInj,
CellValueInj, CellValueInj,
@ -19,6 +20,7 @@ import {
isLink, isLink,
isLookup, isLookup,
isMm, isMm,
isPrimary,
isQrCode, isQrCode,
isRollup, isRollup,
provide, provide,
@ -99,6 +101,7 @@ onUnmounted(() => {
class="nc-virtual-cell w-full flex items-center" class="nc-virtual-cell w-full flex items-center"
:class="{ :class="{
'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm, 'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm,
'text-brand-500': isPrimary(column) && !isForm,
}" }"
@keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)" @keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
@ -114,6 +117,8 @@ onUnmounted(() => {
<LazyVirtualCellBarcode v-else-if="isBarcode(column)" /> <LazyVirtualCellBarcode v-else-if="isBarcode(column)" />
<LazyVirtualCellCount v-else-if="isCount(column)" /> <LazyVirtualCellCount v-else-if="isCount(column)" />
<LazyVirtualCellLookup v-else-if="isLookup(column)" /> <LazyVirtualCellLookup v-else-if="isLookup(column)" />
<LazyCellReadOnlyDateTimePicker v-else-if="isCreatedOrLastModifiedTimeCol(column)" :model-value="modelValue" />
<LazyCellReadOnlyUser v-else-if="isCreatedOrLastModifiedByCol(column)" :model-value="modelValue" />
</template> </template>
</div> </div>
</template> </template>

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

@ -79,7 +79,16 @@ const mounted = ref(false)
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber] const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const onlyNameUpdateOnEditColumns = [UITypes.LinkToAnotherRecord, UITypes.Lookup, UITypes.Rollup, UITypes.Links] const onlyNameUpdateOnEditColumns = [
UITypes.LinkToAnotherRecord,
UITypes.Lookup,
UITypes.Rollup,
UITypes.Links,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
]
// To close column type dropdown on escape and // To close column type dropdown on escape and
// close modal only when the type popup is close // close modal only when the type popup is close
@ -344,6 +353,7 @@ if (props.fromTableExplorer) {
<SmartsheetColumnSelectOptions <SmartsheetColumnSelectOptions
v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect" v-if="formState.uidt === UITypes.SingleSelect || formState.uidt === UITypes.MultiSelect"
v-model:value="formState" v-model:value="formState"
:from-table-explorer="props.fromTableExplorer || false"
/> />
</template> </template>
</div> </div>

14
packages/nc-gui/components/smartsheet/column/FormulaOptions.vue

@ -5,6 +5,7 @@ import jsep from 'jsep'
import { import {
FormulaError, FormulaError,
UITypes, UITypes,
isCreatedOrLastModifiedByCol,
jsepCurlyHook, jsepCurlyHook,
substituteColumnIdWithAliasInFormula, substituteColumnIdWithAliasInFormula,
validateFormulaAndExtractTreeWithType, validateFormulaAndExtractTreeWithType,
@ -51,7 +52,18 @@ const { predictFunction: _predictFunction } = useNocoEe()
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
const supportedColumns = computed( const supportedColumns = computed(
() => meta?.value?.columns?.filter((col) => !uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) || [], () =>
meta?.value?.columns?.filter((col) => {
if (uiTypesNotSupportedInFormulas.includes(col.uidt as UITypes)) {
return false
}
if (isCreatedOrLastModifiedByCol(col) && col.system) {
return false
}
return true
}) || [],
) )
const { getMeta } = useMetas() const { getMeta } = useMetas()

16
packages/nc-gui/components/smartsheet/column/LookupOptions.vue

@ -22,7 +22,7 @@ const baseStore = useBase()
const { tables } = storeToRefs(baseStore) const { tables } = storeToRefs(baseStore)
const { metas } = useMetas() const { metas, getMeta } = useMetas()
setAdditionalValidations({ setAdditionalValidations({
fk_relation_column_id: [{ required: true, message: t('general.required') }], fk_relation_column_id: [{ required: true, message: t('general.required') }],
@ -48,12 +48,15 @@ const refTables = computed(() => {
return _refTables as Required<TableType & { column: ColumnType; col: Required<LinkToAnotherRecordType> }>[] return _refTables as Required<TableType & { column: ColumnType; col: Required<LinkToAnotherRecordType> }>[]
}) })
const selectedTable = computed(() => {
return refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id)
})
const columns = computed<ColumnType[]>(() => { const columns = computed<ColumnType[]>(() => {
const selectedTable = refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id) if (!selectedTable.value?.id) {
if (!selectedTable?.id) {
return [] return []
} }
return metas.value[selectedTable.id].columns.filter( return metas.value[selectedTable.value.id]?.columns.filter(
(c: ColumnType) => (c: ColumnType) =>
vModel.value.fk_lookup_column_id === c.id || (!isSystemColumn(c) && c.id !== vModel.value.id && c.uidt !== UITypes.Links), vModel.value.fk_lookup_column_id === c.id || (!isSystemColumn(c) && c.id !== vModel.value.id && c.uidt !== UITypes.Links),
) )
@ -66,7 +69,10 @@ onMounted(() => {
} }
}) })
const onRelationColChange = () => { const onRelationColChange = async () => {
if (selectedTable.value) {
await getMeta(selectedTable.value.id)
}
vModel.value.fk_lookup_column_id = columns.value?.[0]?.id vModel.value.fk_lookup_column_id = columns.value?.[0]?.id
onDataTypeChange() onDataTypeChange()
} }

17
packages/nc-gui/components/smartsheet/column/RollupOptions.vue

@ -35,7 +35,7 @@ const baseStore = useBase()
const { tables } = storeToRefs(baseStore) const { tables } = storeToRefs(baseStore)
const { metas } = useMetas() const { metas, getMeta } = useMetas()
const { t } = useI18n() const { t } = useI18n()
@ -70,14 +70,16 @@ const refTables = computed(() => {
return _refTables as Required<TableType & { column: ColumnType; col: Required<LinkToAnotherRecordType> }>[] return _refTables as Required<TableType & { column: ColumnType; col: Required<LinkToAnotherRecordType> }>[]
}) })
const columns = computed(() => { const selectedTable = computed(() => {
const selectedTable = refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id) return refTables.value.find((t) => t.column.id === vModel.value.fk_relation_column_id)
})
if (!selectedTable?.id) { const columns = computed<ColumnType[]>(() => {
if (!selectedTable.value?.id) {
return [] return []
} }
return metas.value[selectedTable.id].columns.filter( return metas.value[selectedTable.value.id]?.columns.filter(
(c: ColumnType) => !isVirtualCol(c.uidt as UITypes) && (!isSystemColumn(c) || c.pk), (c: ColumnType) => !isVirtualCol(c.uidt as UITypes) && (!isSystemColumn(c) || c.pk),
) )
}) })
@ -90,7 +92,10 @@ onMounted(() => {
} }
}) })
const onRelationColChange = () => { const onRelationColChange = async () => {
if (selectedTable.value) {
await getMeta(selectedTable.value.id)
}
vModel.value.fk_rollup_column_id = columns.value?.[0]?.id vModel.value.fk_rollup_column_id = columns.value?.[0]?.id
onDataTypeChange() onDataTypeChange()
} }

3
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -15,6 +15,7 @@ interface Option {
const props = defineProps<{ const props = defineProps<{
value: any value: any
fromTableExplorer?: boolean
}>() }>()
const emit = defineEmits(['update:value']) const emit = defineEmits(['update:value'])
@ -308,7 +309,7 @@ const loadListData = async ($state: any) => {
ref="optionsWrapperDomRef" ref="optionsWrapperDomRef"
class="nc-col-option-select-option overflow-x-auto scrollbar-thin-dull" class="nc-col-option-select-option overflow-x-auto scrollbar-thin-dull"
:style="{ :style="{
maxHeight: 'calc(min(30vh, 250px))', maxHeight: props.fromTableExplorer ? 'calc(100vh - (var(--topbar-height) * 3.6) - 320px)' : 'calc(min(30vh, 250px))',
}" }"
> >
<InfiniteLoading v-if="isReverseLazyLoad" v-bind="$attrs" @infinite="loadListDataReverse"> <InfiniteLoading v-if="isReverseLazyLoad" v-bind="$attrs" @infinite="loadListDataReverse">

147
packages/nc-gui/components/smartsheet/details/Fields.vue

@ -100,6 +100,7 @@ const fields = computed<TableExplorerColumn[]>({
const x = ((meta.value?.columns as ColumnType[]) ?? []) const x = ((meta.value?.columns as ColumnType[]) ?? [])
.filter((field) => !field.fk_column_id && !isSystemColumn(field)) .filter((field) => !field.fk_column_id && !isSystemColumn(field))
.concat(newFields.value) .concat(newFields.value)
.map((field) => updateDefaultColumnValues(field))
.sort((a, b) => { .sort((a, b) => {
return getFieldOrder(a) - getFieldOrder(b) return getFieldOrder(a) - getFieldOrder(b)
}) })
@ -268,8 +269,30 @@ const duplicateField = async (field: TableExplorerColumn) => {
const onFieldUpdate = (state: TableExplorerColumn) => { const onFieldUpdate = (state: TableExplorerColumn) => {
const col = fields.value.find((col) => compareCols(col, state)) const col = fields.value.find((col) => compareCols(col, state))
if (!col) return if (!col) return
const diffs = diff(col, state) const diffs = diff(col, state)
if (Object.keys(diffs).length === 0 || (Object.keys(diffs).length === 1 && 'altered' in diffs)) {
// hack to prevent update status `Updated Field` when clicking on field first time
let isUpdated = true
if (
[UITypes.SingleSelect, UITypes.MultiSelect].includes(col.uidt) &&
Object.keys(diffs).length === 1 &&
diffs?.colOptions?.options &&
(diffs?.colOptions?.options?.length === 0 ||
(diffs?.colOptions?.options[0]?.index !== undefined && Object.keys(diffs?.colOptions?.options[0] || {}).length === 1))
) {
isUpdated = false
}
if (!isUpdated) {
let field = fields.value.find((field) => compareCols(field, state))
if (field) {
field = state
}
}
if (Object.keys(diffs).length === 0 || (Object.keys(diffs).length === 1 && 'altered' in diffs) || !isUpdated) {
ops.value = ops.value.filter((op) => op.op === 'add' || !compareCols(op.column, state)) ops.value = ops.value.filter((op) => op.op === 'add' || !compareCols(op.column, state))
} else { } else {
const field = ops.value.find((op) => compareCols(op.column, state)) const field = ops.value.find((op) => compareCols(op.column, state))
@ -291,7 +314,7 @@ const onFieldUpdate = (state: TableExplorerColumn) => {
return return
} }
if (field && !moveField) { if (field || (field && moveField)) {
field.column = state field.column = state
} else { } else {
ops.value.push({ ops.value.push({
@ -376,6 +399,19 @@ const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => {
return return
} }
const mop = moveOps.value.find((op) => compareCols(op.column, fields.value[_event.moved.oldIndex]))
if (mop) {
mop.index = _event.moved.newIndex
mop.order = order
} else {
moveOps.value.push({
op: 'move',
column: fields.value[_event.moved.oldIndex],
index: _event.moved.newIndex,
order,
})
}
if (op) { if (op) {
onFieldUpdate({ onFieldUpdate({
...op.column, ...op.column,
@ -393,19 +429,6 @@ const onMove = (_event: { moved: { newIndex: number; oldIndex: number } }) => {
}, },
}) })
} }
const mop = moveOps.value.find((op) => compareCols(op.column, fields.value[_event.moved.oldIndex]))
if (mop) {
mop.index = _event.moved.newIndex
mop.order = order
} else {
moveOps.value.push({
op: 'move',
column: fields.value[_event.moved.oldIndex],
index: _event.moved.newIndex,
order,
})
}
} }
const isColumnValid = (column: TableExplorerColumn) => { const isColumnValid = (column: TableExplorerColumn) => {
@ -438,6 +461,52 @@ const isColumnValid = (column: TableExplorerColumn) => {
return true return true
} }
function updateDefaultColumnValues(column: TableExplorerColumn) {
if (column.uidt === UITypes.QrCode && column.colOptions?.fk_qr_value_column_id) {
if (!column?.fk_qr_value_column_id) {
column.fk_qr_value_column_id = column.colOptions.fk_qr_value_column_id
}
}
if (column.uidt === UITypes.Barcode && column.colOptions?.fk_barcode_value_column_id) {
if (!column?.fk_barcode_value_column_id) {
column.fk_barcode_value_column_id = column.colOptions.fk_barcode_value_column_id
}
}
if (column.uidt === UITypes.Lookup && column?.colOptions?.fk_lookup_column_id && column?.colOptions?.fk_relation_column_id) {
if (!column?.fk_lookup_column_id) {
column.fk_lookup_column_id = column.colOptions.fk_lookup_column_id
}
if (!column?.fk_relation_column_id) {
column.fk_relation_column_id = column.colOptions.fk_relation_column_id
}
}
if (
column.uidt === UITypes.Rollup &&
column?.colOptions?.fk_relation_column_id &&
column?.colOptions?.fk_rollup_column_id &&
column?.colOptions?.rollup_function
) {
if (!column?.fk_relation_column_id) {
column.fk_relation_column_id = column.colOptions.fk_relation_column_id
}
if (!column?.fk_rollup_column_id) {
column.fk_rollup_column_id = column.colOptions.fk_rollup_column_id
}
if (!column?.rollup_function) {
column.rollup_function = column.colOptions.rollup_function
}
}
if (column.uidt === UITypes.Formula && column.colOptions?.formula_raw && !column?.formula_raw) {
column.formula_raw = column.colOptions?.formula_raw
}
return column
}
const recoverField = (state: TableExplorerColumn) => { const recoverField = (state: TableExplorerColumn) => {
const field = ops.value.find((op) => compareCols(op.column, state)) const field = ops.value.find((op) => compareCols(op.column, state))
if (field) { if (field) {
@ -512,10 +581,13 @@ const saveChanges = async () => {
view_id: view.value?.id as string, view_id: view.value?.id as string,
} }
} }
}
for (const f of fields.value) { if (op && op.op === 'update') {
console.log(f.title, getFieldOrder(f)) op.column.column_order = {
order: mop.order,
view_id: view.value?.id as string,
}
}
} }
for (const op of ops.value) { for (const op of ops.value) {
@ -545,7 +617,10 @@ const saveChanges = async () => {
await loadViewColumns() await loadViewColumns()
if (res) { if (res) {
ops.value = (res.failedOps as op[]) || [] ops.value =
res.failedOps && res.failedOps?.length
? (res.failedOps as (op & { error: unknown })[]).map(({ error: _, ...rest }) => rest)
: []
newFields.value = newFields.value.filter((col) => { newFields.value = newFields.value.filter((col) => {
if (res.failedOps) { if (res.failedOps) {
const op = res.failedOps.find((fop) => { const op = res.failedOps.find((fop) => {
@ -631,8 +706,14 @@ onKeyDown('ArrowUp', () => {
onKeyDown('Delete', () => { onKeyDown('Delete', () => {
if (isLocked.value) return if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return if (
if (document.activeElement?.tagName === 'TEXTAREA') return document.activeElement?.tagName === 'INPUT' ||
document.activeElement?.tagName === 'TEXTAREA' ||
// A rich text editor is a div with the contenteditable attribute set to true.
document.activeElement?.getAttribute('contenteditable')
) {
return
}
const isDeletedField = fieldStatus(activeField.value) === 'delete' const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) { if (!isDeletedField && activeField.value) {
@ -643,8 +724,14 @@ onKeyDown('Delete', () => {
onKeyDown('Backspace', () => { onKeyDown('Backspace', () => {
if (isLocked.value) return if (isLocked.value) return
if (document.activeElement?.tagName === 'INPUT') return if (
if (document.activeElement?.tagName === 'TEXTAREA') return document.activeElement?.tagName === 'INPUT' ||
document.activeElement?.tagName === 'TEXTAREA' ||
// A rich text editor is a div with the contenteditable attribute set to true.
document.activeElement?.getAttribute('contenteditable')
) {
return
}
const isDeletedField = fieldStatus(activeField.value) === 'delete' const isDeletedField = fieldStatus(activeField.value) === 'delete'
if (!isDeletedField && activeField.value) { if (!isDeletedField && activeField.value) {
@ -705,6 +792,16 @@ const onFieldOptionUpdate = () => {
isFieldIdCopied.value = false isFieldIdCopied.value = false
}, 200) }, 200)
} }
watch(
fields,
() => {
if (activeField.value) {
activeField.value = fields.value.find((field) => field.id === activeField.value.id) || activeField.value
}
},
{ deep: true },
)
</script> </script>
<template> <template>
@ -771,7 +868,7 @@ const onFieldOptionUpdate = () => {
</div> </div>
<div class="flex flex-row rounded-lg border-1 overflow-clip border-gray-200"> <div class="flex flex-row rounded-lg border-1 overflow-clip border-gray-200">
<div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto flex-1 flex-grow-1 nc-fields-height"> <div ref="fieldsListWrapperDomRef" class="nc-scrollbar-md !overflow-auto flex-1 flex-grow-1 nc-fields-height">
<Draggable v-model="fields" :disabled="isLocked" item-key="id" @change="onMove($event)"> <Draggable :model-value="fields" :disabled="isLocked" item-key="id" @change="onMove($event)">
<template #item="{ element: field }"> <template #item="{ element: field }">
<div <div
v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv" v-if="field.title.toLowerCase().includes(searchQuery.toLowerCase()) && !field.pv"
@ -1080,7 +1177,7 @@ const onFieldOptionUpdate = () => {
</template> </template>
</Draggable> </Draggable>
</div> </div>
<Transition v-if="!changingField" name="slide-fade"> <Transition name="slide-fade">
<div v-if="!changingField" class="border-gray-200 border-l-1 nc-scrollbar-md nc-fields-height !overflow-y-auto"> <div v-if="!changingField" class="border-gray-200 border-l-1 nc-scrollbar-md nc-fields-height !overflow-y-auto">
<SmartsheetColumnEditOrAddProvider <SmartsheetColumnEditOrAddProvider
v-if="activeField" v-if="activeField"

28
packages/nc-gui/components/smartsheet/expanded-form/index.vue

@ -1,6 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, TableType, ViewType } from 'nocodb-sdk'
import { ViewTypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' import {
ViewTypes,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
isLinksOrLTAR,
isSystemColumn,
isVirtualCol,
} from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import MdiChevronDown from '~icons/mdi/chevron-down' import MdiChevronDown from '~icons/mdi/chevron-down'
@ -278,7 +285,7 @@ const reloadHook = createEventHook()
reloadHook.on(() => { reloadHook.on(() => {
reloadParentRowHook?.trigger(false) reloadParentRowHook?.trigger(false)
if (isNew.value) return if (isNew.value) return
_loadRow() _loadRow(null, true)
}) })
provide(ReloadRowDataHookInj, reloadHook) provide(ReloadRowDataHookInj, reloadHook)
@ -458,7 +465,16 @@ const onIsExpandedUpdate = (v: boolean) => {
} }
const isReadOnlyVirtualCell = (column: ColumnType) => { const isReadOnlyVirtualCell = (column: ColumnType) => {
return isRollup(column) || isFormula(column) || isBarcode(column) || isLookup(column) || isQrCode(column) return (
isRollup(column) ||
isFormula(column) ||
isBarcode(column) ||
isLookup(column) ||
isQrCode(column) ||
isSystemColumn(column) ||
isCreatedOrLastModifiedTimeCol(column) ||
isCreatedOrLastModifiedByCol(column)
)
} }
// Small hack. We need to scroll to the bottom of the form after its mounted and back to top. // Small hack. We need to scroll to the bottom of the form after its mounted and back to top.
@ -686,7 +702,7 @@ export default {
:ref="i ? null : (el: any) => (cellWrapperEl = el)" :ref="i ? null : (el: any) => (cellWrapperEl = el)"
class="bg-white w-80 xs:w-full px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative" class="bg-white w-80 xs:w-full px-1 sm:min-h-[35px] xs:min-h-13 flex items-center relative"
:class="{ :class="{
'!bg-gray-50 !px-0 !select-text': isReadOnlyVirtualCell(col), '!bg-gray-50 !px-0 !select-text nc-system-field': isReadOnlyVirtualCell(col),
}" }"
> >
<LazySmartsheetVirtualCell <LazySmartsheetVirtualCell
@ -924,4 +940,8 @@ export default {
.nc-data-cell:focus-within { .nc-data-cell:focus-within {
@apply !border-1 !border-brand-500 !rounded-lg !shadow-none !ring-0; @apply !border-1 !border-brand-500 !rounded-lg !shadow-none !ring-0;
} }
:deep(.nc-system-field input) {
@apply bg-transparent;
}
</style> </style>

41
packages/nc-gui/components/smartsheet/grid/GroupBy.vue

@ -1,13 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import tinycolor from 'tinycolor2' import tinycolor from 'tinycolor2'
import dayjs from 'dayjs' import { UITypes, dateFormats, parseStringDateTime, timeFormats } from 'nocodb-sdk'
import { UITypes, dateFormats, timeFormats } from 'nocodb-sdk'
import Table from './Table.vue' import Table from './Table.vue'
import GroupBy from './GroupBy.vue' import GroupBy from './GroupBy.vue'
import GroupByTable from './GroupByTable.vue' import GroupByTable from './GroupByTable.vue'
import GroupByLabel from './GroupByLabel.vue' import GroupByLabel from './GroupByLabel.vue'
import { GROUP_BY_VARS, computed, ref } from '#imports'
import type { Group, Row } from '#imports' import type { Group, Row } from '#imports'
import { GROUP_BY_VARS, computed, ref } from '#imports'
const props = defineProps<{ const props = defineProps<{
group: Group group: Group
@ -154,21 +153,23 @@ const parseKey = (group: Group) => {
} }
// show the groupBy dateTime field title format as like cell format // show the groupBy dateTime field title format as like cell format
if (key && group.column?.uidt === UITypes.DateTime && dayjs(key).isValid()) { if (key && group.column?.uidt === UITypes.DateTime) {
const dateFormat = parseProp(group.column?.meta)?.date_format ?? dateFormats[0] return [
const timeFormat = parseProp(group.column?.meta)?.time_format ?? timeFormats[0] parseStringDateTime(
key,
const dateTimeFormat = `${dateFormat} ${timeFormat}` `${parseProp(group.column?.meta)?.date_format ?? dateFormats[0]} ${
parseProp(group.column?.meta)?.time_format ?? timeFormats[0]
return [dayjs(key).utc().local().format(dateTimeFormat)] }`,
),
]
} }
// show the groupBy time field title format as like cell format // show the groupBy time field title format as like cell format
if (key && group.column?.uidt === UITypes.Time && dayjs(key).isValid()) { if (key && group.column?.uidt === UITypes.Time) {
return [dayjs(key).format(timeFormats[0])] return [parseStringDateTime(key, timeFormats[0], false)]
} }
if (key && group.column?.uidt === UITypes.User) { if (key && [UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(group.column?.uidt as UITypes)) {
try { try {
const parsedKey = JSON.parse(key) const parsedKey = JSON.parse(key)
return [parsedKey] return [parsedKey]
@ -181,7 +182,19 @@ const parseKey = (group: Group) => {
} }
const shouldRenderCell = (column) => const shouldRenderCell = (column) =>
[UITypes.Lookup, UITypes.Attachment, UITypes.Barcode, UITypes.QrCode, UITypes.Links, UITypes.User].includes(column?.uidt) [
UITypes.Lookup,
UITypes.Attachment,
UITypes.Barcode,
UITypes.QrCode,
UITypes.Links,
UITypes.User,
UITypes.DateTime,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
].includes(column?.uidt)
</script> </script>
<template> <template>

12
packages/nc-gui/components/smartsheet/grid/GroupByLabel.vue

@ -1,19 +1,27 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { isVirtualCol } from 'nocodb-sdk' import { isVirtualCol } from 'nocodb-sdk'
import { IsGroupByLabelInj, ReadonlyInj } from '#imports'
defineProps<{ defineProps<{
column: ColumnType column: ColumnType
modelValue: any modelValue: any
}>() }>()
provide(ReadonlyInj, true) provide(ReadonlyInj, ref(true))
provide(IsGroupByLabelInj, ref(true))
</script> </script>
<template> <template>
<div class="pointer-events-none"> <div class="pointer-events-none">
<LazySmartsheetRow :row="{ row: { [column.title]: modelValue }, rowMeta: {} }"> <LazySmartsheetRow :row="{ row: { [column.title]: modelValue }, rowMeta: {} }">
<LazySmartsheetVirtualCell v-if="isVirtualCol(column)" :model-value="modelValue" class="!text-gray-600" :column="column" /> <LazySmartsheetVirtualCell
v-if="isVirtualCol(column)"
:model-value="modelValue"
class="!text-gray-600"
:column="column"
:read-only="true"
/>
<LazySmartsheetCell <LazySmartsheetCell
v-else v-else

22
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -1,8 +1,16 @@
<script lang="ts" setup> <script lang="ts" setup>
import axios from 'axios' import axios from 'axios'
import { nextTick } from '@vue/runtime-core' import { nextTick } from '@vue/runtime-core'
import type { ColumnReqType, ColumnType, PaginatedType, TableType, ViewType } from 'nocodb-sdk' import { type ColumnReqType, type ColumnType, type PaginatedType, type TableType, type ViewType } from 'nocodb-sdk'
import { UITypes, ViewTypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' import {
UITypes,
ViewTypes,
isCreatedOrLastModifiedByCol,
isCreatedOrLastModifiedTimeCol,
isLinksOrLTAR,
isSystemColumn,
isVirtualCol,
} from 'nocodb-sdk'
import { useColumnDrag } from './useColumnDrag' import { useColumnDrag } from './useColumnDrag'
import usePaginationShortcuts from './usePaginationShortcuts' import usePaginationShortcuts from './usePaginationShortcuts'
@ -1010,7 +1018,9 @@ const showFillHandle = computed(
!( !(
isLookup(fields.value[activeCell.col]) || isLookup(fields.value[activeCell.col]) ||
isRollup(fields.value[activeCell.col]) || isRollup(fields.value[activeCell.col]) ||
isFormula(fields.value[activeCell.col]) isFormula(fields.value[activeCell.col]) ||
isCreatedOrLastModifiedTimeCol(fields.value[activeCell.col]) ||
isCreatedOrLastModifiedByCol(fields.value[activeCell.col])
), ),
) )
@ -1570,7 +1580,11 @@ onKeyStroke('ArrowDown', onDown)
'align-top': rowHeight && rowHeight !== 1, 'align-top': rowHeight && rowHeight !== 1,
'filling': isCellInFillRange(rowIndex, colIndex), 'filling': isCellInFillRange(rowIndex, colIndex),
'readonly': 'readonly':
(isLookup(columnObj) || isRollup(columnObj) || isFormula(columnObj)) && (isLookup(columnObj) ||
isRollup(columnObj) ||
isFormula(columnObj) ||
isCreatedOrLastModifiedTimeCol(columnObj) ||
isCreatedOrLastModifiedByCol(columnObj)) &&
hasEditPermission && hasEditPermission &&
isCellSelected(rowIndex, colIndex), isCellSelected(rowIndex, colIndex),
'!border-r-blue-400 !border-r-3': toBeDroppedColId === columnObj.id, '!border-r-blue-400 !border-r-3': toBeDroppedColId === columnObj.id,

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

@ -1,6 +1,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ColumnReqType } from 'nocodb-sdk' import type { ColumnReqType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isLinksOrLTAR } from 'nocodb-sdk' import { RelationTypes, UITypes, isLinksOrLTAR } from 'nocodb-sdk'
import { computed } from 'vue'
import { import {
ActiveViewInj, ActiveViewInj,
ColumnInj, ColumnInj,
@ -128,6 +129,7 @@ const duplicateVirtualColumn = async () => {
id: undefined, id: undefined,
colOptions: undefined, colOptions: undefined,
order: undefined, order: undefined,
system: false,
} }
try { try {
@ -165,7 +167,17 @@ const duplicateVirtualColumn = async () => {
const openDuplicateDlg = async () => { const openDuplicateDlg = async () => {
if (!column?.value) return if (!column?.value) return
if (column.value.uidt && [UITypes.Lookup, UITypes.Rollup].includes(column.value.uidt as UITypes)) { if (
column.value.uidt &&
[
UITypes.Lookup,
UITypes.Rollup,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
].includes(column.value.uidt as UITypes)
) {
duplicateVirtualColumn() duplicateVirtualColumn()
} else { } else {
const gridViewColumnList = (await $api.dbViewColumn.list(view.value?.id as string)).list const gridViewColumnList = (await $api.dbViewColumn.list(view.value?.id as string)).list
@ -276,6 +288,13 @@ const onInsertAfter = () => {
isOpen.value = false isOpen.value = false
addColumn() addColumn()
} }
const isDeleteAllowed = computed(() => {
return column?.value && !column.value.system
})
const isDuplicateAllowed = computed(() => {
return column?.value && !column.value.system
})
</script> </script>
<template> <template>
@ -345,7 +364,7 @@ const onInsertAfter = () => {
<a-divider v-if="!column?.pk" class="!my-0" /> <a-divider v-if="!column?.pk" class="!my-0" />
<NcMenuItem v-if="!column?.pk" @click="openDuplicateDlg"> <NcMenuItem v-if="!column?.pk" :disabled="!isDuplicateAllowed" @click="openDuplicateDlg">
<div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item"> <div v-e="['a:field:duplicate']" class="nc-column-duplicate nc-header-menu-item">
<component :is="iconMap.duplicate" class="text-gray-700" /> <component :is="iconMap.duplicate" class="text-gray-700" />
<!-- Duplicate --> <!-- Duplicate -->
@ -368,7 +387,7 @@ const onInsertAfter = () => {
</NcMenuItem> </NcMenuItem>
<a-divider v-if="!column?.pv" class="!my-0" /> <a-divider v-if="!column?.pv" class="!my-0" />
<NcMenuItem v-if="!column?.pv" class="!hover:bg-red-50" @click="handleDelete"> <NcMenuItem v-if="!column?.pv" :disabled="!isDeleteAllowed" class="!hover:bg-red-50" @click="handleDelete">
<div class="nc-column-delete nc-header-menu-item text-red-600"> <div class="nc-column-delete nc-header-menu-item text-red-600">
<component :is="iconMap.delete" /> <component :is="iconMap.delete" />
<!-- Delete --> <!-- Delete -->

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

@ -62,6 +62,12 @@ const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
return { icon: iconMap.rollup, color: 'text-grey' } return { icon: iconMap.rollup, color: 'text-grey' }
case UITypes.Count: case UITypes.Count:
return { icon: CountIcon, color: 'text-grey' } return { icon: CountIcon, color: 'text-grey' }
case UITypes.CreatedTime:
case UITypes.LastModifiedTime:
return { icon: iconMap.datetime, color: 'text-grey' }
case UITypes.CreatedBy:
case UITypes.LastModifiedBy:
return { icon: iconMap.phUser, color: 'text-grey' }
} }
return { icon: iconMap.generic, color: 'text-grey' } return { icon: iconMap.generic, color: 'text-grey' }

18
packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, FilterType } from 'nocodb-sdk' import type { ColumnType, FilterType } from 'nocodb-sdk'
import { PlanLimitTypes, UITypes } from 'nocodb-sdk' import { PlanLimitTypes, UITypes } from 'nocodb-sdk'
import type { Filter } from '#imports'
import { import {
ActiveViewInj, ActiveViewInj,
AllFiltersInj, AllFiltersInj,
@ -17,7 +18,6 @@ import {
useViewFilters, useViewFilters,
watch, watch,
} from '#imports' } from '#imports'
import type { Filter } from '#imports'
interface Props { interface Props {
nestedLevel?: number nestedLevel?: number
@ -143,7 +143,7 @@ const filterUpdateCondition = (filter: FilterType, i: number) => {
// hence remove the previous value // hence remove the previous value
filter.value = null filter.value = null
filter.comparison_sub_op = null filter.comparison_sub_op = null
} else if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes)) { } else if (isDateType(col.uidt as UITypes)) {
// for date / datetime, // for date / datetime,
// the input type could be decimal or datepicker / datetime picker // the input type could be decimal or datepicker / datetime picker
// hence remove the previous value // hence remove the previous value
@ -188,7 +188,7 @@ watch(
() => activeView.value?.id, () => activeView.value?.id,
(n, o) => { (n, o) => {
// if nested no need to reload since it will get reloaded from parent // if nested no need to reload since it will get reloaded from parent
if (!nested.value && n !== o && (hookId?.value || !webHook.value)) loadFilters(hookId?.value) if (!nested.value && n !== o && (hookId?.value || !webHook.value)) loadFilters(hookId?.value, webHook.value)
}, },
) )
@ -241,7 +241,7 @@ const selectFilterField = (filter: Filter, index: number) => {
isComparisonOpAllowed(filter, compOp), isComparisonOpAllowed(filter, compOp),
)?.value as FilterType['comparison_op'] )?.value as FilterType['comparison_op']
if ([UITypes.Date, UITypes.DateTime].includes(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op!)) { if (isDateType(col.uidt as UITypes) && !['blank', 'notblank'].includes(filter.comparison_op!)) {
if (filter.comparison_op === 'isWithin') { if (filter.comparison_op === 'isWithin') {
filter.comparison_sub_op = 'pastNumberOfDays' filter.comparison_sub_op = 'pastNumberOfDays'
} else { } else {
@ -325,7 +325,7 @@ const showFilterInput = (filter: Filter) => {
} }
onMounted(() => { onMounted(() => {
loadFilters(hookId?.value) loadFilters(hookId?.value, webHook.value)
}) })
onMounted(async () => { onMounted(async () => {
@ -335,6 +335,10 @@ onMounted(async () => {
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (parentId.value) delete allFilters.value[parentId.value] if (parentId.value) delete allFilters.value[parentId.value]
}) })
function isDateType(uidt: UITypes) {
return [UITypes.Date, UITypes.DateTime, UITypes.CreatedTime, UITypes.LastModifiedTime].includes(uidt)
}
</script> </script>
<template> <template>
@ -477,7 +481,7 @@ onBeforeUnmount(() => {
<div v-if="['blank', 'notblank'].includes(filter.comparison_op)" class="flex flex-grow"></div> <div v-if="['blank', 'notblank'].includes(filter.comparison_op)" class="flex flex-grow"></div>
<NcSelect <NcSelect
v-else-if="[UITypes.Date, UITypes.DateTime].includes(getColumn(filter)?.uidt)" v-else-if="isDateType(getColumn(filter)?.uidt)"
v-model:value="filter.comparison_sub_op" v-model:value="filter.comparison_sub_op"
v-e="['c:filter:sub-comparison-op:select']" v-e="['c:filter:sub-comparison-op:select']"
:dropdown-match-select-width="false" :dropdown-match-select-width="false"
@ -527,7 +531,7 @@ onBeforeUnmount(() => {
@update-filter-value="(value) => updateFilterValue(value, filter, i)" @update-filter-value="(value) => updateFilterValue(value, filter, i)"
@click.stop @click.stop
/> />
<div v-else-if="![UITypes.Date, UITypes.DateTime].includes(getColumn(filter)?.uidt)" class="flex-grow"></div> <div v-else-if="!isDateType(getColumn(filter)?.uidt)" class="flex-grow"></div>
<NcButton <NcButton
v-if="!filter.readOnly" v-if="!filter.readOnly"

8
packages/nc-gui/components/smartsheet/toolbar/CreateGroupBy.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk' import { RelationTypes, UITypes, isCreatedOrLastModifiedByCol, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
const props = defineProps<{ const props = defineProps<{
// As we need to focus search box when the parent is opened // As we need to focus search box when the parent is opened
@ -39,6 +39,12 @@ const options = computed<ColumnType[]>(
/** ignore virtual fields which are system fields ( mm relation ) and qr code fields */ /** ignore virtual fields which are system fields ( mm relation ) and qr code fields */
return false return false
} }
if (isCreatedOrLastModifiedByCol(c)) {
/** ignore created by and last modified by system field */
return false
}
return showSystemFields.value return showSystemFields.value
} else if (c.uidt === UITypes.QrCode || c.uidt === UITypes.Barcode || c.uidt === UITypes.ID) { } else if (c.uidt === UITypes.QrCode || c.uidt === UITypes.Barcode || c.uidt === UITypes.ID) {
return false return false

7
packages/nc-gui/components/smartsheet/toolbar/CreateSort.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk' import { RelationTypes, UITypes, isCreatedOrLastModifiedByCol, isLinksOrLTAR, isSystemColumn } from 'nocodb-sdk'
const props = defineProps<{ const props = defineProps<{
// As we need to focus search box when the parent is opened // As we need to focus search box when the parent is opened
@ -33,6 +33,11 @@ const options = computed<ColumnType[]>(
return true return true
} }
if (isSystemColumn(metaColumnById?.value?.[c.id!])) { if (isSystemColumn(metaColumnById?.value?.[c.id!])) {
if (isCreatedOrLastModifiedByCol(c)) {
/** ignore created by and last modified by system field */
return false
}
return ( return (
/** hide system columns if not enabled */ /** hide system columns if not enabled */
showSystemFields.value showSystemFields.value

17
packages/nc-gui/components/smartsheet/toolbar/FieldListAutoCompleteDropdown.vue

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { SelectProps } from 'ant-design-vue' import type { SelectProps } from 'ant-design-vue'
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' import { RelationTypes, UITypes, isCreatedOrLastModifiedByCol, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import { MetaInj, computed, inject, ref, resolveComponent, useViewColumnsOrThrow } from '#imports' import { MetaInj, computed, inject, ref, resolveComponent, useViewColumnsOrThrow } from '#imports'
const { modelValue, isSort, allowEmpty, ...restProps } = defineProps<{ const { modelValue, isSort, allowEmpty, ...restProps } = defineProps<{
@ -26,12 +26,25 @@ const { showSystemFields, metaColumnById } = useViewColumnsOrThrow()
const options = computed<SelectProps['options']>(() => const options = computed<SelectProps['options']>(() =>
( (
customColumns.value || customColumns.value?.filter((c: ColumnType) => {
if (isSystemColumn(metaColumnById?.value?.[c.id!])) {
if (isCreatedOrLastModifiedByCol(c)) {
/** ignore created by and last modified by system field */
return false
}
}
return true
}) ||
meta.value?.columns?.filter((c: ColumnType) => { meta.value?.columns?.filter((c: ColumnType) => {
if (c.uidt === UITypes.Links) { if (c.uidt === UITypes.Links) {
return true return true
} }
if (isSystemColumn(metaColumnById?.value?.[c.id!])) { if (isSystemColumn(metaColumnById?.value?.[c.id!])) {
if (isCreatedOrLastModifiedByCol(c)) {
/** ignore created by and last modified by system field */
return false
}
return ( return (
/** if the field is used in filter, then show it anyway */ /** if the field is used in filter, then show it anyway */
localValue.value === c.id || localValue.value === c.id ||

20
packages/nc-gui/components/smartsheet/toolbar/FilterInput.vue

@ -18,6 +18,8 @@ import {
isMultiSelect, isMultiSelect,
isPercent, isPercent,
isRating, isRating,
isReadonlyDateTime,
isReadonlyUser,
isSingleSelect, isSingleSelect,
isTextArea, isTextArea,
isTime, isTime,
@ -68,7 +70,7 @@ provide(EditModeInj, readonly(editEnabled))
provide(ReadonlyInj, ref(false)) provide(ReadonlyInj, ref(false))
const checkTypeFunctions = { const checkTypeFunctions: Record<string, (column: ColumnType, abstractType?: string) => boolean> = {
isSingleSelect, isSingleSelect,
isMultiSelect, isMultiSelect,
isDate, isDate,
@ -80,11 +82,13 @@ const checkTypeFunctions = {
isPercent, isPercent,
isCurrency, isCurrency,
isDecimal, isDecimal,
isReadonlyDateTime,
isInt, isInt,
isFloat, isFloat,
isTextArea, isTextArea,
isLinks: (col: ColumnType) => col.uidt === UITypes.Links, isLinks: (col: ColumnType) => col.uidt === UITypes.Links,
isUser, isUser,
isReadonlyUser,
} }
type FilterType = keyof typeof checkTypeFunctions type FilterType = keyof typeof checkTypeFunctions
@ -102,7 +106,7 @@ const checkType = (filterType: FilterType) => {
return false return false
} }
return checkTypeFunction(column.value, abstractType) return checkTypeFunction(column.value, abstractType.value)
} }
const filterInput = computed({ const filterInput = computed({
@ -142,6 +146,7 @@ const componentMap: Partial<Record<FilterType, any>> = computed(() => {
isDate: renderDateFilterInput(props.filter.comparison_sub_op!), isDate: renderDateFilterInput(props.filter.comparison_sub_op!),
isYear: YearPicker, isYear: YearPicker,
isDateTime: renderDateFilterInput(props.filter.comparison_sub_op!), isDateTime: renderDateFilterInput(props.filter.comparison_sub_op!),
isReadonlyDateTime: renderDateFilterInput(props.filter.comparison_sub_op!),
isTime: TimePicker, isTime: TimePicker,
isRating: Rating, isRating: Rating,
isDuration: Duration, isDuration: Duration,
@ -152,6 +157,7 @@ const componentMap: Partial<Record<FilterType, any>> = computed(() => {
isFloat: Float, isFloat: Float,
isLinks: Integer, isLinks: Integer,
isUser: User, isUser: User,
isReadonlyUser: User,
} }
}) })
@ -178,6 +184,12 @@ const componentProps = computed(() => {
case 'isUser': { case 'isUser': {
return { forceMulti: true } return { forceMulti: true }
} }
case 'isReadonlyUser': {
if (['anyof', 'nanyof'].includes(props.filter.comparison_op!)) {
return { forceMulti: true }
}
return {}
}
default: { default: {
return {} return {}
} }
@ -212,7 +224,7 @@ provide(IsFormInj, ref(true))
/> />
<div <div
v-else v-else
class="bg-white border-1 flex flex-grow min-h-4 h-full items-center nc-filter-input-wrapper !rounded-lg" class="bg-white border-1 flex flex-grow min-h-4 h-full px-1 items-center nc-filter-input-wrapper !rounded-lg"
:class="{ 'px-2': hasExtraPadding, 'border-brand-500': isInputBoxOnFocus }" :class="{ 'px-2': hasExtraPadding, 'border-brand-500': isInputBoxOnFocus }"
@mouseup.stop @mouseup.stop
> >
@ -222,7 +234,7 @@ provide(IsFormInj, ref(true))
:disabled="filter.readOnly" :disabled="filter.readOnly"
placeholder="Enter a value" placeholder="Enter a value"
:column="column" :column="column"
class="flex" class="flex !rounded-lg"
v-bind="componentProps" v-bind="componentProps"
location="filter" location="filter"
@focus="isInputBoxOnFocus = true" @focus="isInputBoxOnFocus = true"

2
packages/nc-gui/components/tabs/Smartsheet.vue

@ -87,7 +87,7 @@ const onDrop = async (event: DragEvent) => {
event.preventDefault() event.preventDefault()
try { try {
// Access the dropped data // Access the dropped data
const data = JSON.parse(event.dataTransfer!.getData('text/json')) const data = JSON.parse(event.dataTransfer!.getData('text/json') || '{}')
// Do something with the received data // Do something with the received data
// if dragged item is not from the same source, return // if dragged item is not from the same source, return

13
packages/nc-gui/components/template/Editor.vue

@ -100,9 +100,16 @@ const uiTypeOptions = ref<Option[]>(
.filter( .filter(
(uiType) => (uiType) =>
!isVirtualCol(UITypes[uiType]) && !isVirtualCol(UITypes[uiType]) &&
![UITypes.ForeignKey, UITypes.ID, UITypes.CreateTime, UITypes.LastModifiedTime, UITypes.Barcode, UITypes.Button].includes( ![
UITypes[uiType], UITypes.ForeignKey,
), UITypes.ID,
UITypes.CreatedTime,
UITypes.LastModifiedTime,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
UITypes.Barcode,
UITypes.Button,
].includes(UITypes[uiType]),
) )
.map<Option>((uiType) => ({ .map<Option>((uiType) => ({
value: uiType, value: uiType,

3
packages/nc-gui/components/virtual-cell/Formula.vue

@ -11,7 +11,6 @@ import {
renderValue, renderValue,
replaceUrlsWithLink, replaceUrlsWithLink,
useBase, useBase,
useGlobal,
} from '#imports' } from '#imports'
// todo: column type doesn't have required property `error` - throws in typecheck // todo: column type doesn't have required property `error` - throws in typecheck
@ -23,8 +22,6 @@ const isExpandedFormOpen = inject(IsExpandedFormOpenInj, ref(false))!
const { isPg } = useBase() const { isPg } = useBase()
const { showNull } = useGlobal()
const result = computed(() => const result = computed(() =>
isPg(column.value.source_id) ? renderValue(handleTZ(cellValue?.value)) : renderValue(cellValue?.value), isPg(column.value.source_id) ? renderValue(handleTZ(cellValue?.value)) : renderValue(cellValue?.value),
) )

6
packages/nc-gui/components/virtual-cell/Lookup.vue

@ -25,6 +25,8 @@ const meta = inject(MetaInj, ref())
const cellValue = inject(CellValueInj, ref()) const cellValue = inject(CellValueInj, ref())
const isGroupByLabel = inject(IsGroupByLabelInj, ref(false))
// Change the row height of the child cell under lookup // Change the row height of the child cell under lookup
// Other wise things like text will can take multi line tag // Other wise things like text will can take multi line tag
const providedHeightRef = ref(1) as any const providedHeightRef = ref(1) as any
@ -100,7 +102,7 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
<div <div
class="h-full w-full nc-lookup-cell" class="h-full w-full nc-lookup-cell"
tabindex="-1" tabindex="-1"
:style="{ height: rowHeight && rowHeight !== 1 ? `${rowHeight * 2}rem` : `2.85rem` }" :style="{ height: isGroupByLabel ? undefined : rowHeight && rowHeight !== 1 ? `${rowHeight * 2}rem` : `2.85rem` }"
@dblclick="activateShowEditNonEditableFieldWarning" @dblclick="activateShowEditNonEditableFieldWarning"
> >
<div <div
@ -169,6 +171,8 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
UITypes.MultiSelect, UITypes.MultiSelect,
UITypes.SingleSelect, UITypes.SingleSelect,
UITypes.User, UITypes.User,
UITypes.CreatedBy,
UITypes.LastModifiedBy,
].includes(lookupColumn.uidt), ].includes(lookupColumn.uidt),
'min-h-0 min-w-0': isAttachment(lookupColumn), 'min-h-0 min-w-0': isAttachment(lookupColumn),
}" }"

7
packages/nc-gui/components/virtual-cell/components/ListChildItems.vue

@ -49,15 +49,15 @@ const {
loadChildrenList, loadChildrenList,
childrenListPagination, childrenListPagination,
relatedTableDisplayValueProp, relatedTableDisplayValueProp,
displayValueTypeAndFormatProp,
unlink, unlink,
isChildrenListLoading, isChildrenListLoading,
isChildrenListLinked, isChildrenListLinked,
isChildrenLoading, isChildrenLoading,
relatedTableMeta, relatedTableMeta,
row,
link, link,
meta, meta,
displayValueProp, headerDisplayValue,
} = useLTARStoreOrThrow() } = useLTARStoreOrThrow()
const { isNew, state, removeLTARRef, addLTARRef } = useSmartsheetRowStoreOrThrow() const { isNew, state, removeLTARRef, addLTARRef } = useSmartsheetRowStoreOrThrow()
@ -195,7 +195,7 @@ const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
:table-title="meta?.title" :table-title="meta?.title"
:header="$t('activity.linkedRecords')" :header="$t('activity.linkedRecords')"
:related-table-title="relatedTableMeta?.title" :related-table-title="relatedTableMeta?.title"
:display-value="row.row[displayValueProp]" :display-value="headerDisplayValue"
/> />
<div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2"> <div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2">
<div <div
@ -260,6 +260,7 @@ const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
data-testid="nc-child-list-item" data-testid="nc-child-list-item"
:attachment="attachmentCol" :attachment="attachmentCol"
:related-table-display-value-prop="relatedTableDisplayValueProp" :related-table-display-value-prop="relatedTableDisplayValueProp"
:display-value-type-and-format-prop="displayValueTypeAndFormatProp"
:is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true" :is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true"
:is-loading="isChildrenListLoading[Number.parseInt(id)]" :is-loading="isChildrenListLoading[Number.parseInt(id)]"
@expand="onClick(refRow)" @expand="onClick(refRow)"

22
packages/nc-gui/components/virtual-cell/components/ListItem.vue

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { isVirtualCol } from 'nocodb-sdk' import { UITypes, isVirtualCol, parseStringDateTime } from 'nocodb-sdk'
import { import {
type ComputedRef, type ComputedRef,
IsExpandedFormOpenInj, IsExpandedFormOpenInj,
@ -22,6 +22,7 @@ const props = defineProps<{
fields: any[] fields: any[]
attachment: any attachment: any
relatedTableDisplayValueProp: string relatedTableDisplayValueProp: string
displayValueTypeAndFormatProp: { type: string; format: string }
isLoading: boolean isLoading: boolean
isLinked: boolean isLinked: boolean
}>() }>()
@ -68,6 +69,21 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
return [] return []
} }
}) })
const displayValue = computed(() => {
if (
row.value[props.relatedTableDisplayValueProp] &&
props.displayValueTypeAndFormatProp.type &&
props.displayValueTypeAndFormatProp.format
) {
return parseStringDateTime(
row.value[props.relatedTableDisplayValueProp],
props.displayValueTypeAndFormatProp.format,
!(props.displayValueTypeAndFormatProp.format === UITypes.Time),
)
}
return row.value[props.relatedTableDisplayValueProp]
})
</script> </script>
<template> <template>
@ -103,8 +119,8 @@ const attachments: ComputedRef<Attachment[]> = computed(() => {
<div class="flex flex-col m-[.75rem] gap-1 flex-grow justify-center overflow-hidden"> <div class="flex flex-col m-[.75rem] gap-1 flex-grow justify-center overflow-hidden">
<div class="flex justify-between xs:gap-x-2"> <div class="flex justify-between xs:gap-x-2">
<span class="font-semibold text-gray-800 nc-display-value xs:(truncate)"> <span class="font-semibold text-brand-500 nc-display-value xs:(truncate)">
{{ row[relatedTableDisplayValueProp] }} {{ displayValue }}
</span> </span>
<div <div
v-if="isLinked && !isLoading" v-if="isLinked && !isLoading"

6
packages/nc-gui/components/virtual-cell/components/ListItems.vue

@ -37,18 +37,19 @@ const {
childrenExcludedList, childrenExcludedList,
isChildrenExcludedListLinked, isChildrenExcludedListLinked,
isChildrenExcludedListLoading, isChildrenExcludedListLoading,
displayValueProp,
isChildrenExcludedLoading, isChildrenExcludedLoading,
childrenListCount, childrenListCount,
loadChildrenExcludedList, loadChildrenExcludedList,
loadChildrenList, loadChildrenList,
childrenExcludedListPagination, childrenExcludedListPagination,
relatedTableDisplayValueProp, relatedTableDisplayValueProp,
displayValueTypeAndFormatProp,
link, link,
relatedTableMeta, relatedTableMeta,
meta, meta,
unlink, unlink,
row, row,
headerDisplayValue,
} = useLTARStoreOrThrow() } = useLTARStoreOrThrow()
const { addLTARRef, isNew, removeLTARRef, state: rowState } = useSmartsheetRowStoreOrThrow() const { addLTARRef, isNew, removeLTARRef, state: rowState } = useSmartsheetRowStoreOrThrow()
@ -235,7 +236,7 @@ const onCreatedRecord = (record: any) => {
:relation="relation" :relation="relation"
:table-title="meta?.title" :table-title="meta?.title"
:related-table-title="relatedTableMeta?.title" :related-table-title="relatedTableMeta?.title"
:display-value="row.row[displayValueProp]" :display-value="headerDisplayValue"
:header="$t('activity.addNewLink')" :header="$t('activity.addNewLink')"
/> />
<div class="flex mt-2 mb-2 items-center gap-2"> <div class="flex mt-2 mb-2 items-center gap-2">
@ -315,6 +316,7 @@ const onCreatedRecord = (record: any) => {
:fields="fields" :fields="fields"
:attachment="attachmentCol" :attachment="attachmentCol"
:related-table-display-value-prop="relatedTableDisplayValueProp" :related-table-display-value-prop="relatedTableDisplayValueProp"
:display-value-type-and-format-prop="displayValueTypeAndFormatProp"
:is-loading="isChildrenExcludedListLoading[Number.parseInt(id)]" :is-loading="isChildrenExcludedListLoading[Number.parseInt(id)]"
:is-linked="isChildrenExcludedListLinked[Number.parseInt(id)]" :is-linked="isChildrenExcludedListLinked[Number.parseInt(id)]"
@expand=" @expand="

35
packages/nc-gui/components/workspace/CollaboratorsList.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import { OrderedWorkspaceRoles, WorkspaceUserRoles, parseStringDateTime, timeAgo } from 'nocodb-sdk' import { OrderedWorkspaceRoles, WorkspaceUserRoles, parseStringDateTime, timeAgo } from 'nocodb-sdk'
import { storeToRefs, useWorkspace } from '#imports' import { storeToRefs, useUserSorts, useWorkspace } from '#imports'
const { workspaceRoles, loadRoles } = useRoles() const { workspaceRoles, loadRoles } = useRoles()
@ -9,6 +9,9 @@ const workspaceStore = useWorkspace()
const { removeCollaborator, updateCollaborator: _updateCollaborator } = workspaceStore const { removeCollaborator, updateCollaborator: _updateCollaborator } = workspaceStore
const { collaborators } = storeToRefs(workspaceStore) const { collaborators } = storeToRefs(workspaceStore)
const { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData } = useUserSorts('Workspace')
const userSearchText = ref('') const userSearchText = ref('')
const filterCollaborators = computed(() => { const filterCollaborators = computed(() => {
@ -19,11 +22,20 @@ const filterCollaborators = computed(() => {
return collaborators.value.filter((collab) => collab.email!.includes(userSearchText.value)) return collaborators.value.filter((collab) => collab.email!.includes(userSearchText.value))
}) })
const sortedCollaborators = computed(() => {
return handleGetSortedData(filterCollaborators.value, sorts.value)
})
const updateCollaborator = async (collab: any, roles: WorkspaceUserRoles) => { const updateCollaborator = async (collab: any, roles: WorkspaceUserRoles) => {
collab.roles = roles
try { try {
await _updateCollaborator(collab.id, collab.roles) await _updateCollaborator(collab.id, roles)
message.success('Successfully updated user role') message.success('Successfully updated user role')
collaborators.value?.forEach((collaborator) => {
if (collaborator.id === collab.id) {
collaborator.roles = roles
}
})
} catch (e: any) { } catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} }
@ -39,6 +51,7 @@ const accessibleRoles = computed<WorkspaceUserRoles[]>(() => {
onMounted(async () => { onMounted(async () => {
await loadRoles() await loadRoles()
loadSorts()
}) })
</script> </script>
@ -59,15 +72,25 @@ onMounted(async () => {
<div v-else class="nc-collaborators-list mt-6 h-full"> <div v-else class="nc-collaborators-list mt-6 h-full">
<div class="flex flex-col rounded-lg overflow-hidden border-1 max-w-350 max-h-[calc(100%-8rem)]"> <div class="flex flex-col rounded-lg overflow-hidden border-1 max-w-350 max-h-[calc(100%-8rem)]">
<div class="flex flex-row bg-gray-50 min-h-12 items-center"> <div class="flex flex-row bg-gray-50 min-h-12 items-center">
<div class="text-gray-700 users-email-grid w-3/8 ml-10 mr-3">{{ $t('objects.users') }}</div> <div class="text-gray-700 users-email-grid w-3/8 ml-10 mr-3 flex items-center space-x-2">
<div class="text-gray-700 user-access-grid w-2/8 mr-3">{{ $t('general.access') }}</div> <span>
{{ $t('objects.users') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.email" field="email" :handle-user-sort="saveOrUpdate" />
</div>
<div class="text-gray-700 user-access-grid w-2/8 mr-3 flex items-center space-x-2">
<span>
{{ $t('general.access') }}
</span>
<LazyAccountUserMenu :direction="sortDirection.roles" field="roles" :handle-user-sort="saveOrUpdate" />
</div>
<div class="text-gray-700 date-joined-grid w-2/8 mr-3">{{ $t('title.dateJoined') }}</div> <div class="text-gray-700 date-joined-grid w-2/8 mr-3">{{ $t('title.dateJoined') }}</div>
<div class="text-gray-700 user-access-grid w-1/8">Actions</div> <div class="text-gray-700 user-access-grid w-1/8">Actions</div>
</div> </div>
<div class="flex flex-col nc-scrollbar-md"> <div class="flex flex-col nc-scrollbar-md">
<div <div
v-for="(collab, i) of filterCollaborators" v-for="(collab, i) of sortedCollaborators"
:key="i" :key="i"
class="flex flex-row border-b-1 py-1 min-h-14 items-center justify-around last" class="flex flex-row border-b-1 py-1 min-h-14 items-center justify-around last"
> >

6
packages/nc-gui/composables/useApi/interceptors.ts

@ -84,11 +84,15 @@ export function addAxiosInterceptors(api: Api<any>) {
// ignore since it could have already been handled and redirected to sign in // ignore since it could have already been handled and redirected to sign in
}) })
} else { } else {
// if
refreshTokenPromise = new Promise<string>((resolve, reject) => { refreshTokenPromise = new Promise<string>((resolve, reject) => {
refreshTokenPromiseRes = resolve refreshTokenPromiseRes = resolve
refreshTokenPromiseRej = reject refreshTokenPromiseRej = reject
}) })
// set a catch on the promise to avoid unhandled promise rejection
refreshTokenPromise.catch(() => {
// ignore
})
} }
// Try request again with new token // Try request again with new token

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

@ -113,6 +113,18 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
{ {
validator: (rule: any, value: any) => { validator: (rule: any, value: any) => {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
if (
(tableExplorerColumns?.value || meta.value?.columns)?.some(
(c) =>
c.id !== formState.value.id && // ignore current column
// compare against column_name and title
((value || '').toLowerCase() === (c.column_name || '').toLowerCase() ||
(value || '').toLowerCase() === (c.title || '').toLowerCase()) &&
c.system,
)
) {
return reject(new Error(t('msg.error.duplicateSystemColumnName')))
}
if ( if (
(tableExplorerColumns?.value || meta.value?.columns)?.some( (tableExplorerColumns?.value || meta.value?.columns)?.some(
(c) => (c) =>

63
packages/nc-gui/composables/useData.ts

@ -1,5 +1,5 @@
import { UITypes } from 'nocodb-sdk'
import type { ColumnType, LinkToAnotherRecordType, PaginatedType, RelationTypes, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, PaginatedType, RelationTypes, TableType, ViewType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import { import {
NOCO, NOCO,
@ -236,14 +236,18 @@ export function useData(args: {
toUpdate.row, toUpdate.row,
metaValue!.columns!.reduce<Record<string, any>>((acc: Record<string, any>, col: ColumnType) => { metaValue!.columns!.reduce<Record<string, any>>((acc: Record<string, any>, col: ColumnType) => {
if ( if (
col.uidt === UITypes.Formula || col.title in updatedRowData &&
col.uidt === UITypes.QrCode || (col.uidt === UITypes.Formula ||
col.uidt === UITypes.Barcode || col.uidt === UITypes.QrCode ||
col.uidt === UITypes.Rollup || col.uidt === UITypes.Barcode ||
col.uidt === UITypes.Checkbox || col.uidt === UITypes.Rollup ||
col.uidt === UITypes.User || col.uidt === UITypes.Checkbox ||
col.au || col.uidt === UITypes.User ||
col.cdf?.includes(' on update ') col.uidt === UITypes.LastModifiedTime ||
col.uidt === UITypes.LastModifiedBy ||
col.uidt === UITypes.Lookup ||
col.au ||
(col.cdf && / on update /i.test(col.cdf)))
) )
acc[col.title!] = updatedRowData[col.title!] acc[col.title!] = updatedRowData[col.title!]
return acc return acc
@ -376,33 +380,26 @@ export function useData(args: {
} }
for (const row of rows) { for (const row of rows) {
if (!undo) {
/** update row data(to sync formula and other related columns)
* update only formula, rollup and auto updated datetime columns data to avoid overwriting any changes made by user
*/
Object.assign(
row.row,
metaValue!.columns!.reduce<Record<string, any>>((acc: Record<string, any>, col: ColumnType) => {
if (
col.uidt === UITypes.Formula ||
col.uidt === UITypes.QrCode ||
col.uidt === UITypes.Barcode ||
col.uidt === UITypes.Rollup ||
col.uidt === UITypes.Checkbox ||
col.uidt === UITypes.User ||
col.au ||
col.cdf?.includes(' on update ')
)
acc[col.title!] = row.row[col.title!]
return acc
}, {} as Record<string, any>),
)
Object.assign(row.oldRow, row.row)
}
if (row.rowMeta) row.rowMeta.saving = false if (row.rowMeta) row.rowMeta.saving = false
} }
// reload data since row update may change the other columns data( Formula, QrCode, Barcode, Rollup, Checkbox, User, Auto Updated Datetime, etc...)
if (
metaValue!.columns!.some((col) =>
[
UITypes.QrCode,
UITypes.LastModifiedTime,
UITypes.Barcode,
UITypes.Formula,
UITypes.Lookup,
UITypes.Rollup,
UITypes.LinkToAnotherRecord,
UITypes.LastModifiedBy,
].includes(col.uidt),
)
) {
await callbacks?.loadData?.()
}
await callbacks?.globalCallback?.() await callbacks?.globalCallback?.()
} }

19
packages/nc-gui/composables/useExpandedFormStore.ts

@ -1,4 +1,4 @@
import { UITypes, ViewTypes } from 'nocodb-sdk' import { UITypes, ViewTypes, isVirtualCol } from 'nocodb-sdk'
import type { AuditType, ColumnType, TableType } from 'nocodb-sdk' import type { AuditType, ColumnType, TableType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
@ -295,8 +295,8 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
changedColumns.value = new Set() changedColumns.value = new Set()
} }
const loadRow = async (rowId?: string) => { const loadRow = async (rowId?: string, onlyVirtual = false) => {
const record = await $api.dbTableRow.read( let record = await $api.dbTableRow.read(
NOCO, NOCO,
// todo: base_id missing on view type // todo: base_id missing on view type
(base?.value?.id || (sharedView.value?.view as any)?.base_id) as string, (base?.value?.id || (sharedView.value?.view as any)?.base_id) as string,
@ -307,6 +307,19 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
}, },
) )
// update only virtual columns value if `onlyVirtual` is true
if (onlyVirtual) {
record = {
...row.value.row,
...meta.value.columns.reduce((partialRecord, col) => {
if (isVirtualCol(col) && col.title in record) {
partialRecord[col.title] = record[col.title]
}
return partialRecord
}, {} as Record<string, any>),
}
}
Object.assign(row.value, { Object.assign(row.value, {
row: record, row: record,
oldRow: { ...record }, oldRow: { ...record },

2
packages/nc-gui/composables/useGlobal/index.ts

@ -66,6 +66,8 @@ export const useGlobal = createGlobalState((): UseGlobalReturn => {
(nextPayload) => { (nextPayload) => {
if (nextPayload) { if (nextPayload) {
state.user.value = { state.user.value = {
// keep existing props if user id same as before
...(state.user.value?.id === nextPayload.id ? state.user.value || {} : {}),
id: nextPayload.id, id: nextPayload.id,
email: nextPayload.email, email: nextPayload.email,
firstname: nextPayload.firstname, firstname: nextPayload.firstname,

57
packages/nc-gui/composables/useLTARStore.ts

@ -1,4 +1,14 @@
import type { ColumnType, LinkToAnotherRecordType, PaginatedType, RequestParams, TableType } from 'nocodb-sdk' import {
type ColumnType,
type LinkToAnotherRecordType,
type PaginatedType,
type RequestParams,
type TableType,
UITypes,
dateFormats,
parseStringDateTime,
timeFormats,
} from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import { import {
IsPublicInj, IsPublicInj,
@ -9,6 +19,7 @@ import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
inject, inject,
message, message,
parseProp,
reactive, reactive,
ref, ref,
storeToRefs, storeToRefs,
@ -124,10 +135,52 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
const relatedTablePrimaryKeyProps = computed(() => { const relatedTablePrimaryKeyProps = computed(() => {
return relatedTableMeta.value?.columns?.filter((c) => c.pk)?.map((c) => c.title) ?? [] return relatedTableMeta.value?.columns?.filter((c) => c.pk)?.map((c) => c.title) ?? []
}) })
const displayValueProp = computed(() => { const displayValueProp = computed(() => {
return (meta.value?.columns?.find((c: Required<ColumnType>) => c.pv) || relatedTableMeta?.value?.columns?.[0])?.title return (meta.value?.columns?.find((c: Required<ColumnType>) => c.pv) || relatedTableMeta?.value?.columns?.[0])?.title
}) })
const displayValueTypeAndFormatProp = computed(() => {
let displayValueTypeAndFormat = {
type: '',
format: '',
}
const currentColumn = relatedTableMeta.value?.columns?.find((c) => c.pv) || relatedTableMeta?.value?.columns?.[0]
if (currentColumn) {
if (currentColumn?.uidt === UITypes.DateTime) {
displayValueTypeAndFormat = {
type: currentColumn?.uidt,
format: `${parseProp(currentColumn?.meta)?.date_format ?? dateFormats[0]} ${
parseProp(currentColumn?.meta)?.time_format ?? timeFormats[0]
}`,
}
}
if (currentColumn?.uidt === UITypes.Time) {
displayValueTypeAndFormat = {
type: currentColumn?.uidt,
format: `${timeFormats[0]}`,
}
}
}
return displayValueTypeAndFormat
})
const headerDisplayValue = computed(() => {
if (
row.value.row[displayValueProp.value] &&
displayValueTypeAndFormatProp.value.type &&
displayValueTypeAndFormatProp.value.format
) {
return parseStringDateTime(
row.value.row[displayValueProp.value],
displayValueTypeAndFormatProp.value.format,
!(displayValueTypeAndFormatProp.value.format === UITypes.Time),
)
}
return row.value.row[displayValueProp.value]
})
const loadChildrenExcludedList = async (activeState?: any) => { const loadChildrenExcludedList = async (activeState?: any) => {
if (activeState) newRowState.state = activeState if (activeState) newRowState.state = activeState
try { try {
@ -470,6 +523,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
relatedTableMeta, relatedTableMeta,
loadRelatedTableMeta, loadRelatedTableMeta,
relatedTableDisplayValueProp, relatedTableDisplayValueProp,
displayValueTypeAndFormatProp,
childrenExcludedList, childrenExcludedList,
childrenList, childrenList,
childrenListCount, childrenListCount,
@ -491,6 +545,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
isChildrenExcludedLoading, isChildrenExcludedLoading,
deleteRelatedRow, deleteRelatedRow,
getRelatedTableRowId, getRelatedTableRowId,
headerDisplayValue,
} }
}, },
'ltar-store', 'ltar-store',

4
packages/nc-gui/composables/useMultiSelect/convertCellData.ts

@ -195,7 +195,9 @@ export default function convertCellData(
return validVals.join(',') return validVals.join(',')
} }
case UITypes.User: { case UITypes.User:
case UITypes.CreatedBy:
case UITypes.LastModifiedBy: {
let parsedVal let parsedVal
try { try {
try { try {

20
packages/nc-gui/composables/useMultiSelect/index.ts

@ -1,6 +1,6 @@
import type { Ref } from 'vue'
import { computed } from 'vue' import { computed } from 'vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import type { Ref } from 'vue'
import type { MaybeRef } from '@vueuse/core' import type { MaybeRef } from '@vueuse/core'
import type { ColumnType, LinkToAnotherRecordType, TableType, UserFieldRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, TableType, UserFieldRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes, dateFormats, isDateMonthFormat, isSystemColumn, isVirtualCol, timeFormats } from 'nocodb-sdk' import { RelationTypes, UITypes, dateFormats, isDateMonthFormat, isSystemColumn, isVirtualCol, timeFormats } from 'nocodb-sdk'
@ -112,13 +112,15 @@ export function useMultiSelect(
textToCopy = !!textToCopy textToCopy = !!textToCopy
} }
if (columnObj.uidt === UITypes.User) { if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(columnObj.uidt as UITypes)) {
if (textToCopy && Array.isArray(textToCopy)) { if (textToCopy) {
textToCopy = textToCopy textToCopy = Array.isArray(textToCopy)
.map((user: UserFieldRecordType) => { ? textToCopy
return user.email : [textToCopy]
}) .map((user: UserFieldRecordType) => {
.join(', ') return user.email
})
.join(', ')
} }
} }
@ -136,7 +138,7 @@ export function useMultiSelect(
}) })
} }
if (columnObj.uidt === UITypes.DateTime) { if ([UITypes.DateTime, UITypes.CreatedTime, UITypes.LastModifiedTime].includes(columnObj.uidt)) {
// remove `"` // remove `"`
// e.g. "2023-05-12T08:03:53.000Z" -> 2023-05-12T08:03:53.000Z // e.g. "2023-05-12T08:03:53.000Z" -> 2023-05-12T08:03:53.000Z
textToCopy = textToCopy.replace(/["']/g, '') textToCopy = textToCopy.replace(/["']/g, '')

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

@ -21,7 +21,7 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState(
const { sqlUis } = storeToRefs(baseStore) const { sqlUis } = storeToRefs(baseStore)
const sqlUi = ref( const sqlUi = computed(() =>
(meta.value as TableType)?.source_id ? sqlUis.value[(meta.value as TableType).source_id!] : Object.values(sqlUis.value)[0], (meta.value as TableType)?.source_id ? sqlUis.value[(meta.value as TableType).source_id!] : Object.values(sqlUis.value)[0],
) )

177
packages/nc-gui/composables/useUserSorts.ts

@ -0,0 +1,177 @@
import rfdc from 'rfdc'
import { OrderedOrgRoles, OrderedProjectRoles, OrderedWorkspaceRoles } from 'nocodb-sdk'
import type { UsersSortType } from '~/lib'
import { useGlobal } from '#imports'
/**
* Hook for managing user sorts and sort configurations.
*
* @param {string} roleType - The type of role for which user sorts are managed ('Workspace', 'Org', or 'Project').
* @returns {object} An object containing reactive values and functions related to user sorts.
*/
export function useUserSorts(roleType: 'Workspace' | 'Org' | 'Project') {
const clone = rfdc()
const { user } = useGlobal()
const sorts = ref<UsersSortType>({})
// Key for storing user sort configurations in local storage
const userSortConfigKey = 'userSortConfig'
// Default user ID if no user found (fallback)
const defaultUserId = 'default'
/**
* Computed property that returns a record of sort directions based on the current sort configurations.
* @type {ComputedRef<Record<string, UsersSortType['direction']>>}
*/
const sortDirection: ComputedRef<Record<string, UsersSortType['direction']>> = computed(() => {
if (sorts.value.field) {
return { [sorts.value.field]: sorts.value.direction } as Record<string, UsersSortType['direction']>
}
return {} as Record<string, UsersSortType['direction']>
})
/**
* Loads user sort configurations from local storage based on the current user ID.
*/
function loadSorts(): void {
try {
// Retrieve sort configuration from local storage
const storedConfig = localStorage.getItem(userSortConfigKey)
const sortConfig = storedConfig ? JSON.parse(storedConfig) : ({} as Record<string, UsersSortType>)
if (sortConfig && isValidSortConfig(sortConfig)) {
// Load user-specific sort configurations or default configurations
sorts.value = user.value?.id ? sortConfig[user.value.id] || {} : sortConfig[defaultUserId] || {}
} else {
throw new Error('Invalid sort config stored in local storage')
}
} catch (error) {
console.error(error)
// remove sortConfig from localStorage in case of error
localStorage.removeItem(userSortConfigKey)
// Set sorts to an empty obj in case of an error
sorts.value = {}
}
}
/**
* Saves or updates a user sort configuration and updates local storage.
* @param {UsersSortType} newSortConfig - The new sort configuration to save or update.
*/
function saveOrUpdate(newSortConfig: UsersSortType): void {
try {
if (newSortConfig.field && newSortConfig.direction) {
sorts.value = { ...newSortConfig }
} else {
sorts.value = {}
}
// Update local storage with the new sort configurations
const storedConfig = localStorage.getItem(userSortConfigKey)
const sortConfig = storedConfig ? JSON.parse(storedConfig) : {}
if (user.value?.id) {
// Save or delete user-specific sort configurations
if (sorts.value.field) {
sortConfig[user.value.id] = sorts.value
} else {
delete sortConfig[user.value.id]
}
} else {
// Save or delete default user sort configurations
sortConfig[defaultUserId] = sorts.value
}
localStorage.setItem(userSortConfigKey, JSON.stringify(sortConfig))
} catch (error) {
console.error('Error while saving sort configuration into local storage:', error)
}
}
/**
* Sorts and returns a deep copy of an array of objects based on the provided sort configurations.
*
* @param data - The array of objects to be sorted.
* @param sortsConfig - The object of sort configurations.
* @returns A new array containing sorted objects.
* @template T - The type of objects in the input array.
*/
function handleGetSortedData<T extends Record<string, any>>(data: T[], sortsConfig: UsersSortType = sorts.value): T[] {
let userRoleOrder: string[] = []
if (roleType === 'Workspace') {
userRoleOrder = Object.values(OrderedWorkspaceRoles)
} else if (roleType === 'Org') {
userRoleOrder = Object.values(OrderedOrgRoles)
} else if (roleType === 'Project') {
userRoleOrder = Object.values(OrderedProjectRoles)
}
data = clone(data)
const superUserIndex = data.findIndex((user) => user?.roles?.includes('super'))
const superUser = sortsConfig.field === 'roles' && superUserIndex !== -1 ? data.splice(superUserIndex, 1) : null
let sortedData = data.sort((a, b) => {
switch (sortsConfig.field) {
case 'roles': {
const roleA = a?.roles?.split(',')[0]
const roleB = b?.roles?.split(',')[0]
if (sortsConfig.direction === 'asc') {
return userRoleOrder.indexOf(roleA) - userRoleOrder.indexOf(roleB)
} else {
return userRoleOrder.indexOf(roleB) - userRoleOrder.indexOf(roleA)
}
}
case 'email': {
if (sortsConfig.direction === 'asc') {
return a[sortsConfig.field]?.localeCompare(b[sortsConfig.field])
} else {
return b[sortsConfig.field]?.localeCompare(a[sortsConfig.field])
}
}
}
return 0
})
if (superUser && superUser.length) {
if (sortsConfig.direction === 'desc') {
sortedData = [...sortedData, superUser[0]]
} else {
sortedData = [superUser[0], ...sortedData]
}
}
return sortedData
}
/**
* Checks if the provided sort configuration has the expected structure.
* @param sortConfig - The sort configuration to validate.
* @param expectedStructure - The expected structure for the sort configuration.
* Defaults to { field: 'email', direction: 'asc' }.
* @returns `true` if the sort configuration is valid, otherwise `false`.
*/
function isValidSortConfig(
sortConfig: Record<string, any>,
expectedStructure: UsersSortType = { field: 'email', direction: 'asc' },
): boolean {
// Check if the sortConfig has the expected keys
for (const key in sortConfig) {
const isValidConfig = Object.keys(sortConfig[key]).every((key) =>
Object.prototype.hasOwnProperty.call(expectedStructure, key),
)
if (!isValidConfig) return false
}
return true
}
return { sorts, sortDirection, loadSorts, saveOrUpdate, handleGetSortedData }
}

9
packages/nc-gui/composables/useViewColumns.ts

@ -1,4 +1,4 @@
import { ViewTypes, isSystemColumn } from 'nocodb-sdk' import { ViewTypes, isCreatedOrLastModifiedByCol, isSystemColumn } from 'nocodb-sdk'
import type { ColumnType, GridColumnReqType, GridColumnType, MapType, TableType, ViewType } from 'nocodb-sdk' import type { ColumnType, GridColumnReqType, GridColumnType, MapType, TableType, ViewType } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue' import type { ComputedRef, Ref } from 'vue'
import { computed, ref, storeToRefs, useBase, useNuxtApp, useRoles, useUndoRedo, watch } from '#imports' import { computed, ref, storeToRefs, useBase, useNuxtApp, useRoles, useUndoRedo, watch } from '#imports'
@ -70,7 +70,12 @@ const [useProvideViewColumns, useViewColumns] = useInjectionState(
}, {}) }, {})
fields.value = meta.value?.columns fields.value = meta.value?.columns
?.map((column: ColumnType) => { ?.filter((column: ColumnType) => {
// filter created by and last modified by system columns
if (isCreatedOrLastModifiedByCol(column) && column.system) return false
return true
})
.map((column: ColumnType) => {
const currentColumnField = fieldById[column.id!] || {} const currentColumnField = fieldById[column.id!] || {}
return { return {

14
packages/nc-gui/composables/useViewData.ts

@ -211,7 +211,8 @@ export function useViewData(
if (error.code === 'ERR_CANCELED') { if (error.code === 'ERR_CANCELED') {
return return
} }
throw error console.error(error)
return message.error(await extractSdkResponseErrorMsg(error))
} }
formattedData.value = formatData(response.list) formattedData.value = formatData(response.list)
paginationData.value = response.pageInfo paginationData.value = response.pageInfo
@ -238,10 +239,13 @@ export function useViewData(
async function changePage(page: number) { async function changePage(page: number) {
paginationData.value.page = page paginationData.value.page = page
await loadData({ await loadData(
offset: (page - 1) * (paginationData.value.pageSize || appInfoDefaultLimit), {
where: where?.value, offset: (page - 1) * (paginationData.value.pageSize || appInfoDefaultLimit),
} as any) where: where?.value,
} as any,
true,
)
} }
const { const {

8
packages/nc-gui/composables/useViewFilters.ts

@ -190,7 +190,7 @@ export function useViewFilters(
} }
} }
const loadFilters = async (hookId?: string) => { const loadFilters = async (hookId?: string, isWebhook = false) => {
if (!view.value?.id) return if (!view.value?.id) return
if (nestedMode.value) { if (nestedMode.value) {
@ -199,11 +199,11 @@ export function useViewFilters(
} }
try { try {
if (hookId) { if (isWebhook || hookId) {
if (parentId) { if (parentId) {
filters.value = (await $api.dbTableFilter.childrenRead(parentId)).list as Filter[] filters.value = (await $api.dbTableFilter.childrenRead(parentId)).list as Filter[]
} else { } else if (hookId) {
filters.value = (await $api.dbTableWebhookFilter.read(hookId!)).list as Filter[] filters.value = (await $api.dbTableWebhookFilter.read(hookId)).list as Filter[]
} }
} else { } else {
if (parentId) { if (parentId) {

12
packages/nc-gui/composables/useViewGroupBy.ts

@ -90,7 +90,7 @@ export const useViewGroupBy = (view: Ref<ViewType | undefined>, where?: Computed
return value ? GROUP_BY_VARS.TRUE : GROUP_BY_VARS.FALSE return value ? GROUP_BY_VARS.TRUE : GROUP_BY_VARS.FALSE
} }
if (col.uidt === UITypes.User) { if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(col.uidt as UITypes)) {
if (!value) { if (!value) {
return GROUP_BY_VARS.NULL return GROUP_BY_VARS.NULL
} }
@ -161,11 +161,15 @@ export const useViewGroupBy = (view: Ref<ViewType | undefined>, where?: Computed
acc += `${acc.length ? '~and' : ''}(${curr.title},${curr.key === GROUP_BY_VARS.TRUE ? 'checked' : 'notchecked'})` acc += `${acc.length ? '~and' : ''}(${curr.title},${curr.key === GROUP_BY_VARS.TRUE ? 'checked' : 'notchecked'})`
} else if ([UITypes.Date, UITypes.DateTime].includes(curr.column_uidt as UITypes)) { } else if ([UITypes.Date, UITypes.DateTime].includes(curr.column_uidt as UITypes)) {
acc += `${acc.length ? '~and' : ''}(${curr.title},eq,exactDate,${curr.key})` acc += `${acc.length ? '~and' : ''}(${curr.title},eq,exactDate,${curr.key})`
} else if (curr.column_uidt === UITypes.User) { } else if ([UITypes.User, UITypes.CreatedBy, UITypes.LastModifiedBy].includes(curr.column_uidt as UITypes)) {
try { try {
const value = JSON.parse(curr.key) const value = JSON.parse(curr.key)
acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${value.map((v: any) => v.id).join(',')})` acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${(Array.isArray(value) ? value : [value])
} catch (e) {} .map((v: any) => v.id)
.join(',')})`
} catch (e) {
console.error(e)
}
} else { } else {
acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${curr.key})` acc += `${acc.length ? '~and' : ''}(${curr.title},gb_eq,${curr.key})`
} }

1
packages/nc-gui/context/index.ts

@ -13,6 +13,7 @@ export const IsFormInj: InjectionKey<Ref<boolean>> = Symbol('is-form-injection')
export const IsSurveyFormInj: InjectionKey<Ref<boolean>> = Symbol('is-survey-form-injection') export const IsSurveyFormInj: InjectionKey<Ref<boolean>> = Symbol('is-survey-form-injection')
export const IsGridInj: InjectionKey<Ref<boolean>> = Symbol('is-grid-injection') export const IsGridInj: InjectionKey<Ref<boolean>> = Symbol('is-grid-injection')
export const IsGroupByInj: InjectionKey<Ref<boolean>> = Symbol('is-group-by-injection') export const IsGroupByInj: InjectionKey<Ref<boolean>> = Symbol('is-group-by-injection')
export const IsGroupByLabelInj: InjectionKey<Ref<boolean>> = Symbol('is-group-by-label-injection')
export const IsGalleryInj: InjectionKey<Ref<boolean>> = Symbol('is-gallery-injection') export const IsGalleryInj: InjectionKey<Ref<boolean>> = Symbol('is-gallery-injection')
export const IsKanbanInj: InjectionKey<Ref<boolean>> = Symbol('is-kanban-injection') export const IsKanbanInj: InjectionKey<Ref<boolean>> = Symbol('is-kanban-injection')
export const IsLockedInj: InjectionKey<Ref<boolean>> = Symbol('is-locked-injection') export const IsLockedInj: InjectionKey<Ref<boolean>> = Symbol('is-locked-injection')

3
packages/nc-gui/lang/ar.json

@ -273,7 +273,7 @@
"Count": "العد", "Count": "العد",
"Lookup": "مشاهدة بيانات", "Lookup": "مشاهدة بيانات",
"DateTime": "تاريخ وقت", "DateTime": "تاريخ وقت",
"CreateTime": "إنشاء وقت", "CreatedTime": "إنشاء وقت",
"LastModifiedTime": "وقت آخر تعديل", "LastModifiedTime": "وقت آخر تعديل",
"AutoNumber": "عدد تلقائي", "AutoNumber": "عدد تلقائي",
"Barcode": "رمز", "Barcode": "رمز",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/bn_IN.json

@ -273,7 +273,7 @@
"Count": "গণন", "Count": "গণন",
"Lookup": "খ", "Lookup": "খ",
"DateTime": "তিখ সময", "DateTime": "তিখ সময",
"CreateTime": "সমযি করন", "CreatedTime": "সমযি করন",
"LastModifiedTime": "শষ পরিবরিত সময", "LastModifiedTime": "শষ পরিবরিত সময",
"AutoNumber": "অট নমবর", "AutoNumber": "অট নমবর",
"Barcode": "বরকড", "Barcode": "বরকড",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/cs.json

@ -273,7 +273,7 @@
"Count": "Počet", "Count": "Počet",
"Lookup": "Vyhledávání", "Lookup": "Vyhledávání",
"DateTime": "Datum a čas", "DateTime": "Datum a čas",
"CreateTime": "Vytvořit čas", "CreatedTime": "Vytvořit čas",
"LastModifiedTime": "Čas poslední úpravy", "LastModifiedTime": "Čas poslední úpravy",
"AutoNumber": "Automatické číslo", "AutoNumber": "Automatické číslo",
"Barcode": "Čárový kód", "Barcode": "Čárový kód",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Následující znaky nejsou povoleny", "followingCharactersAreNotAllowed": "Následující znaky nejsou povoleny",
"columnNameRequired": "Název sloupce je povinný", "columnNameRequired": "Název sloupce je povinný",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Název projektu přesahuje 50 znaků", "projectNameExceeds50Characters": "Název projektu přesahuje 50 znaků",

3
packages/nc-gui/lang/da.json

@ -273,7 +273,7 @@
"Count": "Tælle", "Count": "Tælle",
"Lookup": "Kig op", "Lookup": "Kig op",
"DateTime": "Dato tid", "DateTime": "Dato tid",
"CreateTime": "Opret tid", "CreatedTime": "Opret tid",
"LastModifiedTime": "Sidste ændret tid", "LastModifiedTime": "Sidste ændret tid",
"AutoNumber": "Auto nummer.", "AutoNumber": "Auto nummer.",
"Barcode": "Stregkode.", "Barcode": "Stregkode.",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Følgende tegn er ikke tilladt", "followingCharactersAreNotAllowed": "Følgende tegn er ikke tilladt",
"columnNameRequired": "Kolonnens navn er påkrævet", "columnNameRequired": "Kolonnens navn er påkrævet",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "Længden af kolonnenavnet overstiger maks. {value} tegn", "columnNameExceedsCharacters": "Længden af kolonnenavnet overstiger maks. {value} tegn",
"projectNameExceeds50Characters": "Projektnavnet overstiger 50 tegn", "projectNameExceeds50Characters": "Projektnavnet overstiger 50 tegn",

209
packages/nc-gui/lang/de.json

@ -1,7 +1,7 @@
{ {
"dashboards": { "dashboards": {
"create_new_dashboard_project": "Create New Interface", "create_new_dashboard_project": "Create New Interface",
"connect_data_sources": "Connect data sources", "connect_data_sources": "Datenquellen hinzufügen",
"alert": "Alert", "alert": "Alert",
"alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.", "alert-message": "No databases have been connected. Connect database bases to build interfaces. Skip this step and add databases from the base home page later.",
"select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.", "select_database_projects_that_you_want_to_link_to_this_dashboard_projects": "Select Database Bases that you want to link to this Interface.",
@ -53,12 +53,12 @@
"add": "Hinzufügen", "add": "Hinzufügen",
"edit": "Bearbeiten", "edit": "Bearbeiten",
"link": "Link", "link": "Link",
"links": "Links", "links": "Verknüpfung",
"remove": "Entfernen", "remove": "Entfernen",
"import": "Import", "import": "Import",
"logout": "Log Out", "logout": "Ausloggen",
"empty": "Empty", "empty": "Empty",
"changeIcon": "Change Icon", "changeIcon": "Icon ändern",
"save": "Speichern", "save": "Speichern",
"available": "Available", "available": "Available",
"abort": "Abort", "abort": "Abort",
@ -101,7 +101,7 @@
"reset": "Zurücksetzen", "reset": "Zurücksetzen",
"install": "Installieren", "install": "Installieren",
"show": "Anzeigen", "show": "Anzeigen",
"access": "Access", "access": "Zugriff",
"visibility": "Visibility", "visibility": "Visibility",
"hide": "Verstecken", "hide": "Verstecken",
"deprecated": "Deprecated", "deprecated": "Deprecated",
@ -161,13 +161,13 @@
"sortAsc": "Aufsteigend sortieren", "sortAsc": "Aufsteigend sortieren",
"sortDesc": "Absteigend sortieren", "sortDesc": "Absteigend sortieren",
"move": "Move", "move": "Move",
"geoDataField": "GeoDaten Feld", "geoDataField": "Geodaten Feld",
"type": "Type", "type": "Type",
"name": "Name", "name": "Name",
"changes": "Changes", "changes": "Changes",
"new": "New", "new": "New",
"old": "Old", "old": "Old",
"data": "Data", "data": "Daten",
"source": "Source", "source": "Source",
"destination": "Destination", "destination": "Destination",
"active": "Active", "active": "Active",
@ -192,8 +192,8 @@
"paste": "Paste" "paste": "Paste"
}, },
"objects": { "objects": {
"workspace": "Workspace", "workspace": "Arbeitsbereich",
"workspaces": "Workspaces", "workspaces": "Arbeitsbereiche",
"project": "Projekt", "project": "Projekt",
"projects": "Projekte", "projects": "Projekte",
"table": "Tabelle", "table": "Tabelle",
@ -230,20 +230,20 @@
"editor": "Bearbeiter", "editor": "Bearbeiter",
"commenter": "Kommentator", "commenter": "Kommentator",
"viewer": "Betrachter", "viewer": "Betrachter",
"noaccess": "No Access", "noaccess": "Kein Zugriff",
"superAdmin": "Super Admin", "superAdmin": "Super Admin",
"orgLevelCreator": "Organisationsebenen-Ersteller", "orgLevelCreator": "Organisationsebenen-Ersteller",
"orgLevelViewer": "Organisationsebenen-Betrachter" "orgLevelViewer": "Organisationsebenen-Betrachter"
}, },
"sqlVIew": "SQL Ansicht", "sqlVIew": "SQL Ansicht",
"rowHeight": "Record Height", "rowHeight": "Zeilenhöhe",
"heightClass": { "heightClass": {
"short": "Short", "short": "klein",
"medium": "Medium", "medium": "mittel",
"tall": "Tall", "tall": "groß",
"extra": "Extra" "extra": "riesig"
}, },
"externalDb": "External Database" "externalDb": "Externe Datenbank"
}, },
"datatype": { "datatype": {
"ID": "ID", "ID": "ID",
@ -266,14 +266,14 @@
"Currency": "Währung", "Currency": "Währung",
"Percent": "Prozent", "Percent": "Prozent",
"Duration": "Dauer", "Duration": "Dauer",
"GeoData": "GeoDaten", "GeoData": "Geodaten",
"Rating": "Bewertung", "Rating": "Bewertung",
"Formula": "Formel", "Formula": "Formel",
"Rollup": "Zusammenfassung", "Rollup": "Zusammenfassung",
"Count": "Zählen", "Count": "Zählen",
"Lookup": "Nachschlagen", "Lookup": "Nachschlagen",
"DateTime": "Datum/Zeit", "DateTime": "Datum/Zeit",
"CreateTime": "Zeit erstellen", "CreatedTime": "Zeit erstellen",
"LastModifiedTime": "Zuletzt bearbeitet", "LastModifiedTime": "Zuletzt bearbeitet",
"AutoNumber": "Auto-Nummerierung", "AutoNumber": "Auto-Nummerierung",
"Barcode": "Barcode", "Barcode": "Barcode",
@ -298,24 +298,24 @@
"isNotNull": "ist nicht Null" "isNotNull": "ist nicht Null"
}, },
"title": { "title": {
"docs": "Docs", "docs": "Dokumentation",
"forum": "Forum", "forum": "Forum",
"parameter": "Parameter", "parameter": "Parameter",
"headers": "Headers", "headers": "Headers",
"parameterName": "Parameter Name", "parameterName": "Parameter Name",
"currencyLocale": "Currency Locale", "currencyLocale": "Currency Locale",
"currencyCode": "Currency Code", "currencyCode": "Währungscode",
"searchMembers": "Search Members", "searchMembers": "Nach Nutzern suchen",
"noMembersFound": "No members found", "noMembersFound": "No members found",
"dateJoined": "Date Joined", "dateJoined": "Date Joined",
"tokenName": "Token name", "tokenName": "Name des Tokens",
"inDesktop": "in Desktop", "inDesktop": "in Desktop",
"rowData": "Record data", "rowData": "Record data",
"creator": "Creator", "creator": "Creator",
"qrCode": "QR Code", "qrCode": "QR-Code",
"termsOfService": "Terms of Service", "termsOfService": "Terms of Service",
"updateSelectedRows": "Update Selected Records", "updateSelectedRows": "Update Selected Records",
"noFiltersAdded": "No filters added", "noFiltersAdded": "Keine Filter verwendet",
"editCards": "Edit Cards", "editCards": "Edit Cards",
"noFieldsFound": "No fields found", "noFieldsFound": "No fields found",
"displayValue": "Display Value", "displayValue": "Display Value",
@ -332,19 +332,19 @@
"renameTable": "Rename Table", "renameTable": "Rename Table",
"renamingTable": "Renaming Table", "renamingTable": "Renaming Table",
"renamingWs": "Renaming Workspace", "renamingWs": "Renaming Workspace",
"renameWs": "Rename Workspace", "renameWs": "Arbeitsbereich umbenennen",
"deleteWs": "Delete Workspace", "deleteWs": "Arbeitsbereich löschen",
"deletingWs": "Deleting Workspace", "deletingWs": "Deleting Workspace",
"copyAuthToken": "Copy Auth Token", "copyAuthToken": "Auth-Token kopieren",
"copiedAuthToken": "Copied Auth Token", "copiedAuthToken": "Copied Auth Token",
"copyInviteToken": "Copy Invite Token", "copyInviteToken": "Copy Invite Token",
"showSidebar": "Show Sidebar", "showSidebar": "Show Sidebar",
"hideSidebar": "Hide Sidebar", "hideSidebar": "Hide Sidebar",
"creatingTable": "Creating Table", "creatingTable": "Creating Table",
"erdView": "ERD Ansicht", "erdView": "ERD Ansicht",
"newBase": "New Data Source", "newBase": "Neue Datenquelle hinzufügen",
"newProj": "Neues Projekt", "newProj": "Neues Projekt",
"createBase": "Create Base", "createBase": "Projekt erstellen",
"myProject": "Meine Projekte", "myProject": "Meine Projekte",
"formTitle": "Formular Titel", "formTitle": "Formular Titel",
"collaborative": "Collaborative", "collaborative": "Collaborative",
@ -353,7 +353,7 @@
"appStore": "App-Store", "appStore": "App-Store",
"teamAndAuth": "Team & Authentifizierung", "teamAndAuth": "Team & Authentifizierung",
"rolesUserMgmt": "Rollen- & Benutzermanagement", "rolesUserMgmt": "Rollen- & Benutzermanagement",
"userMgmt": "Benutzermanagement", "userMgmt": "Benutzerverwaltung",
"apiTokens": "API Tokens", "apiTokens": "API Tokens",
"apiTokenMgmt": "API-Token-Management", "apiTokenMgmt": "API-Token-Management",
"rolesMgmt": "Rollenmanagement", "rolesMgmt": "Rollenmanagement",
@ -391,17 +391,17 @@
"codeSnippet": "Code Ausschnitt", "codeSnippet": "Code Ausschnitt",
"keyboardShortcut": "Tastenkürzel", "keyboardShortcut": "Tastenkürzel",
"generateRandomName": "Zufälligen Namen generieren", "generateRandomName": "Zufälligen Namen generieren",
"findRowByScanningCode": "Find row by scanning a QR or Barcode", "findRowByScanningCode": "Zeile durch Scannen eines QR-Codes oder Barcodes finden",
"tokenManagement": "Token Management", "tokenManagement": "Token Management",
"addNewToken": "Add new token", "addNewToken": "Neuen Token hinzufügen",
"accountSettings": "Account Settings", "accountSettings": "Kontoeinstellungen",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Passwort zurücksetzen",
"tokens": "Tokens", "tokens": "Tokens",
"userManagement": "User Management", "userManagement": "Benutzerverwaltung",
"accountManagement": "Account management", "accountManagement": "Account management",
"licence": "Licence", "licence": "Licence",
"allowAllMimeTypes": "Allow All Mime Types", "allowAllMimeTypes": "Allow All Mime Types",
"defaultView": "Default View", "defaultView": "Standardansicht",
"relations": "Relations", "relations": "Relations",
"switchLanguage": "Switch Language", "switchLanguage": "Switch Language",
"renameFile": "Rename File", "renameFile": "Rename File",
@ -427,7 +427,7 @@
"downloadData": "Download Data", "downloadData": "Download Data",
"blockQuote": "Block Quote", "blockQuote": "Block Quote",
"noToken": "No Token", "noToken": "No Token",
"tokenLimit": "Only one token per user is allowed", "tokenLimit": "Nur ein Token pro Benutzer ist erlaubt",
"duplicateAttachment": "File with name {filename} already attached", "duplicateAttachment": "File with name {filename} already attached",
"viewIdColon": "VIEW ID: {viewId}", "viewIdColon": "VIEW ID: {viewId}",
"toAddress": "To Address", "toAddress": "To Address",
@ -444,17 +444,17 @@
"duplicateRecord": "Duplicate record", "duplicateRecord": "Duplicate record",
"binaryEncodingFormat": "Binary encoding format", "binaryEncodingFormat": "Binary encoding format",
"syntax": "Syntax", "syntax": "Syntax",
"examples": "Examples", "examples": "Beispiele",
"durationInfo": "A duration of time in minutes or seconds (e.g. 1:23).", "durationInfo": "Dauer der Zeit in Minuten oder Sekunden (z.B. 1:23).",
"addHeader": "Add Header", "addHeader": "Add Header",
"enterDefaultUrlOptional": "Enter default URL (Optional)", "enterDefaultUrlOptional": "Standard-URL eingeben (optional)",
"negative": "Negative", "negative": "Negative",
"discard": "Discard", "discard": "Discard",
"default": "Default", "default": "Default",
"defaultNumberPercent": "Default Number (%)", "defaultNumberPercent": "Default Number (%)",
"durationFormat": "Duration Format", "durationFormat": "Format der Zeitspanne",
"dateFormat": "Date Format", "dateFormat": "Datumsformat",
"timeFormat": "Time Format", "timeFormat": "Zeitformat",
"singularLabel": "Singular Label", "singularLabel": "Singular Label",
"pluralLabel": "Plural Label", "pluralLabel": "Plural Label",
"optional": "(Optional)", "optional": "(Optional)",
@ -467,29 +467,29 @@
"forRole": "for role", "forRole": "for role",
"clickToCopyViewID": "Click to copy View ID", "clickToCopyViewID": "Click to copy View ID",
"viewMode": "View Mode", "viewMode": "View Mode",
"searchUsers": "Search Users", "searchUsers": "Benutzer durchsuchen",
"superAdmin": "Super Admin", "superAdmin": "Super Admin",
"allTables": "All Tables", "allTables": "Alle Tabellen",
"members": "Members", "members": "Mitglieder",
"dataSources": "Data Sources", "dataSources": "Datenquellen",
"connectDataSource": "Connect a Data Source", "connectDataSource": "Eine Datenquelle hinzufügen",
"searchProjects": "Search Bases", "searchProjects": "Search Bases",
"createdBy": "Erstellt von", "createdBy": "Erstellt von",
"viewingAttachmentsOf": "Viewing Attachments of", "viewingAttachmentsOf": "Viewing Attachments of",
"readOnly": "Readonly", "readOnly": "Readonly",
"dropHere": "Drop here", "dropHere": "Drop here",
"createdOn": "Created On", "createdOn": "Erstellt am",
"notifyVia": "Benachrichtigen mit", "notifyVia": "Benachrichtigen mit",
"projName": "Projektname", "projName": "Projektname",
"profile": "Profile", "profile": "Profil",
"accountDetails": "Account Details", "accountDetails": "Kontoinformationen",
"controlAppearance": "Control your Appearance.", "controlAppearance": "Bearbeite dein Erscheinungsbild.",
"accountEmailID": "Account Email ID", "accountEmailID": "Account Email ID",
"backToWorkspace": "Back to Workspace", "backToWorkspace": "Zurück zum Arbeitsbereich",
"untitledToken": "Untitled token", "untitledToken": "Untitled token",
"tableName": "Tabellenname", "tableName": "Tabellenname",
"dashboardName": "Dashboard name", "dashboardName": "Dashboard name",
"createView": "Create a View", "createView": "Neue Ansicht erstellen",
"creatingView": "Creating View", "creatingView": "Creating View",
"duplicateView": "Duplicate View", "duplicateView": "Duplicate View",
"duplicateGridView": "Duplicate Grid View", "duplicateGridView": "Duplicate Grid View",
@ -529,7 +529,7 @@
"where": "Wo", "where": "Wo",
"cache": "Zwischenspeicher", "cache": "Zwischenspeicher",
"chat": "Chat", "chat": "Chat",
"showOrHide": "Show or Hide", "showOrHide": "Anzeigen / Verbergen",
"airtable": "Airtable", "airtable": "Airtable",
"csv": "CSV", "csv": "CSV",
"csvFile": "CSV File", "csvFile": "CSV File",
@ -544,10 +544,10 @@
"syncState": "Sync-Status", "syncState": "Sync-Status",
"created": "Erstellt", "created": "Erstellt",
"sqlOutput": "SQL-Ausgabe", "sqlOutput": "SQL-Ausgabe",
"addOption": "Option hinzufügen", "addOption": "Auswahloption hinzufügen",
"interfaceColor": "Interface Color", "interfaceColor": "Interface Color",
"qrCodeValueColumn": "Spalte mit QR-Code", "qrCodeValueColumn": "Spalte mit Werten für QR-Codes",
"barcodeValueColumn": "Spalte mit Barcode-Wert", "barcodeValueColumn": "Spalte mit Werten für Barcode",
"barcodeFormat": "Barcode-Format", "barcodeFormat": "Barcode-Format",
"qrCodeValueTooLong": "Zu viele Zeichen für einen QR-Code", "qrCodeValueTooLong": "Zu viele Zeichen für einen QR-Code",
"barcodeValueTooLong": "Zu viele Zeichen für einen Barcode", "barcodeValueTooLong": "Zu viele Zeichen für einen Barcode",
@ -584,7 +584,7 @@
"childField": "Child field", "childField": "Child field",
"joinCloudForFree": "Join Cloud for Free", "joinCloudForFree": "Join Cloud for Free",
"linkToAnotherRecord": "Link zu einem anderen Datensatz", "linkToAnotherRecord": "Link zu einem anderen Datensatz",
"links": "Links", "links": "Verknüpfung",
"onUpdate": "Update", "onUpdate": "Update",
"onDelete": "Löschen", "onDelete": "Löschen",
"account": "Benutzerkonto", "account": "Benutzerkonto",
@ -602,7 +602,7 @@
"importLookupColumns": "Suchspalten importieren", "importLookupColumns": "Suchspalten importieren",
"importAttachmentColumns": "Spalten für Anhänge importieren", "importAttachmentColumns": "Spalten für Anhänge importieren",
"importFormulaColumns": "Formelspalten importieren", "importFormulaColumns": "Formelspalten importieren",
"importUsers": "Import Users (by email)", "importUsers": "Benutzer importieren (per E-Mail)",
"noData": "Keine Daten", "noData": "Keine Daten",
"goToDashboard": "Zum Dashboard gehen", "goToDashboard": "Zum Dashboard gehen",
"importing": "Wird importiert", "importing": "Wird importiert",
@ -629,7 +629,7 @@
"prevRow": "Vorherige Reihe", "prevRow": "Vorherige Reihe",
"addRowGrid": "Manually add data in grid view", "addRowGrid": "Manually add data in grid view",
"addRowForm": "Enter record data through a form", "addRowForm": "Enter record data through a form",
"noAccess": "No access", "noAccess": "Kein Zugriff",
"restApis": "Rest APIs", "restApis": "Rest APIs",
"apis": "APIs", "apis": "APIs",
"includeData": "Include Data", "includeData": "Include Data",
@ -639,7 +639,7 @@
"embedInSite": "Embed this view in your site", "embedInSite": "Embed this view in your site",
"titleRequired": "title is required.", "titleRequired": "title is required.",
"sourceNameRequired": "Source name is required", "sourceNameRequired": "Source name is required",
"changeWsName": "Change Workspace Name", "changeWsName": "Name des Arbeitsbereichs ändern",
"pressEnter": "Press Enter", "pressEnter": "Press Enter",
"newFormLoaded": "New form will be loaded after", "newFormLoaded": "New form will be loaded after",
"webhook": "Webhook" "webhook": "Webhook"
@ -659,11 +659,11 @@
"submitAnotherForm": "Submit Another Form", "submitAnotherForm": "Submit Another Form",
"dragAndDropFieldsHereToAdd": "Drag and drop fields here to add", "dragAndDropFieldsHereToAdd": "Drag and drop fields here to add",
"editSource": "Edit Data Source", "editSource": "Edit Data Source",
"enterText": "Enter text", "enterText": "Text eingeben",
"okEditBase": "Ok & Edit Base", "okEditBase": "Ok & Edit Base",
"showInUI": "Show in UI", "showInUI": "Show in UI",
"outOfSync": "Out of sync", "outOfSync": "Out of sync",
"newSource": "New Data Source", "newSource": "Neue Datenquelle hinzufügen",
"newWebhook": "New Webhook", "newWebhook": "New Webhook",
"enablePublicAccess": "Enable Public Access", "enablePublicAccess": "Enable Public Access",
"doYouWantToSaveTheChanges": "Do you want to save the changes ?", "doYouWantToSaveTheChanges": "Do you want to save the changes ?",
@ -718,8 +718,8 @@
"filter": "Filter", "filter": "Filter",
"addFilter": "Filter hinzufügen", "addFilter": "Filter hinzufügen",
"share": "Teilen", "share": "Teilen",
"groupBy": "Group By", "groupBy": "Gruppieren nach",
"addSubGroup": "Add subgroup", "addSubGroup": "Untergruppe hinzufügen",
"shareBase": { "shareBase": {
"label": "Share base", "label": "Share base",
"disable": "Freigegebene Datenbank deaktivieren", "disable": "Freigegebene Datenbank deaktivieren",
@ -748,7 +748,7 @@
"copyApiURL": "API-URL kopieren", "copyApiURL": "API-URL kopieren",
"createTable": "Create New Table", "createTable": "Create New Table",
"createDashboard": "Create Dashboard", "createDashboard": "Create Dashboard",
"createWorkspace": "Create Workspace", "createWorkspace": "Arbeitsbereich erstellen",
"refreshTable": "Tabellen aktualisieren", "refreshTable": "Tabellen aktualisieren",
"renameTable": "Tabelle umbenennen", "renameTable": "Tabelle umbenennen",
"renameLayout": "Layout Rename", "renameLayout": "Layout Rename",
@ -831,9 +831,9 @@
"markAllAsRead": "Mark all as read", "markAllAsRead": "Mark all as read",
"column": { "column": {
"delete": "Delete Field", "delete": "Delete Field",
"addNumber": "Add Number Field", "addNumber": "Nummernfeld hinzufügen",
"addSingleLineText": "Add SingleLineText Field", "addSingleLineText": "Einzeiliges Textfeld hinzufügen",
"addLongText": "Add LongText Field", "addLongText": "Langes Textfeld hinzufügen",
"addOther": "Add Other Field" "addOther": "Add Other Field"
}, },
"erd": { "erd": {
@ -897,12 +897,12 @@
"webhookTitle": "Webhook Title", "webhookTitle": "Webhook Title",
"barcodeColumn": "Select a field for the Barcode value", "barcodeColumn": "Select a field for the Barcode value",
"notFoundContent": "No valid field Type can be found.", "notFoundContent": "No valid field Type can be found.",
"selectBarcodeFormat": "Select a Barcode format", "selectBarcodeFormat": "Barcode-Format auswählen",
"projName": "Projektnamen eingeben", "projName": "Projektnamen eingeben",
"selectGroupField": "Select a Grouping Field", "selectGroupField": "Select a Grouping Field",
"selectGroupFieldNotFound": "No Single Select Field can be found. Please create one first.", "selectGroupFieldNotFound": "No Single Select Field can be found. Please create one first.",
"selectGeoField": "Select a GeoData Field", "selectGeoField": "Geodaten Feld auswählen",
"selectGeoFieldNotFound": "No GeoData Field can be found. Please create one first.", "selectGeoFieldNotFound": "Es konnte kein Geodaten Feld gefunden werden. Bitte erstellen Sie zuerst eins.",
"password": { "password": {
"enter": "Passwort eingeben", "enter": "Passwort eingeben",
"current": "Aktuelles Passwort", "current": "Aktuelles Passwort",
@ -911,7 +911,7 @@
"confirm": "Neues Passwort bestätigen" "confirm": "Neues Passwort bestätigen"
}, },
"selectAColumnForTheQRCodeValue": "Select a field for the QR code value", "selectAColumnForTheQRCodeValue": "Select a field for the QR code value",
"allowNegativeNumbers": "Allow negative numbers", "allowNegativeNumbers": "Negative nummern erlauben",
"searchProjectTree": "Tabellen suchen", "searchProjectTree": "Tabellen suchen",
"searchFields": "Felder suchen", "searchFields": "Felder suchen",
"searchColumn": "Spalten suchen {search}", "searchColumn": "Spalten suchen {search}",
@ -922,7 +922,7 @@
"filterByEmail": "Filtern nach E-Mail", "filterByEmail": "Filtern nach E-Mail",
"filterQuery": "Filter-Abfrage", "filterQuery": "Filter-Abfrage",
"selectField": "Feld wählen", "selectField": "Feld wählen",
"precision": "Precision", "precision": "Dezimalstellen",
"decimal1": "1.0", "decimal1": "1.0",
"decimal2": "1.00", "decimal2": "1.00",
"decimal3": "1.000", "decimal3": "1.000",
@ -931,12 +931,12 @@
"decimal6": "1.000000", "decimal6": "1.000000",
"decimal7": "1.0000000", "decimal7": "1.0000000",
"decimal8": "1.00000000", "decimal8": "1.00000000",
"value": "Value", "value": "Wert",
"key": "Key" "key": "Key"
}, },
"msg": { "msg": {
"clickToCopyFieldId": "Click to copy Field Id", "clickToCopyFieldId": "Click to copy Field Id",
"enterPassword": "Enter password", "enterPassword": "Passwort eingeben",
"bySigningUp": "By signing up, you agree to the", "bySigningUp": "By signing up, you agree to the",
"subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter", "subscribeToOurWeeklyNewsletter": "Subscribe to our weekly newsletter",
"verifyingPassword": "Verifying Password", "verifyingPassword": "Verifying Password",
@ -947,10 +947,10 @@
"optimizedQueryDisabled": "Optimized query is disabled", "optimizedQueryDisabled": "Optimized query is disabled",
"optimizedQueryEnabled": "Optimized query is enabled", "optimizedQueryEnabled": "Optimized query is enabled",
"lookupNonBtWarning": "Lookup field is not supported for non-Belongs to relation", "lookupNonBtWarning": "Lookup field is not supported for non-Belongs to relation",
"invalidTime": "Invalid Time", "invalidTime": "Ungültige Uhrzeit",
"linkColumnClearNotSupportedYet": "You don't have any supported links for Lookup", "linkColumnClearNotSupportedYet": "You don't have any supported links for Lookup",
"recordCouldNotBeFound": "Record could not be found", "recordCouldNotBeFound": "Record could not be found",
"invalidPhoneNumber": "Invalid phone number", "invalidPhoneNumber": "Ungültige Telefonnummer",
"pageSizeChanged": "Page size changed", "pageSizeChanged": "Page size changed",
"errorLoadingData": "Error loading data", "errorLoadingData": "Error loading data",
"webhookBodyMsg1": "Use context variable", "webhookBodyMsg1": "Use context variable",
@ -984,18 +984,18 @@
"columnNotMatchedWithType": "{columnName} is not matched with {columnType}" "columnNotMatchedWithType": "{columnName} is not matched with {columnType}"
}, },
"selectOption": { "selectOption": {
"cantBeNull": "Select options can't be null", "cantBeNull": "Auswahloptionen dürfen nicht leer sein",
"multiSelectCantHaveCommas": "MultiSelect fields can't have commas(',')", "multiSelectCantHaveCommas": "Auswahloptionen dürfen keine Kommas (\",\") enthalten",
"cantHaveDuplicates": "Select options can't have duplicates", "cantHaveDuplicates": "Auswahloptionen dürfen keine Duplikate enthalten",
"createNewOptionNamed": "Create new option named" "createNewOptionNamed": "Create new option named"
}, },
"plsEnterANumber": "Please enter a number", "plsEnterANumber": "Bitte geben Sie eine Zahl ein",
"plsInputEmail": "Please input email", "plsInputEmail": "Bitte E-Mail-Adresse eingeben",
"invalidDate": "Invalid date", "invalidDate": "Ungültiges Datum",
"invalidLocale": "Invalid locale", "invalidLocale": "Invalid locale",
"invalidCurrencyCode": "Invalid Currency Code", "invalidCurrencyCode": "Ungültiger Währungscode",
"postgresHasItsOwnCurrencySettings": "PostgreSQL 'money' type has own currency settings", "postgresHasItsOwnCurrencySettings": "PostgreSQL 'money' type has own currency settings",
"validColumnsForBarCode": "The valid Field Types for a Barcode Field are: Number, Single Line Text, Long Text, Phone Number, URL, Email, Decimal. Please create one first.", "validColumnsForBarCode": "Die gültigen Feldtypen für ein Barcodefeld sind: Zahl, einzeiliger Text, langer Text, Telefonnummer, URL, E-Mail und Dezimalzahl. Bitte erstellen Sie zuerst ein gültiges Feld.",
"hm": { "hm": {
"title": "Has Many Relation", "title": "Has Many Relation",
"tooltip_desc": "A single record from table ", "tooltip_desc": "A single record from table ",
@ -1020,9 +1020,9 @@
"noRecordsLinked": "No records linked", "noRecordsLinked": "No records linked",
"recordsLinked": "records linked", "recordsLinked": "records linked",
"acceptOnlyValid": "Accepts only", "acceptOnlyValid": "Accepts only",
"apiTokenCreate": "Create personal API tokens to use in automation or external apps.", "apiTokenCreate": "Personalisierte API Tokens zur Verwendung in Automatisierungen oder externen Apps erstellen.",
"selectFieldToSort": "Select Field to Sort", "selectFieldToSort": "Feld zum Sortieren auswählen",
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "Feld zum Gruppieren auswählen",
"thereAreNoRecordsInTable": "There are no records in table", "thereAreNoRecordsInTable": "There are no records in table",
"createWebhookMsg1": "Get started with web-hooks!", "createWebhookMsg1": "Get started with web-hooks!",
"createWebhookMsg2": "Create web-hooks to power you automations,", "createWebhookMsg2": "Create web-hooks to power you automations,",
@ -1045,7 +1045,7 @@
"nonEditableFields": { "nonEditableFields": {
"computedFieldUnableToClear": "Warnung: Berechnetes Feld - Text kann nicht gelöscht werden", "computedFieldUnableToClear": "Warnung: Berechnetes Feld - Text kann nicht gelöscht werden",
"qrFieldsCannotBeDirectlyChanged": "Warnung: QR-Felder können nicht direkt geändert werden.", "qrFieldsCannotBeDirectlyChanged": "Warnung: QR-Felder können nicht direkt geändert werden.",
"barcodeFieldsCannotBeDirectlyChanged": "Warning: Barcode fields cannot be directly changed." "barcodeFieldsCannotBeDirectlyChanged": "Achtung: Barcode Felder können nicht direkt geändert werden."
}, },
"duplicateProject": "Are you sure you want to duplicate the base?", "duplicateProject": "Are you sure you want to duplicate the base?",
"duplicateTable": "Are you sure you want to duplicate the table?" "duplicateTable": "Are you sure you want to duplicate the table?"
@ -1060,7 +1060,7 @@
}, },
"codeScanner": { "codeScanner": {
"loadingScanner": "Lade Scanner...", "loadingScanner": "Lade Scanner...",
"selectColumn": "Select a column (QR code or Barcode) that you want to use for finding a row by scanning.", "selectColumn": "Eine Spalte (QR-Code oder Barcode) auswählen, nach der beim Scannen des Codes gesucht werden soll.",
"moreThanOneRowFoundForCode": "More than one row found for this code. Currently only unique codes are supported.", "moreThanOneRowFoundForCode": "More than one row found for this code. Currently only unique codes are supported.",
"noRowFoundForCode": "No row found for this code for the selected column" "noRowFoundForCode": "No row found for this code for the selected column"
}, },
@ -1197,14 +1197,14 @@
"computedFieldEditWarning": "Berechnetes Feld: Der Inhalt ist schreibgeschützt. Verwenden Sie das Menü \"Spalten bearbeiten\", um das Feld neu zu konfigurieren.", "computedFieldEditWarning": "Berechnetes Feld: Der Inhalt ist schreibgeschützt. Verwenden Sie das Menü \"Spalten bearbeiten\", um das Feld neu zu konfigurieren.",
"computedFieldDeleteWarning": "Berechnetes Feld: Inhalt ist schreibgeschützt. Inhalt kann nicht gelöscht werden.", "computedFieldDeleteWarning": "Berechnetes Feld: Inhalt ist schreibgeschützt. Inhalt kann nicht gelöscht werden.",
"noMoreRecords": "Keine weiteren Aufzeichnungen", "noMoreRecords": "Keine weiteren Aufzeichnungen",
"tokenNameNotEmpty": "Token name should not be empty", "tokenNameNotEmpty": "Token Name darf nicht leer sein",
"tokenNameMaxLength": "Token name should not be more than 255 characters", "tokenNameMaxLength": "Token Name darf nicht mehr als 255 Zeichen lang sein",
"dbNameRequired": "Database name is required", "dbNameRequired": "Database name is required",
"wsNameRequired": "Workspace name required", "wsNameRequired": "Name des Arbeitsbereiches darf nicht Leer sein",
"wsNameMinLength": "Workspace name must be at least 3 characters long", "wsNameMinLength": "Name des Arbeitsbereiches muss mindestens 3 Zeichen lang sein",
"wsNameMaxLength": "Workspace name must be at most 50 characters long", "wsNameMaxLength": "Name des Arbeitsbereiches darf nicht länger als 50 Zeichen sein",
"wsDeleteDlg": "Delete this workspace and all it’s contents.", "wsDeleteDlg": "Diesen Arbeitsbereich und all seine Inhalte löschen.",
"userConfirmation": "I understand that this action is irreversible", "userConfirmation": "Ich verstehe, dass diese Aktion nicht rückgängig gemacht werden kann",
"pageNotFound": "Page Not Found", "pageNotFound": "Page Not Found",
"makeLineBreak": "to make a line break", "makeLineBreak": "to make a line break",
"goToPrevious": "Go to previous", "goToPrevious": "Go to previous",
@ -1224,13 +1224,13 @@
"invalidChar": "Ungültiges Zeichen im Ordnerpfad.", "invalidChar": "Ungültiges Zeichen im Ordnerpfad.",
"invalidDbCredentials": "Ungültige Datenbankanmeldeinformationen.", "invalidDbCredentials": "Ungültige Datenbankanmeldeinformationen.",
"unableToConnectToDb": "Es kann keine Verbindung zur Datenbank hergestellt werden, bitte überprüfen Sie Ihre Datenbank.", "unableToConnectToDb": "Es kann keine Verbindung zur Datenbank hergestellt werden, bitte überprüfen Sie Ihre Datenbank.",
"invalidYear": "Invalid year", "invalidYear": "Ungültiges Jahr",
"userDoesntHaveSufficientPermission": "Den Benutzer gibt es nicht oder er hat keine ausreichenden Rechte, um das Schema zu erstellen.", "userDoesntHaveSufficientPermission": "Den Benutzer gibt es nicht oder er hat keine ausreichenden Rechte, um das Schema zu erstellen.",
"dbConnectionStatus": "Ungültige Datenbankparameter", "dbConnectionStatus": "Ungültige Datenbankparameter",
"dbConnectionFailed": "Verbindungsfehler:", "dbConnectionFailed": "Verbindungsfehler:",
"nullFilterExists": "Null filter exists. Please remove them", "nullFilterExists": "Null filter exists. Please remove them",
"signUpRules": { "signUpRules": {
"emailRequired": "Email is required", "emailRequired": "E-Mail-Adresse ist erforderlich",
"emailInvalid": "Email muß gültig sein", "emailInvalid": "Email muß gültig sein",
"passwdRequired": "Passwort ist erforderlich", "passwdRequired": "Passwort ist erforderlich",
"passwdLength": "Ihr Passwort muß mindestens 8 Zeichen haben", "passwdLength": "Ihr Passwort muß mindestens 8 Zeichen haben",
@ -1241,8 +1241,8 @@
"atLeastOneNumber": "Eine Nummer", "atLeastOneNumber": "Eine Nummer",
"atLeastOneSpecialChar": "Ein Sonderzeichen", "atLeastOneSpecialChar": "Ein Sonderzeichen",
"allowedSpecialCharList": "Erlaubte Sonderzeichenliste", "allowedSpecialCharList": "Erlaubte Sonderzeichenliste",
"invalidEmails": "Invalid emails", "invalidEmails": "Ungültige E-Mail-Adressen",
"invalidEmail": "Invalid Email" "invalidEmail": "Ungültige E-Mail-Adresse"
}, },
"invalidURL": "Ungültige URL", "invalidURL": "Ungültige URL",
"invalidEmail": "Ungültige E-Mail", "invalidEmail": "Ungültige E-Mail",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Folgende Zeichen sind nicht erlaubt", "followingCharactersAreNotAllowed": "Folgende Zeichen sind nicht erlaubt",
"columnNameRequired": "Spaltenname ist erforderlich", "columnNameRequired": "Spaltenname ist erforderlich",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Projektname überschreitet 50 Zeichen", "projectNameExceeds50Characters": "Projektname überschreitet 50 Zeichen",
@ -1322,7 +1323,7 @@
"tableDataExported": "Erfolgreich alle Tabellendaten exportiert", "tableDataExported": "Erfolgreich alle Tabellendaten exportiert",
"updated": "Erfolgreich aktualisiert", "updated": "Erfolgreich aktualisiert",
"sharedViewDeleted": "Gemeinsame Ansicht erfolgreich gelöscht", "sharedViewDeleted": "Gemeinsame Ansicht erfolgreich gelöscht",
"userDeleted": "Benutzer erfolgreich gelöscht", "userDeleted": "Benutzer wurde erfolgreich gelöscht",
"viewRenamed": "Ansicht erfolgreich umbenannt", "viewRenamed": "Ansicht erfolgreich umbenannt",
"tokenGenerated": "Token erfolgreich generiert", "tokenGenerated": "Token erfolgreich generiert",
"tokenDeleted": "Token erfolgreich gelöscht", "tokenDeleted": "Token erfolgreich gelöscht",

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

@ -273,7 +273,7 @@
"Count": "Count", "Count": "Count",
"Lookup": "Lookup", "Lookup": "Lookup",
"DateTime": "Date Time", "DateTime": "Date Time",
"CreateTime": "Create Time", "CreatedTime": "Create Time",
"LastModifiedTime": "Last Modified Time", "LastModifiedTime": "Last Modified Time",
"AutoNumber": "Auto Number", "AutoNumber": "Auto Number",
"Barcode": "Barcode", "Barcode": "Barcode",
@ -1212,7 +1212,6 @@
"thankYou": "Thank you!", "thankYou": "Thank you!",
"submittedFormData": "You have successfully submitted the form data.", "submittedFormData": "You have successfully submitted the form data.",
"editingSystemKeyNotSupported": "Editing system key not supported" "editingSystemKeyNotSupported": "Editing system key not supported"
}, },
"error": { "error": {
"nameRequired": "Name Required", "nameRequired": "Name Required",
@ -1275,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Field name is required", "columnNameRequired": "Field name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of field name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of field name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Base name exceeds 50 characters", "projectNameExceeds50Characters": "Base name exceeds 50 characters",

3
packages/nc-gui/lang/es.json

@ -273,7 +273,7 @@
"Count": "Cuenta", "Count": "Cuenta",
"Lookup": "Búsqueda", "Lookup": "Búsqueda",
"DateTime": "Fecha y hora", "DateTime": "Fecha y hora",
"CreateTime": "Fecha de creación", "CreatedTime": "Fecha de creación",
"LastModifiedTime": "Fecha de modificación", "LastModifiedTime": "Fecha de modificación",
"AutoNumber": "Número automático", "AutoNumber": "Número automático",
"Barcode": "Código de barras", "Barcode": "Código de barras",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Los siguientes caracteres no están permitidos", "followingCharactersAreNotAllowed": "Los siguientes caracteres no están permitidos",
"columnNameRequired": "El nombre de la columna es obligatorio", "columnNameRequired": "El nombre de la columna es obligatorio",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "El nombre del proyecto supera los 50 caracteres", "projectNameExceeds50Characters": "El nombre del proyecto supera los 50 caracteres",

3
packages/nc-gui/lang/eu.json

@ -273,7 +273,7 @@
"Count": "Count", "Count": "Count",
"Lookup": "Lookup", "Lookup": "Lookup",
"DateTime": "Date Time", "DateTime": "Date Time",
"CreateTime": "Create Time", "CreatedTime": "Create Time",
"LastModifiedTime": "Azkeneko aldatze ordua", "LastModifiedTime": "Azkeneko aldatze ordua",
"AutoNumber": "Auto Number", "AutoNumber": "Auto Number",
"Barcode": "Barcode", "Barcode": "Barcode",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/fa.json

@ -273,7 +273,7 @@
"Count": "شمردن", "Count": "شمردن",
"Lookup": "جستوجو", "Lookup": "جستوجو",
"DateTime": "تاریخ و زمان", "DateTime": "تاریخ و زمان",
"CreateTime": "ایجاد زمان", "CreatedTime": "ایجاد زمان",
"LastModifiedTime": "زمان آخرین اصلاح", "LastModifiedTime": "زمان آخرین اصلاح",
"AutoNumber": "شماره خودکار", "AutoNumber": "شماره خودکار",
"Barcode": "بارکد", "Barcode": "بارکد",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "این کاراکتر ها مجاز نیستند", "followingCharactersAreNotAllowed": "این کاراکتر ها مجاز نیستند",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/fi.json

@ -273,7 +273,7 @@
"Count": "Kreivi", "Count": "Kreivi",
"Lookup": "Katso ylös", "Lookup": "Katso ylös",
"DateTime": "Treffiaika", "DateTime": "Treffiaika",
"CreateTime": "Luoda aikaa", "CreatedTime": "Luoda aikaa",
"LastModifiedTime": "Viimeksi muutettu aika", "LastModifiedTime": "Viimeksi muutettu aika",
"AutoNumber": "Automaattinen numero", "AutoNumber": "Automaattinen numero",
"Barcode": "Viivakoodi", "Barcode": "Viivakoodi",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Seuraavat merkit eivät ole sallittuja", "followingCharactersAreNotAllowed": "Seuraavat merkit eivät ole sallittuja",
"columnNameRequired": "Sarakkeen nimi vaaditaan", "columnNameRequired": "Sarakkeen nimi vaaditaan",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Hankkeen nimi ylittää 50 merkkiä", "projectNameExceeds50Characters": "Hankkeen nimi ylittää 50 merkkiä",

3
packages/nc-gui/lang/fr.json

@ -273,7 +273,7 @@
"Count": "Compteur", "Count": "Compteur",
"Lookup": "Consulter", "Lookup": "Consulter",
"DateTime": "Date et heure", "DateTime": "Date et heure",
"CreateTime": "Date de création", "CreatedTime": "Date de création",
"LastModifiedTime": "Dernière modification", "LastModifiedTime": "Dernière modification",
"AutoNumber": "Numérotation automatique", "AutoNumber": "Numérotation automatique",
"Barcode": "Code-barres", "Barcode": "Code-barres",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Les caractères suivants ne sont pas autorisés", "followingCharactersAreNotAllowed": "Les caractères suivants ne sont pas autorisés",
"columnNameRequired": "Nom de la colonne requis", "columnNameRequired": "Nom de la colonne requis",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Le nom du projet dépasse les 50 caractères", "projectNameExceeds50Characters": "Le nom du projet dépasse les 50 caractères",

3
packages/nc-gui/lang/he.json

@ -273,7 +273,7 @@
"Count": "ספירה", "Count": "ספירה",
"Lookup": "הבט מעלה", "Lookup": "הבט מעלה",
"DateTime": "תאריך שעה", "DateTime": "תאריך שעה",
"CreateTime": "צור זמן", "CreatedTime": "צור זמן",
"LastModifiedTime": "שונה לאחרונה", "LastModifiedTime": "שונה לאחרונה",
"AutoNumber": "מספר אוטומטי", "AutoNumber": "מספר אוטומטי",
"Barcode": "ברקוד", "Barcode": "ברקוד",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/hi.json

@ -273,7 +273,7 @@
"Count": "गिनत करन", "Count": "गिनत करन",
"Lookup": "द", "Lookup": "द",
"DateTime": "दिक और समय", "DateTime": "दिक और समय",
"CreateTime": "निण क समय", "CreatedTime": "निण क समय",
"LastModifiedTime": "अिम सित समय", "LastModifiedTime": "अिम सित समय",
"AutoNumber": "वहन नबर", "AutoNumber": "वहन नबर",
"Barcode": "बरकड", "Barcode": "बरकड",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/hr.json

@ -273,7 +273,7 @@
"Count": "Računati", "Count": "Računati",
"Lookup": "Pogledaj", "Lookup": "Pogledaj",
"DateTime": "Datum vrijeme", "DateTime": "Datum vrijeme",
"CreateTime": "Stvoriti vrijeme", "CreatedTime": "Stvoriti vrijeme",
"LastModifiedTime": "Posljednje izmijenjeno vrijeme", "LastModifiedTime": "Posljednje izmijenjeno vrijeme",
"AutoNumber": "Automatski broj", "AutoNumber": "Automatski broj",
"Barcode": "Barkod", "Barcode": "Barkod",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Nije dopušteno koristiti sljedeće znakove", "followingCharactersAreNotAllowed": "Nije dopušteno koristiti sljedeće znakove",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/id.json

@ -273,7 +273,7 @@
"Count": "Menghitung", "Count": "Menghitung",
"Lookup": "Mencari", "Lookup": "Mencari",
"DateTime": "Tanggal Waktu", "DateTime": "Tanggal Waktu",
"CreateTime": "Buat waktu", "CreatedTime": "Buat waktu",
"LastModifiedTime": "Waktu yang dimodifikasi terakhir", "LastModifiedTime": "Waktu yang dimodifikasi terakhir",
"AutoNumber": "Nomor otomatis", "AutoNumber": "Nomor otomatis",
"Barcode": "Barcode.", "Barcode": "Barcode.",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Karakter berikut tidak diperbolehkan", "followingCharactersAreNotAllowed": "Karakter berikut tidak diperbolehkan",
"columnNameRequired": "Nama kolom harus diisi", "columnNameRequired": "Nama kolom harus diisi",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Nama proyek melebihi 50 karakter", "projectNameExceeds50Characters": "Nama proyek melebihi 50 karakter",

3
packages/nc-gui/lang/it.json

@ -273,7 +273,7 @@
"Count": "Contatore", "Count": "Contatore",
"Lookup": "Consultazione", "Lookup": "Consultazione",
"DateTime": "Data e ora", "DateTime": "Data e ora",
"CreateTime": "Data di creazione", "CreatedTime": "Data di creazione",
"LastModifiedTime": "Data di ultima modifica", "LastModifiedTime": "Data di ultima modifica",
"AutoNumber": "Numerazione automatica", "AutoNumber": "Numerazione automatica",
"Barcode": "Codice a barre", "Barcode": "Codice a barre",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "I seguenti caratteri non sono ammessi", "followingCharactersAreNotAllowed": "I seguenti caratteri non sono ammessi",
"columnNameRequired": "Il nome della colonna è richiesto", "columnNameRequired": "Il nome della colonna è richiesto",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Il nome del progetto supera i 50 caratteri", "projectNameExceeds50Characters": "Il nome del progetto supera i 50 caratteri",

3
packages/nc-gui/lang/ja.json

@ -273,7 +273,7 @@
"Count": "カウント", "Count": "カウント",
"Lookup": "ルックアップ", "Lookup": "ルックアップ",
"DateTime": "DateTime型", "DateTime": "DateTime型",
"CreateTime": "作成時刻", "CreatedTime": "作成時刻",
"LastModifiedTime": "最終更新日時", "LastModifiedTime": "最終更新日時",
"AutoNumber": "自動採番", "AutoNumber": "自動採番",
"Barcode": "バーコード", "Barcode": "バーコード",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "以下の文字種は使用できません", "followingCharactersAreNotAllowed": "以下の文字種は使用できません",
"columnNameRequired": "列名が必要です", "columnNameRequired": "列名が必要です",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "プロジェクト名が50文字を超えています", "projectNameExceeds50Characters": "プロジェクト名が50文字を超えています",

3
packages/nc-gui/lang/ko.json

@ -273,7 +273,7 @@
"Count": "카운트", "Count": "카운트",
"Lookup": "조회", "Lookup": "조회",
"DateTime": "일시", "DateTime": "일시",
"CreateTime": "생성시간", "CreatedTime": "생성시간",
"LastModifiedTime": "최종수정시간", "LastModifiedTime": "최종수정시간",
"AutoNumber": "자동번호", "AutoNumber": "자동번호",
"Barcode": "바코드", "Barcode": "바코드",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "다음 문자는 허용되지 않습니다.", "followingCharactersAreNotAllowed": "다음 문자는 허용되지 않습니다.",
"columnNameRequired": "컬럼 이름이 필요합니다.", "columnNameRequired": "컬럼 이름이 필요합니다.",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "열 이름의 길이가 최대 {value}자를 초과합니다.", "columnNameExceedsCharacters": "열 이름의 길이가 최대 {value}자를 초과합니다.",
"projectNameExceeds50Characters": "프로젝트 이름의 길이가 최대 50자를 초과합니다.", "projectNameExceeds50Characters": "프로젝트 이름의 길이가 최대 50자를 초과합니다.",

3
packages/nc-gui/lang/lv.json

@ -273,7 +273,7 @@
"Count": "Skaits", "Count": "Skaits",
"Lookup": "Uzmeklēšana", "Lookup": "Uzmeklēšana",
"DateTime": "Datums un laiks", "DateTime": "Datums un laiks",
"CreateTime": "Izveidošanas laiks", "CreatedTime": "Izveidošanas laiks",
"LastModifiedTime": "Modificēšanas laiks", "LastModifiedTime": "Modificēšanas laiks",
"AutoNumber": "Automātiska numerācija", "AutoNumber": "Automātiska numerācija",
"Barcode": "Svītru kods", "Barcode": "Svītru kods",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Šādas rakstzīmes nav atļautas", "followingCharactersAreNotAllowed": "Šādas rakstzīmes nav atļautas",
"columnNameRequired": "Slejas nosaukums ir obligāts", "columnNameRequired": "Slejas nosaukums ir obligāts",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Projekta nosaukums pārsniedz 50 rakstzīmes", "projectNameExceeds50Characters": "Projekta nosaukums pārsniedz 50 rakstzīmes",

3
packages/nc-gui/lang/nl.json

@ -273,7 +273,7 @@
"Count": "Telling", "Count": "Telling",
"Lookup": "Zoekopdracht", "Lookup": "Zoekopdracht",
"DateTime": "Tijdstip", "DateTime": "Tijdstip",
"CreateTime": "Maak Tijd", "CreatedTime": "Maak Tijd",
"LastModifiedTime": "Laatst gewijzigd", "LastModifiedTime": "Laatst gewijzigd",
"AutoNumber": "Automatische nummering", "AutoNumber": "Automatische nummering",
"Barcode": "Barcode", "Barcode": "Barcode",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "De volgende tekens zijn niet toegestaan", "followingCharactersAreNotAllowed": "De volgende tekens zijn niet toegestaan",
"columnNameRequired": "Kolomnaam is verplicht", "columnNameRequired": "Kolomnaam is verplicht",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Projectnaam meer dan 50 tekens", "projectNameExceeds50Characters": "Projectnaam meer dan 50 tekens",

3
packages/nc-gui/lang/no.json

@ -273,7 +273,7 @@
"Count": "Telle", "Count": "Telle",
"Lookup": "Se opp", "Lookup": "Se opp",
"DateTime": "Dato tid", "DateTime": "Dato tid",
"CreateTime": "Skape tid", "CreatedTime": "Skape tid",
"LastModifiedTime": "Sist endret tid", "LastModifiedTime": "Sist endret tid",
"AutoNumber": "Auto nummer", "AutoNumber": "Auto nummer",
"Barcode": "Strekkode", "Barcode": "Strekkode",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/pl.json

@ -273,7 +273,7 @@
"Count": "Licznik", "Count": "Licznik",
"Lookup": "Wyszukiwanie", "Lookup": "Wyszukiwanie",
"DateTime": "Data i czas", "DateTime": "Data i czas",
"CreateTime": "Data utworzenia", "CreatedTime": "Data utworzenia",
"LastModifiedTime": "Data ostatniej modyfikacji", "LastModifiedTime": "Data ostatniej modyfikacji",
"AutoNumber": "Automatyczny numer", "AutoNumber": "Automatyczny numer",
"Barcode": "Kod kreskowy", "Barcode": "Kod kreskowy",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Następujące znaki są niedozwolone", "followingCharactersAreNotAllowed": "Następujące znaki są niedozwolone",
"columnNameRequired": "Nazwa kolumny jest wymagana", "columnNameRequired": "Nazwa kolumny jest wymagana",
"duplicateColumnName": "Zduplikowana nazwa kolumny", "duplicateColumnName": "Zduplikowana nazwa kolumny",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "Typ danych UI jest wymagany", "uiDataTypeRequired": "Typ danych UI jest wymagany",
"columnNameExceedsCharacters": "Nazwa kolumny przekracza maksymalną długość {value} znaków", "columnNameExceedsCharacters": "Nazwa kolumny przekracza maksymalną długość {value} znaków",
"projectNameExceeds50Characters": "Nazwa projektu przekracza 50 znaków", "projectNameExceeds50Characters": "Nazwa projektu przekracza 50 znaków",

3
packages/nc-gui/lang/pt.json

@ -273,7 +273,7 @@
"Count": "Contar", "Count": "Contar",
"Lookup": "Olho para cima", "Lookup": "Olho para cima",
"DateTime": "Data hora", "DateTime": "Data hora",
"CreateTime": "Criar tempo", "CreatedTime": "Criar tempo",
"LastModifiedTime": "Última hora modificada", "LastModifiedTime": "Última hora modificada",
"AutoNumber": "Número automático", "AutoNumber": "Número automático",
"Barcode": "Código de barras", "Barcode": "Código de barras",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Os seguintes caracteres não são permitidos", "followingCharactersAreNotAllowed": "Os seguintes caracteres não são permitidos",
"columnNameRequired": "O nome da coluna é obrigatório", "columnNameRequired": "O nome da coluna é obrigatório",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "O nome do projecto excede 50 caracteres", "projectNameExceeds50Characters": "O nome do projecto excede 50 caracteres",

3
packages/nc-gui/lang/pt_BR.json

@ -273,7 +273,7 @@
"Count": "Quantidade", "Count": "Quantidade",
"Lookup": "Procurar", "Lookup": "Procurar",
"DateTime": "Data e hora", "DateTime": "Data e hora",
"CreateTime": "Hora da criação", "CreatedTime": "Hora da criação",
"LastModifiedTime": "Hora da última alteração", "LastModifiedTime": "Hora da última alteração",
"AutoNumber": "Número automático", "AutoNumber": "Número automático",
"Barcode": "Código de barras", "Barcode": "Código de barras",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Os seguintes caracteres não são permitidos", "followingCharactersAreNotAllowed": "Os seguintes caracteres não são permitidos",
"columnNameRequired": "O nome da coluna é obrigatório", "columnNameRequired": "O nome da coluna é obrigatório",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "O nome do projecto excede 50 caracteres", "projectNameExceeds50Characters": "O nome do projecto excede 50 caracteres",

165
packages/nc-gui/lang/ru.json

@ -78,36 +78,36 @@
"quote": "Quote", "quote": "Quote",
"submit": "Отправить", "submit": "Отправить",
"create": "Создать", "create": "Создать",
"createEntity": "Create {entity}", "createEntity": "Создать {entity}",
"creating": "Creating", "creating": "Создание",
"creatingEntity": "Creating {entity}", "creatingEntity": "Создание {entity}",
"details": "Details", "details": "Подробности",
"skip": "Skip", "skip": "Пропустить",
"code": "Code", "code": "Код",
"duplicate": "Копировать", "duplicate": "Копировать",
"duplicating": "Duplicating", "duplicating": "Дублирование",
"activate": "Activate", "activate": "Активация",
"action": "Action", "action": "Действие",
"insert": "Вставить", "insert": "Вставить",
"delete": "Удалить", "delete": "Удалить",
"deleteEntity": "Delete {entity}", "deleteEntity": "Удалить {entity}",
"bulkInsert": "Bulk Insert", "bulkInsert": "Массовая вставка",
"bulkDelete": "Bulk Delete", "bulkDelete": "Массовое удаление",
"bulkUpdate": "Bulk Update", "bulkUpdate": "Массовое обновление",
"deleting": "Deleting", "deleting": "Удаление",
"update": "Обновить", "update": "Обновить",
"rename": "Переименовать", "rename": "Переименовать",
"reload": "Перезагрузить", "reload": "Перезагрузить",
"reset": "Сброс настроек", "reset": "Сброс настроек",
"install": "Установить", "install": "Установить",
"show": "Показать", "show": "Показать",
"access": "Access", "access": "Доступ",
"visibility": "Visibility", "visibility": "Видимость",
"hide": "Скрыть", "hide": "Скрыть",
"deprecated": "Deprecated", "deprecated": "Устарело",
"showAll": "Показать все", "showAll": "Показать все",
"hideAll": "Скрыть все", "hideAll": "Скрыть все",
"notFound": "Not found", "notFound": "Не найдено",
"showMore": "Подробнее", "showMore": "Подробнее",
"showOptions": "Открыть настройки", "showOptions": "Открыть настройки",
"hideOptions": "Скрыть настройки", "hideOptions": "Скрыть настройки",
@ -127,8 +127,8 @@
"upload": "Загрузить", "upload": "Загрузить",
"download": "Скачать", "download": "Скачать",
"default": "По умолчанию", "default": "По умолчанию",
"base": "Source", "base": "Источник",
"datasource": "Data Source", "datasource": "Источник данных",
"more": "Больше", "more": "Больше",
"less": "Меньше", "less": "Меньше",
"event": "Событие", "event": "Событие",
@ -136,7 +136,7 @@
"after": "После", "after": "После",
"before": "До", "before": "До",
"search": "Найти", "search": "Найти",
"searchIn": "Search In", "searchIn": "Искать в",
"notification": "Уведомление", "notification": "Уведомление",
"reference": "Ссылка", "reference": "Ссылка",
"function": "Функция", "function": "Функция",
@ -160,29 +160,29 @@
"hideField": "Скрыть поле", "hideField": "Скрыть поле",
"sortAsc": "По Возрастанию", "sortAsc": "По Возрастанию",
"sortDesc": "По убыванию", "sortDesc": "По убыванию",
"move": "Move", "move": "Переместить",
"geoDataField": "Поле геоданных", "geoDataField": "Поле геоданных",
"type": "Type", "type": "Тип",
"name": "Name", "name": "Имя",
"changes": "Changes", "changes": "Изменения",
"new": "New", "new": "Создать",
"old": "Old", "old": "Старый",
"data": "Data", "data": "Данные",
"source": "Source", "source": "Источник",
"destination": "Destination", "destination": "Назначение",
"active": "Active", "active": "Активно",
"inactive": "Inactive", "inactive": "Неактивный",
"linked": "linked", "linked": "связанный",
"finish": "Finish", "finish": "Готово",
"min": "Min", "min": "Минимум",
"max": "Max", "max": "Максимум",
"avg": "Avg", "avg": "Среднее",
"sum": "Sum", "sum": "Сумма",
"count": "Count", "count": "Количество",
"countDistinct": "Count Distinct", "countDistinct": "Количество уникальных",
"sumDistinct": "Sum Distinct", "sumDistinct": "Сумма уникальных",
"avgDistinct": "Avg Distinct", "avgDistinct": "Среднее уникальных",
"join": "Join", "join": "Присоединиться",
"options": "Опции", "options": "Опции",
"primaryValue": "Первичное значение", "primaryValue": "Первичное значение",
"useSurveyMode": "Использовать режим анкеты", "useSurveyMode": "Использовать режим анкеты",
@ -273,7 +273,7 @@
"Count": "Количество", "Count": "Количество",
"Lookup": "Подстановка (Lookup)", "Lookup": "Подстановка (Lookup)",
"DateTime": "Дата и время", "DateTime": "Дата и время",
"CreateTime": "Создан", "CreatedTime": "Создан",
"LastModifiedTime": "Изменен", "LastModifiedTime": "Изменен",
"AutoNumber": "Счетчик", "AutoNumber": "Счетчик",
"Barcode": "Штрих-код", "Barcode": "Штрих-код",
@ -329,22 +329,22 @@
"linkMore": "Ссылка Подробнее", "linkMore": "Ссылка Подробнее",
"linkMoreRecords": "Связать больше записей", "linkMoreRecords": "Связать больше записей",
"downloadFile": "Скачать файл", "downloadFile": "Скачать файл",
"renameTable": "Rename Table", "renameTable": "Переименовать таблицу",
"renamingTable": "Renaming Table", "renamingTable": "Переименование таблицы",
"renamingWs": "Renaming Workspace", "renamingWs": "Переименование рабочей области",
"renameWs": "Rename Workspace", "renameWs": "Переименовать рабочую область",
"deleteWs": "Delete Workspace", "deleteWs": "Удалить рабочую область",
"deletingWs": "Deleting Workspace", "deletingWs": "Удаление рабочей области",
"copyAuthToken": "Copy Auth Token", "copyAuthToken": "Скопировать токен авторизации",
"copiedAuthToken": "Copied Auth Token", "copiedAuthToken": "Токен аутентификации скопирован",
"copyInviteToken": "Copy Invite Token", "copyInviteToken": "Скопировать токен приглашения",
"showSidebar": "Show Sidebar", "showSidebar": "Показать боковую панель",
"hideSidebar": "Hide Sidebar", "hideSidebar": "Скрыть боковую панель",
"creatingTable": "Creating Table", "creatingTable": "Создание таблицы",
"erdView": "Представление ERD", "erdView": "Представление ERD",
"newBase": "New Data Source", "newBase": "Новый источник данных",
"newProj": "Новый проект", "newProj": "Новый проект",
"createBase": "Create Base", "createBase": "Создать базу",
"myProject": "Мои проекты", "myProject": "Мои проекты",
"formTitle": "Заголовок формы", "formTitle": "Заголовок формы",
"collaborative": "Collaborative", "collaborative": "Collaborative",
@ -354,7 +354,7 @@
"teamAndAuth": "Пользватели и API", "teamAndAuth": "Пользватели и API",
"rolesUserMgmt": "Роли и управление пользователями", "rolesUserMgmt": "Роли и управление пользователями",
"userMgmt": "Управление пользователями", "userMgmt": "Управление пользователями",
"apiTokens": "API Tokens", "apiTokens": "API токены",
"apiTokenMgmt": "Управление токенами API", "apiTokenMgmt": "Управление токенами API",
"rolesMgmt": "Управление ролями", "rolesMgmt": "Управление ролями",
"projMeta": "Метаданные проекта", "projMeta": "Метаданные проекта",
@ -381,44 +381,44 @@
"swaggerDocumentation": "Документация Swagger", "swaggerDocumentation": "Документация Swagger",
"quickImportFrom": "Быстрый импорт из", "quickImportFrom": "Быстрый импорт из",
"quickImport": "Быстрый импорт", "quickImport": "Быстрый импорт",
"quickImportAirtable": "Quick Import - Airtable", "quickImportAirtable": "Быстрый импорт - Airtable",
"quickImportCSV": "Quick Import - CSV", "quickImportCSV": "Быстрый импорт - CSV",
"quickImportExcel": "Quick Import - Excel", "quickImportExcel": "Быстрый импорт - Excel",
"quickImportJSON": "Quick Import - JSON", "quickImportJSON": "Быстрый импорт - JSON",
"jsonEditor": "JSON Editor", "jsonEditor": "Редактор JSON",
"comingSoon": "Coming Soon", "comingSoon": "Coming Soon",
"advancedSettings": "Расширенные настройки", "advancedSettings": "Расширенные настройки",
"codeSnippet": "Сниппет кода", "codeSnippet": "Сниппет кода",
"keyboardShortcut": "Горячие клавиши", "keyboardShortcut": "Горячие клавиши",
"generateRandomName": "Сгенерировать случайное имя", "generateRandomName": "Сгенерировать случайное имя",
"findRowByScanningCode": "Найти строку путем сканирования QR или штрих-кода", "findRowByScanningCode": "Найти строку путем сканирования QR или штрих-кода",
"tokenManagement": "Token Management", "tokenManagement": "Управление токенами",
"addNewToken": "Add new token", "addNewToken": "Добавить новый токен",
"accountSettings": "Account Settings", "accountSettings": "Настройки аккаунта",
"resetPasswordMenu": "Reset Password", "resetPasswordMenu": "Сбросить пароль",
"tokens": "Tokens", "tokens": "Токены",
"userManagement": "User Management", "userManagement": "Управление пользователями",
"accountManagement": "Account management", "accountManagement": "Управление аккаунтом",
"licence": "Licence", "licence": "Лицензия",
"allowAllMimeTypes": "Allow All Mime Types", "allowAllMimeTypes": "Разрешить все типы Mime",
"defaultView": "Default View", "defaultView": "Default View",
"relations": "Relations", "relations": "Relations",
"switchLanguage": "Switch Language", "switchLanguage": "Переключить язык",
"renameFile": "Rename File", "renameFile": "Переименовать файл",
"links": { "links": {
"noAction": "No Action", "noAction": "No Action",
"cascade": "Cascade", "cascade": "Cascade",
"restrict": "Restrict", "restrict": "Restrict",
"setNull": "Set NULL", "setNull": "Установить в NULL",
"setDefault": "Set Default" "setDefault": "Установить по умолчанию"
} }
}, },
"labels": { "labels": {
"heading1": "Heading 1", "heading1": "Заголовок 1",
"heading2": "Heading 2", "heading2": "Заголовок 2",
"heading3": "Heading 3", "heading3": "Заголовок 3",
"bold": "Bold", "bold": "Жирный",
"italic": "Italic", "italic": "Курсив",
"underline": "Underline", "underline": "Underline",
"strike": "Strike", "strike": "Strike",
"taskList": "Список задач", "taskList": "Список задач",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Нельзя использовать следующие символы", "followingCharactersAreNotAllowed": "Нельзя использовать следующие символы",
"columnNameRequired": "Требуется название столбца", "columnNameRequired": "Требуется название столбца",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "Длина имени колонки превышает максимальную {value} символов", "columnNameExceedsCharacters": "Длина имени колонки превышает максимальную {value} символов",
"projectNameExceeds50Characters": "Название проекта превышает 50 символов", "projectNameExceeds50Characters": "Название проекта превышает 50 символов",

3
packages/nc-gui/lang/sk.json

@ -273,7 +273,7 @@
"Count": "Počítajte", "Count": "Počítajte",
"Lookup": "Vyhľadávanie", "Lookup": "Vyhľadávanie",
"DateTime": "Dátum Čas", "DateTime": "Dátum Čas",
"CreateTime": "Vytvoriť čas", "CreatedTime": "Vytvoriť čas",
"LastModifiedTime": "Čas poslednej úpravy", "LastModifiedTime": "Čas poslednej úpravy",
"AutoNumber": "Automatické číslo", "AutoNumber": "Automatické číslo",
"Barcode": "Čiarový kód", "Barcode": "Čiarový kód",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Nasledujúce znaky nie sú povolené", "followingCharactersAreNotAllowed": "Nasledujúce znaky nie sú povolené",
"columnNameRequired": "Vyžaduje sa názov stĺpca", "columnNameRequired": "Vyžaduje sa názov stĺpca",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Názov projektu presahuje 50 znakov", "projectNameExceeds50Characters": "Názov projektu presahuje 50 znakov",

3
packages/nc-gui/lang/sl.json

@ -273,7 +273,7 @@
"Count": "Count.", "Count": "Count.",
"Lookup": "Poglej gor", "Lookup": "Poglej gor",
"DateTime": "Datum čas", "DateTime": "Datum čas",
"CreateTime": "Ustvarite čas", "CreatedTime": "Ustvarite čas",
"LastModifiedTime": "Zadnji spremenjen čas", "LastModifiedTime": "Zadnji spremenjen čas",
"AutoNumber": "Samodejna številka", "AutoNumber": "Samodejna številka",
"Barcode": "Črtna koda", "Barcode": "Črtna koda",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Naslednji znaki niso dovoljeni", "followingCharactersAreNotAllowed": "Naslednji znaki niso dovoljeni",
"columnNameRequired": "Ime stolpca je obvezno", "columnNameRequired": "Ime stolpca je obvezno",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Ime projekta presega 50 znakov", "projectNameExceeds50Characters": "Ime projekta presega 50 znakov",

3
packages/nc-gui/lang/sv.json

@ -273,7 +273,7 @@
"Count": "Räkna", "Count": "Räkna",
"Lookup": "Slå upp", "Lookup": "Slå upp",
"DateTime": "Datum Tid", "DateTime": "Datum Tid",
"CreateTime": "Skapa tid", "CreatedTime": "Skapa tid",
"LastModifiedTime": "Senast ändrad tid", "LastModifiedTime": "Senast ändrad tid",
"AutoNumber": "Automatisk nummer", "AutoNumber": "Automatisk nummer",
"Barcode": "Streckkod", "Barcode": "Streckkod",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Följande tecken är inte tillåtna", "followingCharactersAreNotAllowed": "Följande tecken är inte tillåtna",
"columnNameRequired": "Kolumnnamn krävs", "columnNameRequired": "Kolumnnamn krävs",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Projektnamnet har mer än 50 tecken", "projectNameExceeds50Characters": "Projektnamnet har mer än 50 tecken",

3
packages/nc-gui/lang/th.json

@ -273,7 +273,7 @@
"Count": "นบ", "Count": "นบ",
"Lookup": "การคนหา", "Lookup": "การคนหา",
"DateTime": "วนเวลา", "DateTime": "วนเวลา",
"CreateTime": "สรางเวลา", "CreatedTime": "สรางเวลา",
"LastModifiedTime": "เวลาทแกไขลาสด", "LastModifiedTime": "เวลาทแกไขลาสด",
"AutoNumber": "หมายเลขอตโนม", "AutoNumber": "หมายเลขอตโนม",
"Barcode": "บารโคด", "Barcode": "บารโคด",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

3
packages/nc-gui/lang/tr.json

@ -273,7 +273,7 @@
"Count": "Say", "Count": "Say",
"Lookup": "Referans", "Lookup": "Referans",
"DateTime": "Tarih saat", "DateTime": "Tarih saat",
"CreateTime": "Oluşturma zamanı", "CreatedTime": "Oluşturma zamanı",
"LastModifiedTime": "Son değiştirilme", "LastModifiedTime": "Son değiştirilme",
"AutoNumber": "Otomatik Sayı", "AutoNumber": "Otomatik Sayı",
"Barcode": "Barkod", "Barcode": "Barkod",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Aşağıdaki karakterlere izin verilmez", "followingCharactersAreNotAllowed": "Aşağıdaki karakterlere izin verilmez",
"columnNameRequired": "Sütun adı gereklidir", "columnNameRequired": "Sütun adı gereklidir",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "Sütun adının uzunluğu maksimum {value} karakter sınırını aşıyor", "columnNameExceedsCharacters": "Sütun adının uzunluğu maksimum {value} karakter sınırını aşıyor",
"projectNameExceeds50Characters": "Proje adı 50 karakteri aşıyor", "projectNameExceeds50Characters": "Proje adı 50 karakteri aşıyor",

3
packages/nc-gui/lang/uk.json

@ -273,7 +273,7 @@
"Count": "Кількість", "Count": "Кількість",
"Lookup": "Пошук", "Lookup": "Пошук",
"DateTime": "Дата і час", "DateTime": "Дата і час",
"CreateTime": "Час створення", "CreatedTime": "Час створення",
"LastModifiedTime": "Час останньої зміни", "LastModifiedTime": "Час останньої зміни",
"AutoNumber": "Автоматичне прирощування", "AutoNumber": "Автоматичне прирощування",
"Barcode": "Штрих-код", "Barcode": "Штрих-код",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Наступні символи не допускаються", "followingCharactersAreNotAllowed": "Наступні символи не допускаються",
"columnNameRequired": "Ім'я стовпця є обов'язковим", "columnNameRequired": "Ім'я стовпця є обов'язковим",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "Довжина назви стовпця перевищує максимальну кількість в {value} символів", "columnNameExceedsCharacters": "Довжина назви стовпця перевищує максимальну кількість в {value} символів",
"projectNameExceeds50Characters": "Назва проєкту перевищує 50 символів", "projectNameExceeds50Characters": "Назва проєкту перевищує 50 символів",

3
packages/nc-gui/lang/vi.json

@ -273,7 +273,7 @@
"Count": "Đếm", "Count": "Đếm",
"Lookup": "Tra cứu", "Lookup": "Tra cứu",
"DateTime": "Ngày giờ", "DateTime": "Ngày giờ",
"CreateTime": "Tạo thời gian", "CreatedTime": "Tạo thời gian",
"LastModifiedTime": "Thời gian sửa đổi lần cuối", "LastModifiedTime": "Thời gian sửa đổi lần cuối",
"AutoNumber": "Số tự động", "AutoNumber": "Số tự động",
"Barcode": "Mã vạch.", "Barcode": "Mã vạch.",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "Following characters are not allowed", "followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required", "columnNameRequired": "Column name is required",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "Project name exceeds 50 characters", "projectNameExceeds50Characters": "Project name exceeds 50 characters",

17
packages/nc-gui/lang/zh-Hans.json

@ -56,7 +56,7 @@
"links": "链接", "links": "链接",
"remove": "移除", "remove": "移除",
"import": "导入", "import": "导入",
"logout": "退出登录", "logout": "退出",
"empty": "空", "empty": "空",
"changeIcon": "更换图标", "changeIcon": "更换图标",
"save": "保存", "save": "保存",
@ -189,7 +189,7 @@
"shift": "Shift 键", "shift": "Shift 键",
"enter": "回车键", "enter": "回车键",
"seconds": "秒", "seconds": "秒",
"paste": "Paste" "paste": "粘贴"
}, },
"objects": { "objects": {
"workspace": "工作区", "workspace": "工作区",
@ -273,7 +273,7 @@
"Count": "计数", "Count": "计数",
"Lookup": "查找", "Lookup": "查找",
"DateTime": "日期时间", "DateTime": "日期时间",
"CreateTime": "创建时间", "CreatedTime": "创建时间",
"LastModifiedTime": "最后修改时间", "LastModifiedTime": "最后修改时间",
"AutoNumber": "自动编号", "AutoNumber": "自动编号",
"Barcode": "条形码", "Barcode": "条形码",
@ -587,7 +587,7 @@
"links": "链接", "links": "链接",
"onUpdate": "更新时", "onUpdate": "更新时",
"onDelete": "删除时", "onDelete": "删除时",
"account": "户", "account": "户",
"language": "语言", "language": "语言",
"primaryColor": "主色调", "primaryColor": "主色调",
"accentColor": "强调色", "accentColor": "强调色",
@ -642,7 +642,7 @@
"changeWsName": "更改工作区名称", "changeWsName": "更改工作区名称",
"pressEnter": "按回车键", "pressEnter": "按回车键",
"newFormLoaded": "新表格将在", "newFormLoaded": "新表格将在",
"webhook": "Webhook" "webhook": "网络钩子"
}, },
"activity": { "activity": {
"openInANewTab": "在新标签页中打开", "openInANewTab": "在新标签页中打开",
@ -1022,7 +1022,7 @@
"acceptOnlyValid": "仅接受", "acceptOnlyValid": "仅接受",
"apiTokenCreate": "创建个人 API tokens,以便在自动化或外部应用程序中使用。", "apiTokenCreate": "创建个人 API tokens,以便在自动化或外部应用程序中使用。",
"selectFieldToSort": "选择要排序的字段", "selectFieldToSort": "选择要排序的字段",
"selectFieldToGroup": "Select Field to Group", "selectFieldToGroup": "选择要分组的字段",
"thereAreNoRecordsInTable": "表中没有记录", "thereAreNoRecordsInTable": "表中没有记录",
"createWebhookMsg1": "开始使用Web Hooks!", "createWebhookMsg1": "开始使用Web Hooks!",
"createWebhookMsg2": "创建Web Hooks来实现自动化", "createWebhookMsg2": "创建Web Hooks来实现自动化",
@ -1211,7 +1211,7 @@
"goToNext": "转到下一个", "goToNext": "转到下一个",
"thankYou": "谢谢你!", "thankYou": "谢谢你!",
"submittedFormData": "您已成功提交表单数据。", "submittedFormData": "您已成功提交表单数据。",
"editingSystemKeyNotSupported": "Editing system key not supported" "editingSystemKeyNotSupported": "不支持编辑系统密钥"
}, },
"error": { "error": {
"nameRequired": "您必须指定一个名字", "nameRequired": "您必须指定一个名字",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "不允许使用以下字符", "followingCharactersAreNotAllowed": "不允许使用以下字符",
"columnNameRequired": "列名是必填项", "columnNameRequired": "列名是必填项",
"duplicateColumnName": "字段名称重复", "duplicateColumnName": "字段名称重复",
"duplicateSystemColumnName": "字段名称已被系统使用",
"uiDataTypeRequired": "需要 UI 数据类型", "uiDataTypeRequired": "需要 UI 数据类型",
"columnNameExceedsCharacters": "列名的长度超过了 {value} 字符的限制", "columnNameExceedsCharacters": "列名的长度超过了 {value} 字符的限制",
"projectNameExceeds50Characters": "项目名称超过 50 个字符", "projectNameExceeds50Characters": "项目名称超过 50 个字符",
@ -1288,7 +1289,7 @@
"fieldRequired": "{value} 不能为空。", "fieldRequired": "{value} 不能为空。",
"projectNotAccessible": "无权访问此项目", "projectNotAccessible": "无权访问此项目",
"copyToClipboardError": "未能复制到剪贴板", "copyToClipboardError": "未能复制到剪贴板",
"pasteFromClipboardError": "Failed to paste from clipboard" "pasteFromClipboardError": "从剪贴板粘贴失败"
}, },
"toast": { "toast": {
"exportMetadata": "项目元数据成功导出", "exportMetadata": "项目元数据成功导出",

3
packages/nc-gui/lang/zh-Hant.json

@ -273,7 +273,7 @@
"Count": "計數", "Count": "計數",
"Lookup": "查閱", "Lookup": "查閱",
"DateTime": "日期時間", "DateTime": "日期時間",
"CreateTime": "創建時間", "CreatedTime": "創建時間",
"LastModifiedTime": "最後修改時間", "LastModifiedTime": "最後修改時間",
"AutoNumber": "自動編號", "AutoNumber": "自動編號",
"Barcode": "條碼", "Barcode": "條碼",
@ -1274,6 +1274,7 @@
"followingCharactersAreNotAllowed": "以下字元不允許", "followingCharactersAreNotAllowed": "以下字元不允許",
"columnNameRequired": "欄位名稱必填", "columnNameRequired": "欄位名稱必填",
"duplicateColumnName": "Duplicate field name", "duplicateColumnName": "Duplicate field name",
"duplicateSystemColumnName": "Name already used for system field",
"uiDataTypeRequired": "UI data type is required", "uiDataTypeRequired": "UI data type is required",
"columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters", "columnNameExceedsCharacters": "The length of column name exceeds the max {value} characters",
"projectNameExceeds50Characters": "專案名稱超過 50 個字元", "projectNameExceeds50Characters": "專案名稱超過 50 個字元",

6
packages/nc-gui/lib/types.ts

@ -177,6 +177,11 @@ interface SidebarTableNode extends TableType {
isViewsLoading?: boolean isViewsLoading?: boolean
} }
interface UsersSortType {
field?: 'email' | 'roles'
direction?: 'asc' | 'desc'
}
export type { export type {
User, User,
ProjectMetaInfo, ProjectMetaInfo,
@ -202,4 +207,5 @@ export type {
ViewPageType, ViewPageType,
NcButtonSize, NcButtonSize,
SidebarTableNode, SidebarTableNode,
UsersSortType,
} }

20
packages/nc-gui/package.json

@ -73,14 +73,14 @@
"marked": "^4.3.0", "marked": "^4.3.0",
"monaco-editor": "^0.33.0", "monaco-editor": "^0.33.0",
"monaco-sql-languages": "^0.11.0", "monaco-sql-languages": "^0.11.0",
"nocodb-sdk": "0.203.2", "nocodb-sdk": "workspace:^",
"papaparse": "^5.4.1", "papaparse": "^5.4.1",
"parse-github-url": "^1.0.2", "parse-github-url": "^1.0.2",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"rfdc": "^1.3.0", "rfdc": "^1.3.0",
"showdown": "^2.1.0", "showdown": "^2.1.0",
"socket.io-client": "^4.7.2", "socket.io-client": "^4.7.3",
"sortablejs": "^1.15.1", "sortablejs": "^1.15.1",
"splitpanes": "^3.1.5", "splitpanes": "^3.1.5",
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
@ -105,7 +105,7 @@
"@antfu/eslint-config": "^0.26.3", "@antfu/eslint-config": "^0.26.3",
"@esbuild-plugins/node-modules-polyfill": "^0.2.2", "@esbuild-plugins/node-modules-polyfill": "^0.2.2",
"@iconify-json/ant-design": "^1.1.13", "@iconify-json/ant-design": "^1.1.13",
"@iconify-json/bi": "^1.1.22", "@iconify-json/bi": "^1.1.23",
"@iconify-json/carbon": "^1.1.27", "@iconify-json/carbon": "^1.1.27",
"@iconify-json/cil": "^1.1.8", "@iconify-json/cil": "^1.1.8",
"@iconify-json/clarity": "^1.1.12", "@iconify-json/clarity": "^1.1.12",
@ -114,18 +114,18 @@
"@iconify-json/ion": "^1.1.15", "@iconify-json/ion": "^1.1.15",
"@iconify-json/la": "^1.1.8", "@iconify-json/la": "^1.1.8",
"@iconify-json/logos": "^1.1.42", "@iconify-json/logos": "^1.1.42",
"@iconify-json/lucide": "^1.1.149", "@iconify-json/lucide": "^1.1.152",
"@iconify-json/material-symbols": "^1.1.68", "@iconify-json/material-symbols": "^1.1.69",
"@iconify-json/mdi": "^1.1.63", "@iconify-json/mdi": "^1.1.64",
"@iconify-json/mi": "^1.1.8", "@iconify-json/mi": "^1.1.8",
"@iconify-json/ph": "^1.1.9", "@iconify-json/ph": "^1.1.9",
"@iconify-json/ri": "^1.1.18", "@iconify-json/ri": "^1.1.18",
"@iconify-json/simple-icons": "^1.1.86", "@iconify-json/simple-icons": "^1.1.87",
"@iconify-json/system-uicons": "^1.1.12", "@iconify-json/system-uicons": "^1.1.12",
"@iconify-json/tabler": "^1.1.102", "@iconify-json/tabler": "^1.1.103",
"@iconify-json/vscode-icons": "^1.1.32", "@iconify-json/vscode-icons": "^1.1.32",
"@intlify/unplugin-vue-i18n": "^0.12.3", "@intlify/unplugin-vue-i18n": "^0.12.3",
"@nuxt/image-edge": "1.1.0-28393680.ddd021d", "@nuxt/image-edge": "1.1.0-28408112.572f502",
"@types/d3-scale": "^4.0.8", "@types/d3-scale": "^4.0.8",
"@types/dagre": "^0.7.52", "@types/dagre": "^0.7.52",
"@types/file-saver": "^2.0.7", "@types/file-saver": "^2.0.7",
@ -156,7 +156,7 @@
"nuxt": "^3.8.2", "nuxt": "^3.8.2",
"nuxt-windicss": "^2.6.1", "nuxt-windicss": "^2.6.1",
"prettier": "^2.8.8", "prettier": "^2.8.8",
"sass": "^1.69.5", "sass": "^1.69.7",
"ts-loader": "^9.4.4", "ts-loader": "^9.4.4",
"unplugin-icons": "^0.14.15", "unplugin-icons": "^0.14.15",
"unplugin-vue-components": "^0.22.12", "unplugin-vue-components": "^0.22.12",

4
packages/nc-gui/utils/cell.ts

@ -15,6 +15,10 @@ export const isYear = (column: ColumnType, abstractType: any) => abstractType ==
export const isTime = (column: ColumnType, abstractType: any) => abstractType === 'time' || column.uidt === UITypes.Time export const isTime = (column: ColumnType, abstractType: any) => abstractType === 'time' || column.uidt === UITypes.Time
export const isDateTime = (column: ColumnType, abstractType: any) => export const isDateTime = (column: ColumnType, abstractType: any) =>
abstractType === 'datetime' || column.uidt === UITypes.DateTime abstractType === 'datetime' || column.uidt === UITypes.DateTime
export const isReadonlyDateTime = (column: ColumnType, _abstractType: any) =>
column.uidt === UITypes.CreatedTime || column.uidt === UITypes.LastModifiedTime
export const isReadonlyUser = (column: ColumnType, _abstractType: any) =>
column.uidt === UITypes.CreatedBy || column.uidt === UITypes.LastModifiedBy
export const isJSON = (column: ColumnType) => column.uidt === UITypes.JSON export const isJSON = (column: ColumnType) => column.uidt === UITypes.JSON
export const isEnum = (column: ColumnType) => column.uidt === UITypes.SingleSelect export const isEnum = (column: ColumnType) => column.uidt === UITypes.SingleSelect
export const isSingleSelect = (column: ColumnType) => column.uidt === UITypes.SingleSelect export const isSingleSelect = (column: ColumnType) => column.uidt === UITypes.SingleSelect

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

@ -136,7 +136,23 @@ const uiTypes = [
}, },
{ {
name: UITypes.User, name: UITypes.User,
icon: iconMap.account, icon: iconMap.phUser,
},
{
name: UITypes.CreatedTime,
icon: iconMap.datetime,
},
{
name: UITypes.LastModifiedTime,
icon: iconMap.datetime,
},
{
name: UITypes.CreatedBy,
icon: iconMap.phUser,
},
{
name: UITypes.LastModifiedBy,
icon: iconMap.phUser,
}, },
] ]
@ -145,7 +161,7 @@ const getUIDTIcon = (uidt: UITypes | string) => {
[ [
...uiTypes, ...uiTypes,
{ {
name: UITypes.CreateTime, name: UITypes.CreatedTime,
icon: iconMap.calendar, icon: iconMap.calendar,
}, },
{ {
@ -168,6 +184,7 @@ const isColumnRequired = (col?: ColumnType) => col && col.rqd && !col.cdf && !co
const isVirtualColRequired = (col: ColumnType, columns: ColumnType[]) => const isVirtualColRequired = (col: ColumnType, columns: ColumnType[]) =>
col.uidt === UITypes.LinkToAnotherRecord && col.uidt === UITypes.LinkToAnotherRecord &&
col.colOptions &&
(<LinkToAnotherRecordType>col.colOptions).type === RelationTypes.BELONGS_TO && (<LinkToAnotherRecordType>col.colOptions).type === RelationTypes.BELONGS_TO &&
isColumnRequired(columns.find((c) => c.id === (<LinkToAnotherRecordType>col.colOptions).fk_child_column_id)) isColumnRequired(columns.find((c) => c.id === (<LinkToAnotherRecordType>col.colOptions).fk_child_column_id))

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save