Browse Source

Merge pull request #3847 from nocodb/refactor/cells

refactor(nc-gui): reduce computed values generated by cells
pull/4441/head
Pranav C 2 years ago committed by GitHub
parent
commit
9c0a4c2441
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components.d.ts
  2. 4
      packages/nc-gui/components/cell/Email.vue
  3. 1
      packages/nc-gui/components/dashboard/TreeView.vue
  4. 121
      packages/nc-gui/components/smartsheet/Cell.vue
  5. 34
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  6. 132
      packages/nc-gui/components/smartsheet/header/CellIcon.ts
  7. 89
      packages/nc-gui/components/smartsheet/header/CellIcon.vue
  8. 27
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  9. 75
      packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts
  10. 4
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  11. 56
      packages/nc-gui/components/virtual-cell/Lookup.vue
  12. 98
      packages/nc-gui/composables/useColumn.ts
  13. 5
      packages/nc-gui/composables/useProject.ts
  14. 25
      packages/nc-gui/composables/useSmartsheetRowStore.ts
  15. 36
      packages/nc-gui/composables/useVirtualCell.ts
  16. 4
      packages/nc-gui/pages/forgot-password.vue
  17. 4
      packages/nc-gui/pages/signin.vue
  18. 15
      packages/nc-gui/pages/signup/[[token]].vue
  19. 53
      packages/nc-gui/utils/cell.ts
  20. 4
      packages/nc-gui/utils/parsers/parserHelpers.ts
  21. 2
      packages/nc-gui/utils/validation.ts
  22. 19
      packages/nc-gui/utils/virtualCell.ts

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

@ -95,6 +95,8 @@ declare module '@vue/runtime-core' {
MaterialSymbolsDarkModeOutline: typeof import('~icons/material-symbols/dark-mode-outline')['default'] MaterialSymbolsDarkModeOutline: typeof import('~icons/material-symbols/dark-mode-outline')['default']
MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default'] MaterialSymbolsFileCopyOutline: typeof import('~icons/material-symbols/file-copy-outline')['default']
MaterialSymbolsKeyboardReturn: typeof import('~icons/material-symbols/keyboard-return')['default'] MaterialSymbolsKeyboardReturn: typeof import('~icons/material-symbols/keyboard-return')['default']
MaterialSymbolsKeyboardShift: typeof import('~icons/material-symbols/keyboard-shift')['default']
MaterialSymbolsLightMode: typeof import('~icons/material-symbols/light-mode')['default']
MaterialSymbolsLightModeOutline: typeof import('~icons/material-symbols/light-mode-outline')['default'] MaterialSymbolsLightModeOutline: typeof import('~icons/material-symbols/light-mode-outline')['default']
MaterialSymbolsRocketLaunchOutline: typeof import('~icons/material-symbols/rocket-launch-outline')['default'] MaterialSymbolsRocketLaunchOutline: typeof import('~icons/material-symbols/rocket-launch-outline')['default']
MaterialSymbolsSendOutline: typeof import('~icons/material-symbols/send-outline')['default'] MaterialSymbolsSendOutline: typeof import('~icons/material-symbols/send-outline')['default']

4
packages/nc-gui/components/cell/Email.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { VNodeRef } from '@vue/runtime-core' import type { VNodeRef } from '@vue/runtime-core'
import { EditModeInj, computed, inject, isEmail, useVModel } from '#imports' import { EditModeInj, computed, inject, useVModel, validateEmail } from '#imports'
interface Props { interface Props {
modelValue: string | null | undefined modelValue: string | null | undefined
@ -18,7 +18,7 @@ const editEnabled = inject(EditModeInj)
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
const validEmail = computed(() => vModel.value && isEmail(vModel.value)) const validEmail = computed(() => vModel.value && validateEmail(vModel.value))
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus() const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script> </script>

1
packages/nc-gui/components/dashboard/TreeView.vue

@ -336,6 +336,7 @@ const onSearchCloseIconClick = () => {
> >
<GeneralTooltip class="pl-5 pr-3 py-2" modifier-key="Alt"> <GeneralTooltip class="pl-5 pr-3 py-2" modifier-key="Alt">
<template #title>{{ table.table_name }}</template> <template #title>{{ table.table_name }}</template>
<div class="flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)"> <div class="flex items-center gap-2 h-full" @contextmenu="setMenuContext('table', table)">
<div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`"> <div class="flex w-auto" :data-testid="`tree-view-table-draggable-handle-${table.title}`">
<MdiDragVertical <MdiDragVertical

121
packages/nc-gui/components/smartsheet/Cell.vue

@ -10,11 +10,36 @@ import {
ReadonlyInj, ReadonlyInj,
computed, computed,
inject, inject,
isAttachment,
isAutoSaved,
isBoolean,
isCurrency,
isDate,
isDateTime,
isDecimal,
isDuration,
isEmail,
isFloat,
isInt,
isJSON,
isManualSaved,
isMultiSelect,
isPercent,
isPhoneNumber,
isPrimary,
isPrimaryKey,
isRating,
isSingleSelect,
isString,
isTextArea,
isTime,
isURL,
isYear,
provide, provide,
ref, ref,
toRef, toRef,
useColumn,
useDebounceFn, useDebounceFn,
useProject,
useSmartsheetRowStoreOrThrow, useSmartsheetRowStoreOrThrow,
useVModel, useVModel,
} from '#imports' } from '#imports'
@ -46,9 +71,7 @@ provide(EditModeInj, useVModel(props, 'editEnabled', emit))
provide(ActiveCellInj, active) provide(ActiveCellInj, active)
if (readOnly?.value) {
provide(ReadonlyInj, readOnly) provide(ReadonlyInj, readOnly)
}
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
@ -58,6 +81,10 @@ const isLocked = inject(IsLockedInj, ref(false))
const { currentRow } = useSmartsheetRowStoreOrThrow() const { currentRow } = useSmartsheetRowStoreOrThrow()
const { sqlUi } = useProject()
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
const syncValue = useDebounceFn( const syncValue = useDebounceFn(
() => { () => {
currentRow.value.rowMeta.changed = false currentRow.value.rowMeta.changed = false
@ -66,33 +93,6 @@ const syncValue = useDebounceFn(
500, 500,
{ maxWait: 2000 }, { maxWait: 2000 },
) )
const {
isPrimary,
isURL,
isEmail,
isJSON,
isDate,
isYear,
isDateTime,
isTime,
isBoolean,
isDuration,
isRating,
isCurrency,
isAttachment,
isTextArea,
isString,
isInt,
isFloat,
isDecimal,
isSingleSelect,
isMultiSelect,
isPercent,
isPhoneNumber,
isAutoSaved,
isManualSaved,
isPrimaryKey,
} = useColumn(column)
const vModel = computed({ const vModel = computed({
get: () => props.modelValue, get: () => props.modelValue,
@ -100,9 +100,9 @@ const vModel = computed({
if (val !== props.modelValue) { if (val !== props.modelValue) {
currentRow.value.rowMeta.changed = true currentRow.value.rowMeta.changed = true
emit('update:modelValue', val) emit('update:modelValue', val)
if (isAutoSaved.value) { if (isAutoSaved(column.value)) {
syncValue() syncValue()
} else if (!isManualSaved.value) { } else if (!isManualSaved(column.value)) {
emit('save') emit('save')
currentRow.value.rowMeta.changed = true currentRow.value.rowMeta.changed = true
} }
@ -112,7 +112,7 @@ const vModel = computed({
const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => { const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => {
console.log('syncAndNavigate', e.target) console.log('syncAndNavigate', e.target)
if (isJSON.value) return if (isJSON(column.value)) return
if (currentRow.value.rowMeta.changed || currentRow.value.rowMeta.new) { if (currentRow.value.rowMeta.changed || currentRow.value.rowMeta.new) {
emit('save') emit('save')
@ -127,32 +127,41 @@ const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => {
<template> <template>
<div <div
class="nc-cell w-full" class="nc-cell w-full"
:class="[`nc-cell-${(column?.uidt || 'default').toLowerCase()}`, { 'text-blue-600': isPrimary && !virtual && !isForm }]" :class="[
`nc-cell-${(column?.uidt || 'default').toLowerCase()}`,
{ 'text-blue-600': isPrimary(column) && !virtual && !isForm },
]"
@keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)" @keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)"
> >
<LazyCellTextArea v-if="isTextArea" v-model="vModel" /> <template v-if="column">
<LazyCellCheckbox v-else-if="isBoolean" v-model="vModel" /> <LazyCellTextArea v-if="isTextArea(column)" v-model="vModel" />
<LazyCellAttachment v-else-if="isAttachment" v-model="vModel" :row-index="props.rowIndex" /> <LazyCellCheckbox v-else-if="isBoolean(column)" v-model="vModel" />
<LazyCellSingleSelect v-else-if="isSingleSelect" v-model="vModel" :row-index="props.rowIndex" /> <LazyCellAttachment v-else-if="isAttachment(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellMultiSelect v-else-if="isMultiSelect" v-model="vModel" :row-index="props.rowIndex" /> <LazyCellSingleSelect v-else-if="isSingleSelect(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellDatePicker v-else-if="isDate" v-model="vModel" :is-pk="isPrimaryKey" /> <LazyCellMultiSelect v-else-if="isMultiSelect(column)" v-model="vModel" :row-index="props.rowIndex" />
<LazyCellYearPicker v-else-if="isYear" v-model="vModel" :is-pk="isPrimaryKey" /> <LazyCellDatePicker v-else-if="isDate(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellDateTimePicker v-else-if="isDateTime" v-model="vModel" :is-pk="isPrimaryKey" /> <LazyCellYearPicker v-else-if="isYear(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellTimePicker v-else-if="isTime" v-model="vModel" :is-pk="isPrimaryKey" /> <LazyCellDateTimePicker v-else-if="isDateTime(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellRating v-else-if="isRating" v-model="vModel" /> <LazyCellTimePicker v-else-if="isTime(column, abstractType)" v-model="vModel" :is-pk="isPrimaryKey(column)" />
<LazyCellDuration v-else-if="isDuration" v-model="vModel" /> <LazyCellRating v-else-if="isRating(column)" v-model="vModel" />
<LazyCellEmail v-else-if="isEmail" v-model="vModel" /> <LazyCellDuration v-else-if="isDuration(column)" v-model="vModel" />
<LazyCellUrl v-else-if="isURL" v-model="vModel" /> <LazyCellEmail v-else-if="isEmail(column)" v-model="vModel" />
<LazyCellPhoneNumber v-else-if="isPhoneNumber" v-model="vModel" /> <LazyCellUrl v-else-if="isURL(column)" v-model="vModel" />
<LazyCellPercent v-else-if="isPercent" v-model="vModel" /> <LazyCellPhoneNumber v-else-if="isPhoneNumber(column)" v-model="vModel" />
<LazyCellCurrency v-else-if="isCurrency" v-model="vModel" @save="emit('save')" /> <LazyCellPercent v-else-if="isPercent(column)" v-model="vModel" />
<LazyCellDecimal v-else-if="isDecimal" v-model="vModel" /> <LazyCellCurrency v-else-if="isCurrency(column)" v-model="vModel" @save="emit('save')" />
<LazyCellInteger v-else-if="isInt" v-model="vModel" /> <LazyCellDecimal v-else-if="isDecimal(column)" v-model="vModel" />
<LazyCellFloat v-else-if="isFloat" v-model="vModel" /> <LazyCellInteger v-else-if="isInt(column, abstractType)" v-model="vModel" />
<LazyCellText v-else-if="isString" v-model="vModel" /> <LazyCellFloat v-else-if="isFloat(column, abstractType)" v-model="vModel" />
<LazyCellJson v-else-if="isJSON" v-model="vModel" /> <LazyCellText v-else-if="isString(column, abstractType)" v-model="vModel" />
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" /> <LazyCellText v-else v-model="vModel" />
<div v-if="(isLocked || (isPublic && readOnly && !isForm)) && !isAttachment" class="nc-locked-overlay" @click.stop.prevent /> <div
v-if="(isLocked || (isPublic && readOnly && !isForm)) && !isAttachment(column)"
class="nc-locked-overlay"
@click.stop.prevent
/>
</template>
</div> </div>
</template> </template>

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

@ -1,6 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { ActiveCellInj, CellValueInj, ColumnInj, IsFormInj, RowInj, inject, provide, ref, toRef, useVirtualCell } from '#imports' import {
ActiveCellInj,
CellValueInj,
ColumnInj,
IsFormInj,
RowInj,
inject,
isBt,
isCount,
isFormula,
isHm,
isLookup,
isMm,
isRollup,
provide,
toRef,
} from '#imports'
import type { Row } from '~/lib' import type { Row } from '~/lib'
import { NavigateDir } from '~/lib' import { NavigateDir } from '~/lib'
@ -24,8 +40,6 @@ provide(CellValueInj, toRef(props, 'modelValue'))
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualCell(column)
function onNavigate(dir: NavigateDir, e: KeyboardEvent) { function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
emit('navigate', dir) emit('navigate', dir)
@ -39,12 +53,12 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
@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)"
> >
<LazyVirtualCellHasMany v-if="isHm" /> <LazyVirtualCellHasMany v-if="isHm(column)" />
<LazyVirtualCellManyToMany v-else-if="isMm" /> <LazyVirtualCellManyToMany v-else-if="isMm(column)" />
<LazyVirtualCellBelongsTo v-else-if="isBt" /> <LazyVirtualCellBelongsTo v-else-if="isBt(column)" />
<LazyVirtualCellRollup v-else-if="isRollup" /> <LazyVirtualCellRollup v-else-if="isRollup(column)" />
<LazyVirtualCellFormula v-else-if="isFormula" /> <LazyVirtualCellFormula v-else-if="isFormula(column)" />
<LazyVirtualCellCount v-else-if="isCount" /> <LazyVirtualCellCount v-else-if="isCount(column)" />
<LazyVirtualCellLookup v-else-if="isLookup" /> <LazyVirtualCellLookup v-else-if="isLookup(column)" />
</div> </div>
</template> </template>

132
packages/nc-gui/components/smartsheet/header/CellIcon.ts

@ -0,0 +1,132 @@
import type { ColumnType } from 'nocodb-sdk'
import type { PropType } from '@vue/runtime-core'
import {
ColumnInj,
computed,
defineComponent,
h,
inject,
isAttachment,
isBoolean,
isCurrency,
isDate,
isDateTime,
isDecimal,
isDuration,
isEmail,
isFloat,
isInt,
isJSON,
isPercent,
isPhoneNumber,
isPrimary,
isRating,
isSet,
isSingleSelect,
isSpecificDBType,
isString,
isTextArea,
isTime,
isURL,
isYear,
toRef,
useProject,
} from '#imports'
import FilePhoneIcon from '~icons/mdi/file-phone'
import KeyIcon from '~icons/mdi/key-variant'
import JSONIcon from '~icons/mdi/code-json'
import ClockIcon from '~icons/mdi/clock-time-five'
import WebIcon from '~icons/mdi/web'
import TextAreaIcon from '~icons/mdi/card-text-outline'
import StringIcon from '~icons/mdi/alpha-a-box-outline'
import BooleanIcon from '~icons/mdi/check-box-outline'
import CalendarIcon from '~icons/mdi/calendar'
import SingleSelectIcon from '~icons/mdi/arrow-down-drop-circle'
import MultiSelectIcon from '~icons/mdi/format-list-bulleted-square'
import DatetimeIcon from '~icons/mdi/calendar-clock'
import RatingIcon from '~icons/mdi/star'
import GenericIcon from '~icons/mdi/square-rounded'
import NumericIcon from '~icons/mdi/numeric'
import AttachmentIcon from '~icons/mdi/image-multiple-outline'
import EmailIcon from '~icons/mdi/email'
import CurrencyIcon from '~icons/mdi/currency-usd-circle-outline'
import PercentIcon from '~icons/mdi/percent-outline'
import DecimalIcon from '~icons/mdi/decimal'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import DurationIcon from '~icons/mdi/timer-outline'
const renderIcon = (column: ColumnType, abstractType: any) => {
if (isPrimary(column)) {
return KeyIcon
} else if (isJSON(column)) {
return JSONIcon
} else if (isDate(column, abstractType)) {
return CalendarIcon
} else if (isDateTime(column, abstractType)) {
return DatetimeIcon
} else if (isSet(column)) {
return MultiSelectIcon
} else if (isSingleSelect(column)) {
return SingleSelectIcon
} else if (isBoolean(column)) {
return BooleanIcon
} else if (isTextArea(column)) {
return TextAreaIcon
} else if (isEmail(column)) {
return EmailIcon
} else if (isYear(column, abstractType)) {
return CalendarIcon
} else if (isTime(column, abstractType)) {
return ClockIcon
} else if (isRating(column)) {
return RatingIcon
} else if (isAttachment(column)) {
return AttachmentIcon
} else if (isDecimal(column)) {
return DecimalIcon
} else if (isPhoneNumber(column)) {
return FilePhoneIcon
} else if (isURL(column)) {
return WebIcon
} else if (isCurrency(column)) {
return CurrencyIcon
} else if (isDuration(column)) {
return DurationIcon
} else if (isPercent(column)) {
return PercentIcon
} else if (isInt(column, abstractType) || isFloat(column, abstractType)) {
return NumericIcon
} else if (isString(column, abstractType)) {
return StringIcon
} else if (isSpecificDBType(column)) {
return SpecificDBTypeIcon
} else {
return GenericIcon
}
}
export default defineComponent({
name: 'CellIcon',
props: {
columnMeta: {
type: Object as PropType<ColumnType>,
required: false,
},
},
setup(props) {
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta)
const { sqlUi } = useProject()
const abstractType = computed(() => column.value && sqlUi.value.getAbstractType(column.value))
return () => {
if (!column.value) return null
return h(renderIcon(column.value, abstractType.value), { class: 'text-grey mx-1 !text-xs' })
}
},
})

89
packages/nc-gui/components/smartsheet/header/CellIcon.vue

@ -1,89 +0,0 @@
<script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { ColumnInj, computed, inject, toRef, useColumn } from '#imports'
import FilePhoneIcon from '~icons/mdi/file-phone'
import KeyIcon from '~icons/mdi/key-variant'
import JSONIcon from '~icons/mdi/code-json'
import ClockIcon from '~icons/mdi/clock-time-five'
import WebIcon from '~icons/mdi/web'
import TextAreaIcon from '~icons/mdi/card-text-outline'
import StringIcon from '~icons/mdi/alpha-a-box-outline'
import BooleanIcon from '~icons/mdi/check-box-outline'
import CalendarIcon from '~icons/mdi/calendar'
import SingleSelectIcon from '~icons/mdi/arrow-down-drop-circle'
import MultiSelectIcon from '~icons/mdi/format-list-bulleted-square'
import DatetimeIcon from '~icons/mdi/calendar-clock'
import RatingIcon from '~icons/mdi/star'
import GenericIcon from '~icons/mdi/square-rounded'
import NumericIcon from '~icons/mdi/numeric'
import AttachmentIcon from '~icons/mdi/image-multiple-outline'
import EmailIcon from '~icons/mdi/email'
import CurrencyIcon from '~icons/mdi/currency-usd-circle-outline'
import PercentIcon from '~icons/mdi/percent-outline'
import DecimalIcon from '~icons/mdi/decimal'
import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import DurationIcon from '~icons/mdi/timer-outline'
const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta)
const additionalColMeta = useColumn(column as Ref<ColumnType>)
const icon = computed(() => {
if (column?.value?.pk) {
return KeyIcon
} else if (additionalColMeta.isJSON.value) {
return JSONIcon
} else if (additionalColMeta.isDate.value) {
return CalendarIcon
} else if (additionalColMeta.isDateTime.value) {
return DatetimeIcon
} else if (additionalColMeta.isSet.value) {
return MultiSelectIcon
} else if (additionalColMeta.isSingleSelect.value) {
return SingleSelectIcon
} else if (additionalColMeta.isBoolean.value) {
return BooleanIcon
} else if (additionalColMeta.isTextArea.value) {
return TextAreaIcon
} else if (additionalColMeta.isEmail.value) {
return EmailIcon
} else if (additionalColMeta.isYear.value) {
return CalendarIcon
} else if (additionalColMeta.isTime.value) {
return ClockIcon
} else if (additionalColMeta.isRating.value) {
return RatingIcon
} else if (additionalColMeta.isAttachment.value) {
return AttachmentIcon
} else if (additionalColMeta.isDecimal.value) {
return DecimalIcon
} else if (additionalColMeta.isPhoneNumber.value) {
return FilePhoneIcon
} else if (additionalColMeta.isURL.value) {
return WebIcon
} else if (additionalColMeta.isCurrency.value) {
return CurrencyIcon
} else if (additionalColMeta.isDuration.value) {
return DurationIcon
} else if (additionalColMeta.isPercent.value) {
return PercentIcon
} else if (additionalColMeta.isInt.value || additionalColMeta.isFloat.value) {
return NumericIcon
} else if (additionalColMeta.isString.value) {
return StringIcon
} else if (additionalColMeta.isSpecificDBType.value) {
return SpecificDBTypeIcon
} else {
return GenericIcon
}
})
</script>
<template>
<component :is="icon" class="text-grey mx-1 !text-xs" />
</template>

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

@ -7,6 +7,12 @@ import {
MetaInj, MetaInj,
computed, computed,
inject, inject,
isBt,
isFormula,
isHm,
isLookup,
isMm,
isRollup,
isVirtualColRequired, isVirtualColRequired,
provide, provide,
ref, ref,
@ -14,7 +20,6 @@ import {
useI18n, useI18n,
useMetas, useMetas,
useUIPermission, useUIPermission,
useVirtualCell,
} from '#imports' } from '#imports'
const props = defineProps<{ column: ColumnType; hideMenu?: boolean; required?: boolean | number }>() const props = defineProps<{ column: ColumnType; hideMenu?: boolean; required?: boolean | number }>()
@ -37,14 +42,12 @@ const meta = inject(MetaInj, ref())
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const { isLookup, isBt, isRollup, isMm, isHm, isFormula } = useVirtualCell(column)
const colOptions = $computed(() => column.value?.colOptions) const colOptions = $computed(() => column.value?.colOptions)
const tableTile = $computed(() => meta?.value?.title) const tableTile = $computed(() => meta?.value?.title)
const relationColumnOptions = $computed<LinkToAnotherRecordType | null>(() => { const relationColumnOptions = $computed<LinkToAnotherRecordType | null>(() => {
if (isMm.value || isHm.value || isBt.value) { if (isMm(column.value) || isHm(column.value) || isBt(column.value)) {
return column.value?.colOptions as LinkToAnotherRecordType return column.value?.colOptions as LinkToAnotherRecordType
} else if ((column?.value?.colOptions as LookupType | RollupType)?.fk_relation_column_id) { } else if ((column?.value?.colOptions as LookupType | RollupType)?.fk_relation_column_id) {
return meta?.value?.columns?.find( return meta?.value?.columns?.find(
@ -62,10 +65,10 @@ const relatedTableTitle = $computed(() => relatedTableMeta?.title)
const childColumn = $computed(() => { const childColumn = $computed(() => {
if (relatedTableMeta?.columns) { if (relatedTableMeta?.columns) {
if (isRollup.value) { if (isRollup(column.value)) {
return relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as RollupType).fk_rollup_column_id) return relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as RollupType).fk_rollup_column_id)
} }
if (isLookup.value) { if (isLookup(column.value)) {
return relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as LookupType).fk_lookup_column_id) return relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as LookupType).fk_lookup_column_id)
} }
} }
@ -76,22 +79,22 @@ const tooltipMsg = computed(() => {
if (!column.value) { if (!column.value) {
return '' return ''
} }
if (isHm.value) { if (isHm(column.value)) {
return `'${tableTile}' ${t('labels.hasMany')} '${relatedTableTitle}'` return `'${tableTile}' ${t('labels.hasMany')} '${relatedTableTitle}'`
} else if (isMm.value) { } else if (isMm(column.value)) {
return `'${tableTile}' & '${relatedTableTitle}' ${t('labels.manyToMany')}` return `'${tableTile}' & '${relatedTableTitle}' ${t('labels.manyToMany')}`
} else if (isBt.value) { } else if (isBt(column.value)) {
return `'${column?.value?.title}' ${t('labels.belongsTo')} '${relatedTableTitle}'` return `'${column?.value?.title}' ${t('labels.belongsTo')} '${relatedTableTitle}'`
} else if (isLookup.value) { } else if (isLookup(column.value)) {
return `'${childColumn.title}' from '${relatedTableTitle}' (${childColumn.uidt})` return `'${childColumn.title}' from '${relatedTableTitle}' (${childColumn.uidt})`
} else if (isFormula.value) { } else if (isFormula(column.value)) {
const formula = substituteColumnIdWithAliasInFormula( const formula = substituteColumnIdWithAliasInFormula(
(column.value?.colOptions as FormulaType)?.formula, (column.value?.colOptions as FormulaType)?.formula,
meta?.value?.columns as ColumnType[], meta?.value?.columns as ColumnType[],
(column.value?.colOptions as any)?.formula_raw, (column.value?.colOptions as any)?.formula_raw,
) )
return `Formula - ${formula}` return `Formula - ${formula}`
} else if (isRollup.value) { } else if (isRollup(column.value)) {
return `'${childColumn.title}' of '${relatedTableTitle}' (${childColumn.uidt})` return `'${childColumn.title}' of '${relatedTableTitle}' (${childColumn.uidt})`
} }
return '' return ''

75
packages/nc-gui/components/smartsheet/header/VirtualCellIcon.vue → packages/nc-gui/components/smartsheet/header/VirtualCellIcon.ts

@ -1,8 +1,8 @@
<script setup lang="ts"> import type { PropType } from '@vue/runtime-core'
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { ColumnInj, inject, ref, toRef } from '#imports' import { RelationTypes, UITypes } from 'nocodb-sdk'
import { ColumnInj, MetaInj, defineComponent, h, inject, isBt, isHm, isLookup, isMm, isRollup, ref, toRef } from '#imports'
import GenericIcon from '~icons/mdi/square-rounded' import GenericIcon from '~icons/mdi/square-rounded'
import HMIcon from '~icons/mdi/table-arrow-right' import HMIcon from '~icons/mdi/table-arrow-right'
import BTIcon from '~icons/mdi/table-arrow-left' import BTIcon from '~icons/mdi/table-arrow-left'
@ -13,30 +13,10 @@ import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings' import SpecificDBTypeIcon from '~icons/mdi/database-settings'
import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before' import TableColumnPlusBefore from '~icons/mdi/table-column-plus-before'
const props = defineProps<{ columnMeta?: ColumnType }>() const renderIcon = (column: ColumnType, relationColumn?: ColumnType) => {
switch (column.uidt) {
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, ref(columnMeta)) as Ref<ColumnType & { colOptions: LookupType }>
let relationColumn: ColumnType & { colOptions: LookupType }
if (column) {
const { isLookup, isBt, isRollup, isMm, isHm } = useVirtualCell(column as Ref<ColumnType>)
if (isLookup || isBt || isRollup || isMm || isHm) {
const meta = inject(MetaInj, ref())
relationColumn = meta.value?.columns?.find((c) => c.id === column.value?.colOptions?.fk_relation_column_id) as ColumnType & {
colOptions: LinkToAnotherRecordType
}
}
}
const icon = computed(() => {
switch (column?.value?.uidt) {
case UITypes.LinkToAnotherRecord: case UITypes.LinkToAnotherRecord:
switch ((column?.value?.colOptions as LinkToAnotherRecordType)?.type) { switch ((column.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY: case RelationTypes.MANY_TO_MANY:
return { icon: MMIcon, color: 'text-accent' } return { icon: MMIcon, color: 'text-accent' }
case RelationTypes.HAS_MANY: case RelationTypes.HAS_MANY:
@ -72,10 +52,43 @@ const icon = computed(() => {
case UITypes.Count: case UITypes.Count:
return { icon: CountIcon, color: 'text-grey' } return { icon: CountIcon, color: 'text-grey' }
} }
return { icon: GenericIcon, color: 'text-grey' } return { icon: GenericIcon, color: 'text-grey' }
}) }
</script>
export default defineComponent({
name: 'VirtualCellIcon',
props: {
columnMeta: {
type: Object as PropType<ColumnType>,
required: false,
},
},
setup(props) {
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta) as Ref<ColumnType & { colOptions: LookupType }>
let relationColumn: ColumnType & { colOptions: LookupType }
return () => {
if (!column.value) return null
if (column && column.value) {
if (isMm(column.value) || isHm(column.value) || isBt(column.value) || isLookup(column.value) || isRollup(column.value)) {
const meta = inject(MetaInj, ref())
<template> relationColumn = meta.value?.columns?.find(
<component :is="icon.icon" class="mx-1 !text-xs" :class="icon.color" /> (c) => c.id === column.value?.colOptions?.fk_relation_column_id,
</template> ) as ColumnType & {
colOptions: LinkToAnotherRecordType
}
}
}
const { icon: Icon, color } = renderIcon(column.value, relationColumn)
return h(Icon, { class: `${color} mx-1 !text-xs` })
}
},
})

4
packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue

@ -3,7 +3,6 @@ import {
Form, Form,
computed, computed,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
isEmail,
message, message,
onMounted, onMounted,
projectRoleTagColors, projectRoleTagColors,
@ -14,6 +13,7 @@ import {
useI18n, useI18n,
useNuxtApp, useNuxtApp,
useProject, useProject,
validateEmail,
} from '#imports' } from '#imports'
import type { User } from '~/lib' import type { User } from '~/lib'
import { ProjectRole } from '~/lib' import { ProjectRole } from '~/lib'
@ -57,7 +57,7 @@ const validators = computed(() => {
callback('Email is required') callback('Email is required')
return return
} }
const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !isEmail(e)) const invalidEmails = (value || '').split(/\s*,\s*/).filter((e: string) => !validateEmail(e))
if (invalidEmails.length > 0) { if (invalidEmails.length > 0) {
callback(`${invalidEmails.length > 1 ? ' Invalid emails:' : 'Invalid email:'} ${invalidEmails.join(', ')} `) callback(`${invalidEmails.length > 1 ? ' Invalid emails:' : 'Invalid email:'} ${invalidEmails.join(', ')} `)
} else { } else {

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

@ -1,7 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType, LookupType } from 'nocodb-sdk'
import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk' import { RelationTypes, UITypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { import {
CellUrlDisableOverlayInj, CellUrlDisableOverlayInj,
CellValueInj, CellValueInj,
@ -10,43 +9,66 @@ import {
ReadonlyInj, ReadonlyInj,
computed, computed,
inject, inject,
isAttachment,
provide, provide,
refAutoReset, refAutoReset,
useColumn, ref,
useMetas, useMetas,
watch,
} from '#imports' } from '#imports'
const { metas, getMeta } = useMetas() const { metas, getMeta } = useMetas()
provide(ReadonlyInj, ref(true)) provide(ReadonlyInj, ref(true))
const column = inject(ColumnInj)! as Ref<ColumnType & { colOptions: LookupType }> const column = inject(ColumnInj, ref())
const meta = inject(MetaInj, ref()) const meta = inject(MetaInj, ref())
const value = inject(CellValueInj) const cellValue = inject(CellValueInj, ref())
const arrValue = computed(() => (Array.isArray(value?.value) ? value?.value : [value?.value]) ?? []) const arrValue = computed(() => {
if (!cellValue.value) return []
const relationColumn = meta.value?.columns?.find((c) => c.id === column.value.colOptions?.fk_relation_column_id) as ColumnType & { if (Array.isArray(cellValue.value)) return cellValue.value
colOptions: LinkToAnotherRecordType
} return [cellValue.value]
})
const relationColumn = computed(
() =>
meta.value?.columns?.find((c) => c.id === (column.value?.colOptions as LookupType)?.fk_relation_column_id) as
| (ColumnType & {
colOptions: LinkToAnotherRecordType | undefined
})
| undefined,
)
watch(
relationColumn,
async (relationCol) => {
if (relationCol && relationCol.colOptions) await getMeta(relationCol.colOptions.fk_related_model_id!)
},
{ immediate: true },
)
await getMeta(relationColumn.colOptions.fk_related_model_id!) const lookupTableMeta = computed<Record<string, any> | undefined>(() => {
if (relationColumn.value && relationColumn.value?.colOptions)
return metas.value[relationColumn.value.colOptions.fk_related_model_id!]
const lookupTableMeta = computed(() => metas.value[relationColumn.colOptions.fk_related_model_id!]) return undefined
})
const lookupColumn = computed<any>( const lookupColumn = computed(
() => () =>
lookupTableMeta.value.columns?.find( lookupTableMeta.value?.columns?.find((c: any) => c.id === (column.value?.colOptions as LookupType)?.fk_lookup_column_id) as
(c: Record<string, any>) => c.id === column.value.colOptions?.fk_lookup_column_id, | ColumnType
) as ColumnType, | undefined,
) )
provide(MetaInj, lookupTableMeta) provide(MetaInj, lookupTableMeta)
provide(CellUrlDisableOverlayInj, ref(true))
const lookupColumnMetaProps = useColumn(lookupColumn) provide(CellUrlDisableOverlayInj, ref(true))
const timeout = 3000 // in ms const timeout = 3000 // in ms
@ -94,7 +116,7 @@ useSelectedCellKeyupListener(inject(ActiveCellInj, ref(false)), (e: KeyboardEven
:key="i" :key="i"
class="min-w-max" class="min-w-max"
:class="{ :class="{
'bg-gray-100 px-1 rounded-full flex-1': !lookupColumnMetaProps.isAttachment, 'bg-gray-100 px-1 rounded-full flex-1': !isAttachment(lookupColumn),
' border-gray-200 rounded border-1': ![UITypes.Attachment, UITypes.MultiSelect, UITypes.SingleSelect].includes( ' border-gray-200 rounded border-1': ![UITypes.Attachment, UITypes.MultiSelect, UITypes.SingleSelect].includes(
lookupColumn.uidt, lookupColumn.uidt,
), ),

98
packages/nc-gui/composables/useColumn.ts

@ -1,98 +0,0 @@
import type { ColumnType } from 'nocodb-sdk'
import { SqlUiFactory, UITypes, isVirtualCol } from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import { computed, useProject } from '#imports'
export function useColumn(column: Ref<ColumnType | undefined>) {
const { project } = useProject()
const uiDatatype: ComputedRef<UITypes> = computed(() => column.value?.uidt as UITypes)
const abstractType = computed(() => {
// kludge: CY test hack; column.value is being received NULL during attach cell delete operation
return (column.value && isVirtualCol(column.value)) || !column.value
? null
: SqlUiFactory.create(
project.value?.bases?.[0]?.type ? { client: project.value.bases[0].type } : { client: 'mysql2' },
).getAbstractType(column.value)
})
const dataTypeLow = computed(() => column.value?.dt?.toLowerCase())
const isBoolean = computed(() => abstractType.value === 'boolean')
const isString = computed(() => uiDatatype.value === UITypes.SingleLineText || abstractType.value === 'string')
const isTextArea = computed(() => uiDatatype.value === UITypes.LongText)
const isInt = computed(() => abstractType.value === 'integer')
const isFloat = computed(() => abstractType.value === 'float' || abstractType.value === UITypes.Number)
const isDate = computed(() => abstractType.value === 'date' || uiDatatype.value === UITypes.Date)
const isYear = computed(() => abstractType.value === 'year' || uiDatatype.value === UITypes.Year)
const isTime = computed(() => abstractType.value === 'time' || uiDatatype.value === UITypes.Time)
const isDateTime = computed(() => abstractType.value === 'datetime' || uiDatatype.value === UITypes.DateTime)
const isJSON = computed(() => uiDatatype.value === UITypes.JSON)
const isEnum = computed(() => uiDatatype.value === UITypes.SingleSelect)
const isSingleSelect = computed(() => uiDatatype.value === UITypes.SingleSelect)
const isSet = computed(() => uiDatatype.value === UITypes.MultiSelect)
const isMultiSelect = computed(() => uiDatatype.value === UITypes.MultiSelect)
const isURL = computed(() => uiDatatype.value === UITypes.URL)
const isEmail = computed(() => uiDatatype.value === UITypes.Email)
const isAttachment = computed(() => uiDatatype.value === UITypes.Attachment)
const isRating = computed(() => uiDatatype.value === UITypes.Rating)
const isCurrency = computed(() => uiDatatype.value === UITypes.Currency)
const isPhoneNumber = computed(() => uiDatatype.value === UITypes.PhoneNumber)
const isDecimal = computed(() => uiDatatype.value === UITypes.Decimal)
const isDuration = computed(() => uiDatatype.value === UITypes.Duration)
const isPercent = computed(() => uiDatatype.value === UITypes.Percent)
const isSpecificDBType = computed(() => uiDatatype.value === UITypes.SpecificDBType)
const isAutoSaved = computed(() =>
[
UITypes.SingleLineText,
UITypes.LongText,
UITypes.PhoneNumber,
UITypes.Email,
UITypes.URL,
UITypes.Number,
UITypes.Decimal,
UITypes.Percent,
UITypes.Count,
UITypes.AutoNumber,
UITypes.SpecificDBType,
UITypes.Geometry,
UITypes.Duration,
].includes(uiDatatype.value),
)
const isManualSaved = computed(() => [UITypes.Currency].includes(uiDatatype.value))
const isPrimary = computed(() => column.value?.pv)
const isPrimaryKey = computed(() => !!column.value?.pk)
return {
abstractType,
dataTypeLow,
isPrimary,
isBoolean,
isString,
isTextArea,
isInt,
isFloat,
isDate,
isYear,
isTime,
isDateTime,
isJSON,
isEnum,
isSet,
isURL,
isEmail,
isAttachment,
isRating,
isCurrency,
isDecimal,
isDuration,
isAutoSaved,
isManualSaved,
isSingleSelect,
isMultiSelect,
isPercent,
isPhoneNumber,
isSpecificDBType,
isPrimaryKey,
}
}

5
packages/nc-gui/composables/useProject.ts

@ -2,6 +2,7 @@ import type { OracleUi, ProjectType, TableType } from 'nocodb-sdk'
import { SqlUiFactory } from 'nocodb-sdk' import { SqlUiFactory } from 'nocodb-sdk'
import { isString } from '@vueuse/core' import { isString } from '@vueuse/core'
import { import {
ClientType,
computed, computed,
createEventHook, createEventHook,
ref, ref,
@ -54,13 +55,13 @@ const [setup, use] = useInjectionState(() => {
} }
}) })
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '') const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || ClientType.MYSQL)
const sqlUi = computed( const sqlUi = computed(
() => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>, () => SqlUiFactory.create({ client: projectBaseType }) as Exclude<ReturnType<typeof SqlUiFactory['create']>, typeof OracleUi>,
) )
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType)) const isMysql = computed(() => ['mysql', ClientType.MYSQL].includes(projectBaseType))
const isMssql = computed(() => projectBaseType === 'mssql') const isMssql = computed(() => projectBaseType === 'mssql')
const isPg = computed(() => projectBaseType === 'pg') const isPg = computed(() => projectBaseType === 'pg')
const isSharedBase = computed(() => projectType === 'base') const isSharedBase = computed(() => projectType === 'base')

25
packages/nc-gui/composables/useSmartsheetRowStore.ts

@ -8,6 +8,9 @@ import {
deepCompare, deepCompare,
extractPkFromRow, extractPkFromRow,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
isBt,
isHm,
isMm,
message, message,
ref, ref,
unref, unref,
@ -16,7 +19,6 @@ import {
useMetas, useMetas,
useNuxtApp, useNuxtApp,
useProject, useProject,
useVirtualCell,
} from '#imports' } from '#imports'
import type { Row } from '~/lib' import type { Row } from '~/lib'
@ -36,12 +38,11 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({}) const state = ref<Record<string, Record<string, any> | Record<string, any>[] | null>>({})
// getters // getters
const isNew = computed(() => unref(row).rowMeta?.new ?? false) const isNew = computed(() => unref(row).rowMeta.new ?? false)
// actions // actions
const addLTARRef = async (value: Record<string, any>, column: ColumnType) => { const addLTARRef = async (value: Record<string, any>, column: ColumnType) => {
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) if (isHm(column) || isMm(column)) {
if (isHm || isMm) {
if (!state.value[column.title!]) state.value[column.title!] = [] if (!state.value[column.title!]) state.value[column.title!] = []
if (state.value[column.title!]!.find((ln: Record<string, any>) => deepCompare(ln, value))) { if (state.value[column.title!]!.find((ln: Record<string, any>) => deepCompare(ln, value))) {
@ -50,17 +51,16 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
} }
state.value[column.title!]!.push(value) state.value[column.title!]!.push(value)
} else if (isBt) { } else if (isBt(column)) {
state.value[column.title!] = value state.value[column.title!] = value
} }
} }
// actions // actions
const removeLTARRef = async (value: Record<string, any>, column: ColumnType) => { const removeLTARRef = async (value: Record<string, any>, column: ColumnType) => {
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) if (isHm(column) || isMm(column)) {
if (isHm || isMm) {
state.value[column.title!]?.splice(state.value[column.title!]?.indexOf(value), 1) state.value[column.title!]?.splice(state.value[column.title!]?.indexOf(value), 1)
} else if (isBt) { } else if (isBt(column)) {
state.value[column.title!] = null state.value[column.title!] = null
} }
} }
@ -92,13 +92,14 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
const id = extractPkFromRow(row, metaValue?.columns as ColumnType[]) const id = extractPkFromRow(row, metaValue?.columns as ColumnType[])
for (const column of metaValue?.columns ?? []) { for (const column of metaValue?.columns ?? []) {
if (column.uidt !== UITypes.LinkToAnotherRecord) continue if (column.uidt !== UITypes.LinkToAnotherRecord) continue
const colOptions = column?.colOptions as LinkToAnotherRecordType
const { isHm, isMm, isBt } = $(useVirtualCell(ref(column))) const colOptions = column.colOptions as LinkToAnotherRecordType
const relatedTableMeta = metas.value?.[colOptions?.fk_related_model_id as string] const relatedTableMeta = metas.value?.[colOptions?.fk_related_model_id as string]
if (isHm || isMm) { if (isHm(column) || isMm(column)) {
const relatedRows = (state.value?.[column.title!] ?? []) as Record<string, any>[] const relatedRows = (state.value?.[column.title!] ?? []) as Record<string, any>[]
for (const relatedRow of relatedRows) { for (const relatedRow of relatedRows) {
await linkRecord( await linkRecord(
id, id,
@ -108,7 +109,7 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
{ metaValue }, { metaValue },
) )
} }
} else if (isBt && state?.value?.[column.title!]) { } else if (isBt(column) && state.value?.[column.title!]) {
await linkRecord( await linkRecord(
id, id,
extractPkFromRow(state.value?.[column.title!] as Record<string, any>, relatedTableMeta.columns as ColumnType[]), extractPkFromRow(state.value?.[column.title!] as Record<string, any>, relatedTableMeta.columns as ColumnType[]),

36
packages/nc-gui/composables/useVirtualCell.ts

@ -1,36 +0,0 @@
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { computed } from '#imports'
export function useVirtualCell(column: Ref<ColumnType | undefined>) {
const isHm = computed(
() =>
column.value?.uidt === UITypes.LinkToAnotherRecord &&
(<LinkToAnotherRecordType>column.value?.colOptions).type === RelationTypes.HAS_MANY,
)
const isMm = computed(
() =>
column.value?.uidt === UITypes.LinkToAnotherRecord &&
(<LinkToAnotherRecordType>column.value?.colOptions).type === RelationTypes.MANY_TO_MANY,
)
const isBt = computed(
() =>
column.value?.uidt === UITypes.LinkToAnotherRecord &&
(<LinkToAnotherRecordType>column.value?.colOptions).type === RelationTypes.BELONGS_TO,
)
const isLookup = computed(() => column.value?.uidt === UITypes.Lookup)
const isRollup = computed(() => column.value?.uidt === UITypes.Rollup)
const isFormula = computed(() => column.value?.uidt === UITypes.Formula)
const isCount = computed(() => column.value?.uidt === UITypes.Count)
return {
isHm,
isMm,
isBt,
isLookup,
isRollup,
isFormula,
isCount,
}
}

4
packages/nc-gui/pages/forgot-password.vue

@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { definePageMeta, isEmail, reactive, ref, useApi, useI18n } from '#imports' import { definePageMeta, reactive, ref, useApi, useI18n, validateEmail } from '#imports'
definePageMeta({ definePageMeta({
requiresAuth: false, requiresAuth: false,
@ -25,7 +25,7 @@ const formRules = {
{ {
validator: (_: unknown, v: string) => { validator: (_: unknown, v: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (isEmail(v)) return resolve(true) if (validateEmail(v)) return resolve(true)
reject(new Error(t('msg.error.signUpRules.emailInvalid'))) reject(new Error(t('msg.error.signUpRules.emailInvalid')))
}) })
}, },

4
packages/nc-gui/pages/signin.vue

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import type { RuleObject } from 'ant-design-vue/es/form' import type { RuleObject } from 'ant-design-vue/es/form'
import { definePageMeta, isEmail, navigateTo, reactive, ref, useApi, useGlobal, useI18n, useSidebar } from '#imports' import { definePageMeta, navigateTo, reactive, ref, useApi, useGlobal, useI18n, useSidebar, validateEmail } from '#imports'
definePageMeta({ definePageMeta({
requiresAuth: false, requiresAuth: false,
@ -30,7 +30,7 @@ const formRules: Record<string, RuleObject[]> = {
{ {
validator: (_: unknown, v: string) => { validator: (_: unknown, v: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (isEmail(v)) return resolve() if (validateEmail(v)) return resolve()
reject(new Error(t('msg.error.signUpRules.emailInvalid'))) reject(new Error(t('msg.error.signUpRules.emailInvalid')))
}) })

15
packages/nc-gui/pages/signup/[[token]].vue

@ -1,6 +1,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { validatePassword } from 'nocodb-sdk' import { validatePassword } from 'nocodb-sdk'
import { definePageMeta, isEmail, navigateTo, reactive, ref, useApi, useGlobal, useI18n, useNuxtApp, useRoute } from '#imports' import {
definePageMeta,
navigateTo,
reactive,
ref,
useApi,
useGlobal,
useI18n,
useNuxtApp,
useRoute,
validateEmail,
} from '#imports'
definePageMeta({ definePageMeta({
requiresAuth: false, requiresAuth: false,
@ -33,7 +44,7 @@ const formRules = {
{ {
validator: (_: unknown, v: string) => { validator: (_: unknown, v: string) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!v?.length || isEmail(v)) return resolve(true) if (!v?.length || validateEmail(v)) return resolve(true)
reject(new Error(t('msg.error.signUpRules.emailInvalid'))) reject(new Error(t('msg.error.signUpRules.emailInvalid')))
}) })
}, },

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

@ -0,0 +1,53 @@
import type { ColumnType } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
export const dataTypeLow = (column: ColumnType) => column.dt?.toLowerCase()
export const isBoolean = (abstractType: any) => abstractType === 'boolean'
export const isString = (column: ColumnType, abstractType: any) =>
column.uidt === UITypes.SingleLineText || abstractType === 'string'
export const isTextArea = (column: ColumnType) => column.uidt === UITypes.LongText
export const isInt = (column: ColumnType, abstractType: any) => abstractType === 'integer'
export const isFloat = (column: ColumnType, abstractType: any) => abstractType === 'float' || abstractType === UITypes.Number
export const isDate = (column: ColumnType, abstractType: any) => abstractType === 'date' || column.uidt === UITypes.Date
export const isYear = (column: ColumnType, abstractType: any) => abstractType === 'year' || column.uidt === UITypes.Year
export const isTime = (column: ColumnType, abstractType: any) => abstractType === 'time' || column.uidt === UITypes.Time
export const isDateTime = (column: ColumnType, abstractType: any) =>
abstractType === 'datetime' || column.uidt === UITypes.DateTime
export const isJSON = (column: ColumnType) => column.uidt === UITypes.JSON
export const isEnum = (column: ColumnType) => column.uidt === UITypes.SingleSelect
export const isSingleSelect = (column: ColumnType) => column.uidt === UITypes.SingleSelect
export const isSet = (column: ColumnType) => column.uidt === UITypes.MultiSelect
export const isMultiSelect = (column: ColumnType) => column.uidt === UITypes.MultiSelect
export const isURL = (column: ColumnType) => column.uidt === UITypes.URL
export const isEmail = (column: ColumnType) => column.uidt === UITypes.Email
export const isAttachment = (column: ColumnType) => column.uidt === UITypes.Attachment
export const isRating = (column: ColumnType) => column.uidt === UITypes.Rating
export const isCurrency = (column: ColumnType) => column.uidt === UITypes.Currency
export const isPhoneNumber = (column: ColumnType) => column.uidt === UITypes.PhoneNumber
export const isDecimal = (column: ColumnType) => column.uidt === UITypes.Decimal
export const isDuration = (column: ColumnType) => column.uidt === UITypes.Duration
export const isPercent = (column: ColumnType) => column.uidt === UITypes.Percent
export const isSpecificDBType = (column: ColumnType) => column.uidt === UITypes.SpecificDBType
export const isAutoSaved = (column: ColumnType) =>
[
UITypes.SingleLineText,
UITypes.LongText,
UITypes.PhoneNumber,
UITypes.Email,
UITypes.URL,
UITypes.Number,
UITypes.Decimal,
UITypes.Percent,
UITypes.Count,
UITypes.AutoNumber,
UITypes.SpecificDBType,
UITypes.Geometry,
UITypes.Duration,
].includes(column.uidt as UITypes)
export const isManualSaved = (column: ColumnType) =>
[UITypes.Currency, UITypes.Year, UITypes.Time].includes(column.uidt as UITypes)
export const isPrimary = (column: ColumnType) => !!column.pv
export const isPrimaryKey = (column: ColumnType) => !!column.pk

4
packages/nc-gui/utils/parsers/parserHelpers.ts

@ -1,6 +1,6 @@
import { UITypes } from 'nocodb-sdk' import { UITypes } from 'nocodb-sdk'
import { isValidURL } from '~/utils/urlUtils' import { isValidURL } from '~/utils/urlUtils'
import { isEmail } from '~/utils/validation' import { validateEmail } from '~/utils/validation'
const booleanOptions = [ const booleanOptions = [
{ checked: true, unchecked: false }, { checked: true, unchecked: false },
@ -109,7 +109,7 @@ export const isDecimalType = (colData: []) =>
export const isEmailType = (colData: []) => export const isEmailType = (colData: []) =>
colData.some((v: any) => { colData.some((v: any) => {
return v && isEmail(v) return v && validateEmail(v)
}) })
export const isUrlType = (colData: []) => export const isUrlType = (colData: []) =>

2
packages/nc-gui/utils/validation.ts

@ -1,6 +1,6 @@
import { getI18n } from '~/plugins/a.i18n' import { getI18n } from '~/plugins/a.i18n'
export const isEmail = (v: string) => export const validateEmail = (v: string) =>
/^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(v) /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i.test(v)
export const validateTableName = { export const validateTableName = {

19
packages/nc-gui/utils/virtualCell.ts

@ -0,0 +1,19 @@
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
export const isLTAR = (uidt: string, colOptions: unknown): colOptions is LinkToAnotherRecordType =>
uidt === UITypes.LinkToAnotherRecord
export const isHm = (column: ColumnType) =>
isLTAR(column.uidt, column.colOptions) && column.colOptions.type === RelationTypes.HAS_MANY
export const isMm = (column: ColumnType) =>
isLTAR(column.uidt, column.colOptions) && column.colOptions.type === RelationTypes.MANY_TO_MANY
export const isBt = (column: ColumnType) =>
isLTAR(column.uidt, column.colOptions) && column.colOptions.type === RelationTypes.BELONGS_TO
export const isLookup = (column: ColumnType) => column.uidt === UITypes.Lookup
export const isRollup = (column: ColumnType) => column.uidt === UITypes.Rollup
export const isFormula = (column: ColumnType) => column.uidt === UITypes.Formula
export const isCount = (column: ColumnType) => column.uidt === UITypes.Count
Loading…
Cancel
Save