Browse Source

Merge pull request #3005 from nocodb/fix/guiv2-common-issues

fix(gui-v2): Bug fixes in grid
pull/3037/head
Pranav C 2 years ago committed by GitHub
parent
commit
de33055f19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      packages/nc-gui-v2/app.vue
  2. 1
      packages/nc-gui-v2/components.d.ts
  3. 2
      packages/nc-gui-v2/components/cell/Checkbox.vue
  4. 14
      packages/nc-gui-v2/components/cell/Currency.vue
  5. 9
      packages/nc-gui-v2/components/cell/DatePicker.vue
  6. 8
      packages/nc-gui-v2/components/cell/DateTimePicker.vue
  7. 12
      packages/nc-gui-v2/components/cell/Decimal.vue
  8. 4
      packages/nc-gui-v2/components/cell/Duration.vue
  9. 2
      packages/nc-gui-v2/components/cell/Email.vue
  10. 3
      packages/nc-gui-v2/components/cell/Float.vue
  11. 1
      packages/nc-gui-v2/components/cell/Integer.vue
  12. 2
      packages/nc-gui-v2/components/cell/JsonEditableCell.vue
  13. 4
      packages/nc-gui-v2/components/cell/MultiSelect.vue
  14. 6
      packages/nc-gui-v2/components/cell/Percent.vue
  15. 4
      packages/nc-gui-v2/components/cell/Rating.vue
  16. 4
      packages/nc-gui-v2/components/cell/SingleSelect.vue
  17. 8
      packages/nc-gui-v2/components/cell/TimePicker.vue
  18. 6
      packages/nc-gui-v2/components/cell/Url.vue
  19. 8
      packages/nc-gui-v2/components/cell/YearPicker.vue
  20. 2
      packages/nc-gui-v2/components/cell/attachment/utils.ts
  21. 9
      packages/nc-gui-v2/components/smartsheet-column/AdvancedOptions.vue
  22. 45
      packages/nc-gui-v2/components/smartsheet-column/EditOrAdd.vue
  23. 6
      packages/nc-gui-v2/components/smartsheet-header/Cell.vue
  24. 9
      packages/nc-gui-v2/components/smartsheet-header/CellIcon.vue
  25. 6
      packages/nc-gui-v2/components/smartsheet-header/Menu.vue
  26. 204
      packages/nc-gui-v2/components/smartsheet-header/VirtualCell.vue
  27. 10
      packages/nc-gui-v2/components/smartsheet-header/VirtualCellIcon.vue
  28. 2
      packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue
  29. 10
      packages/nc-gui-v2/components/smartsheet/Cell.vue
  30. 97
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  31. 8
      packages/nc-gui-v2/components/smartsheet/Toolbar.vue
  32. 4
      packages/nc-gui-v2/components/smartsheet/VirtualCell.vue
  33. 8
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  34. 3
      packages/nc-gui-v2/components/virtual-cell/BelongsTo.vue
  35. 3
      packages/nc-gui-v2/components/virtual-cell/HasMany.vue
  36. 3
      packages/nc-gui-v2/components/virtual-cell/ManyToMany.vue
  37. 11
      packages/nc-gui-v2/composables/useColumn.ts
  38. 40
      packages/nc-gui-v2/composables/useColumnCreateStore.ts
  39. 18
      packages/nc-gui-v2/composables/useLTARStore.ts
  40. 15
      packages/nc-gui-v2/composables/useSmartsheetStore.ts
  41. 15
      packages/nc-gui-v2/composables/useTabs.ts
  42. 6
      packages/nc-gui-v2/composables/useViewColumns.ts
  43. 2
      packages/nc-gui-v2/composables/useViewData.ts
  44. 19
      packages/nc-gui-v2/composables/useViewFilters.ts
  45. 22
      packages/nc-gui-v2/composables/useVirtualCell.ts
  46. 2
      packages/nc-gui-v2/context/index.ts
  47. 3
      packages/nc-gui-v2/pages/nc/[projectId]/index.vue
  48. 4
      packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue
  49. 5
      scripts/sdk/swagger.json

6
packages/nc-gui-v2/app.vue

@ -30,20 +30,20 @@ const toggleSidebar = () => {
<div class="flex-1" /> <div class="flex-1" />
<div class="ml-4 flex justify-center flex-1"> <div class="ml-4 flex justify-center shrink">
<div class="flex items-center gap-2 cursor-pointer nc-noco-brand-icon" @click="navigateTo('/')"> <div class="flex items-center gap-2 cursor-pointer nc-noco-brand-icon" @click="navigateTo('/')">
<img width="35" src="~/assets/img/icons/512x512-trans.png" /> <img width="35" src="~/assets/img/icons/512x512-trans.png" />
<span class="prose-xl">NocoDB</span> <span class="prose-xl">NocoDB</span>
</div> </div>
</div>
<div class="flex-1 text-left">
<div v-show="state.isLoading.value" class="flex items-center gap-2 ml-3"> <div v-show="state.isLoading.value" class="flex items-center gap-2 ml-3">
{{ $t('general.loading') }} {{ $t('general.loading') }}
<mdi-reload :class="{ 'animate-infinite animate-spin': state.isLoading.value }" /> <mdi-reload :class="{ 'animate-infinite animate-spin': state.isLoading.value }" />
</div> </div>
</div> </div>
<div class="flex-1" />
<div class="flex justify-end gap-4"> <div class="flex justify-end gap-4">
<general-language class="mr-3" /> <general-language class="mr-3" />

1
packages/nc-gui-v2/components.d.ts vendored

@ -66,6 +66,7 @@ declare module '@vue/runtime-core' {
MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default'] MdiDotsVertical: typeof import('~icons/mdi/dots-vertical')['default']
MdiLogout: typeof import('~icons/mdi/logout')['default'] MdiLogout: typeof import('~icons/mdi/logout')['default']
MdiReload: typeof import('~icons/mdi/reload')['default'] MdiReload: typeof import('~icons/mdi/reload')['default']
MdiTableArrowRight: typeof import('~icons/mdi/table-arrow-right')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
} }

2
packages/nc-gui-v2/components/cell/Checkbox.vue

@ -26,7 +26,7 @@ const checkboxMeta = $computed(() => {
unchecked: 'mdi-checkbox-blank-circle-outline', unchecked: 'mdi-checkbox-blank-circle-outline',
}, },
color: 'primary', color: 'primary',
...(column?.meta || {}), ...(column?.value?.meta || {}),
} }
}) })
</script> </script>

14
packages/nc-gui-v2/components/cell/Currency.vue

@ -3,7 +3,7 @@ import { computed, inject, ref, useVModel } from '#imports'
import { ColumnInj, EditModeInj } from '~/context' import { ColumnInj, EditModeInj } from '~/context'
interface Props { interface Props {
modelValue: number modelValue: number | null
} }
const props = defineProps<Props>() const props = defineProps<Props>()
@ -22,7 +22,7 @@ const currencyMeta = computed(() => {
return { return {
currency_locale: 'en-US', currency_locale: 'en-US',
currency_code: 'USD', currency_code: 'USD',
...(column && column.meta ? column.meta : {}), ...(column?.value?.meta ? column?.value?.meta : {}),
} }
}) })
const currency = computed(() => { const currency = computed(() => {
@ -37,10 +37,18 @@ const currency = computed(() => {
return vModel.value return vModel.value
} }
}) })
const focus = (el: HTMLInputElement) => el?.focus()
</script> </script>
<template> <template>
<input v-if="editEnabled" ref="root" v-model="vModel" /> <input
v-if="editEnabled"
:ref="focus"
v-model="vModel"
class="w-full h-full border-none outline-none"
@blur="editEnabled = false"
/>
<span v-else-if="vModel">{{ currency }}</span> <span v-else-if="vModel">{{ currency }}</span>
<span v-else /> <span v-else />
</template> </template>

9
packages/nc-gui-v2/components/cell/DatePicker.vue

@ -2,19 +2,18 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ColumnInj, ReadonlyInj } from '~/context' import { ColumnInj, ReadonlyInj } from '~/context'
interface Props {
modelValue: string | null
}
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
interface Props {
modelValue: string
}
const columnMeta = inject(ColumnInj, null) const columnMeta = inject(ColumnInj, null)
const readOnlyMode = inject(ReadonlyInj, false) const readOnlyMode = inject(ReadonlyInj, false)
let isDateInvalid = $ref(false) let isDateInvalid = $ref(false)
const dateFormat = columnMeta?.meta?.date_format ?? 'YYYY-MM-DD' const dateFormat = columnMeta?.value?.meta?.date_format ?? 'YYYY-MM-DD'
const localState = $computed({ const localState = $computed({
get() { get() {

8
packages/nc-gui-v2/components/cell/DateTimePicker.vue

@ -2,14 +2,14 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ReadonlyInj } from '~/context' import { ReadonlyInj } from '~/context'
interface Props {
modelValue: string | null
}
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
interface Props {
modelValue: string
}
const { isMysql } = useProject() const { isMysql } = useProject()
const readOnlyMode = inject(ReadonlyInj, false) const readOnlyMode = inject(ReadonlyInj, false)

12
packages/nc-gui-v2/components/cell/Decimal.vue

@ -1,8 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed, inject, onMounted, ref } from '#imports' import { computed, inject, onMounted, ref } from '#imports'
import { EditModeInj } from '~/context'
interface Props { interface Props {
modelValue: number | null modelValue: number | null | string
} }
interface Emits { interface Emits {
@ -13,25 +14,24 @@ const props = defineProps<Props>()
const emits = defineEmits<Emits>() const emits = defineEmits<Emits>()
const editEnabled = inject<boolean>('editEnabled') const editEnabled = inject<boolean>(EditModeInj)
const root = ref<HTMLInputElement>() const root = ref<HTMLInputElement>()
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
onMounted(() => { const focus = (el: HTMLInputElement) => el?.focus()
root.value?.focus()
})
</script> </script>
<template> <template>
<input <input
v-if="editEnabled" v-if="editEnabled"
ref="root" :ref="focus"
v-model="vModel" v-model="vModel"
class="outline-none pa-0 border-none w-full h-full prose-sm" class="outline-none pa-0 border-none w-full h-full prose-sm"
type="number" type="number"
step="0.1" step="0.1"
@blur="editEnabled = false"
/> />
<span v-else class="prose-sm">{{ vModel }}</span> <span v-else class="prose-sm">{{ vModel }}</span>
</template> </template>

4
packages/nc-gui-v2/components/cell/Duration.vue

@ -4,7 +4,7 @@ import { ColumnInj } from '~/context'
import { convertDurationToSeconds, convertMS2Duration, durationOptions } from '~/utils' import { convertDurationToSeconds, convertMS2Duration, durationOptions } from '~/utils'
interface Props { interface Props {
modelValue: number | string modelValue: number | string | null
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
@ -16,7 +16,7 @@ const column = inject(ColumnInj)
const showWarningMessage = ref(false) const showWarningMessage = ref(false)
const durationInMS = ref(0) const durationInMS = ref(0)
const isEdited = ref(false) const isEdited = ref(false)
const durationType = ref(column?.meta?.duration || 0) const durationType = ref(column?.value?.meta?.duration || 0)
const durationPlaceholder = computed(() => durationOptions[durationType.value].title) const durationPlaceholder = computed(() => durationOptions[durationType.value].title)
const localState = computed({ const localState = computed({

2
packages/nc-gui-v2/components/cell/Email.vue

@ -25,7 +25,7 @@ const focus = (el: HTMLInputElement) => el?.focus()
</script> </script>
<template> <template>
<input v-if="editEnabled" ref="root" v-model="vModel" class="outline-none prose-sm" /> <input v-if="editEnabled" ref="root" v-model="vModel" class="outline-none prose-sm" @blur="editEnabled = false" />
<a v-else-if="validEmail" class="prose-sm underline hover:opacity-75" :href="`mailto:${vModel}`" target="_blank"> <a v-else-if="validEmail" class="prose-sm underline hover:opacity-75" :href="`mailto:${vModel}`" target="_blank">
{{ vModel }} {{ vModel }}
</a> </a>

3
packages/nc-gui-v2/components/cell/Float.vue

@ -3,7 +3,7 @@ import { inject, ref, useVModel } from '#imports'
import { EditModeInj } from '~/context' import { EditModeInj } from '~/context'
interface Props { interface Props {
modelValue: number modelValue: number | null
} }
interface Emits { interface Emits {
@ -29,6 +29,7 @@ const focus = (el: HTMLInputElement) => el?.focus()
class="outline-none pa-0 border-none w-full h-full prose-sm" class="outline-none pa-0 border-none w-full h-full prose-sm"
type="number" type="number"
step="0.1" step="0.1"
@blur="editEnabled = false"
/> />
<span v-else class="prose-sm">{{ vModel }}</span> <span v-else class="prose-sm">{{ vModel }}</span>
</template> </template>

1
packages/nc-gui-v2/components/cell/Integer.vue

@ -32,6 +32,7 @@ function onKeyDown(evt: KeyboardEvent) {
v-model="vModel" v-model="vModel"
class="outline-none pa-0 border-none w-full h-full prose-sm" class="outline-none pa-0 border-none w-full h-full prose-sm"
type="number" type="number"
@blur="editEnabled = false"
@keydown="onKeyDown" @keydown="onKeyDown"
/> />
<span v-else class="prose-sm">{{ vModel }}</span> <span v-else class="prose-sm">{{ vModel }}</span>

2
packages/nc-gui-v2/components/cell/JsonEditableCell.vue

@ -4,7 +4,7 @@ import { computed, inject } from '#imports'
import { EditModeInj } from '~/context' import { EditModeInj } from '~/context'
interface Props { interface Props {
modelValue: string | Record<string, any> modelValue: string | Record<string, any> | null
isForm: boolean isForm: boolean
} }

4
packages/nc-gui-v2/components/cell/MultiSelect.vue

@ -3,7 +3,7 @@ import { computed, inject } from '#imports'
import { ColumnInj, EditModeInj } from '~/context' import { ColumnInj, EditModeInj } from '~/context'
interface Props { interface Props {
modelValue: string modelValue: string | null
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
@ -14,7 +14,7 @@ const column = inject(ColumnInj)
const isForm = inject<boolean>('isForm', false) const isForm = inject<boolean>('isForm', false)
const editEnabled = inject(EditModeInj, ref(false)) const editEnabled = inject(EditModeInj, ref(false))
const options = computed(() => column?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || []) const options = computed(() => column?.value?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || [])
const localState = computed({ const localState = computed({
get() { get() {

6
packages/nc-gui-v2/components/cell/Percent.vue

@ -4,7 +4,7 @@ import { ColumnInj } from '~/context'
import { getPercentStep, isValidPercent, renderPercent } from '@/utils/percentUtils' import { getPercentStep, isValidPercent, renderPercent } from '@/utils/percentUtils'
interface Props { interface Props {
modelValue: number | string modelValue: number | string | null
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
@ -17,7 +17,7 @@ const percent = ref()
const isEdited = ref(false) const isEdited = ref(false)
const percentType = computed(() => column?.meta?.precision || 0) const percentType = computed(() => column?.value?.meta?.precision || 0)
const percentStep = computed(() => getPercentStep(percentType.value)) const percentStep = computed(() => getPercentStep(percentType.value))
@ -27,7 +27,7 @@ const localState = computed({
}, },
set: (val) => { set: (val) => {
if (val === null) val = 0 if (val === null) val = 0
if (isValidPercent(val, column?.meta?.negative)) { if (isValidPercent(val, column?.value?.meta?.negative)) {
percent.value = val / 100 percent.value = val / 100
} }
}, },

4
packages/nc-gui-v2/components/cell/Rating.vue

@ -8,7 +8,7 @@ import MdiThumbUpIcon from '~icons/mdi/thumb-up'
import MdiFlagIcon from '~icons/mdi/flag' import MdiFlagIcon from '~icons/mdi/flag'
interface Props { interface Props {
modelValue?: number modelValue?: number | null
readOnly?: boolean readOnly?: boolean
} }
@ -26,7 +26,7 @@ const ratingMeta = computed(() => {
}, },
color: '#fcb401', color: '#fcb401',
max: 5, max: 5,
...(column?.meta || {}), ...(column?.value?.meta || {}),
} }
}) })

4
packages/nc-gui-v2/components/cell/SingleSelect.vue

@ -3,7 +3,7 @@ import { computed, inject } from '#imports'
import { ColumnInj, EditModeInj } from '~/context' import { ColumnInj, EditModeInj } from '~/context'
interface Props { interface Props {
modelValue: string modelValue: string | null
} }
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
@ -19,7 +19,7 @@ const vModel = computed({
set: (val) => emit('update:modelValue', val), set: (val) => emit('update:modelValue', val),
}) })
const options = computed(() => column?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || []) const options = computed(() => column?.value?.dtxp?.split(',').map((v) => v.replace(/\\'/g, "'").replace(/^'|'$/g, '')) || [])
</script> </script>
<template> <template>

8
packages/nc-gui-v2/components/cell/TimePicker.vue

@ -2,14 +2,14 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ReadonlyInj } from '~/context' import { ReadonlyInj } from '~/context'
interface Props {
modelValue: string | null
}
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
interface Props {
modelValue: string
}
const { isMysql } = useProject() const { isMysql } = useProject()
const readOnlyMode = inject(ReadonlyInj, false) const readOnlyMode = inject(ReadonlyInj, false)

6
packages/nc-gui-v2/components/cell/Url.vue

@ -4,7 +4,7 @@ import { ColumnInj, EditModeInj } from '~/context'
import { isValidURL } from '~/utils' import { isValidURL } from '~/utils'
interface Props { interface Props {
modelValue: string modelValue: string | null
} }
const { modelValue: value } = defineProps<Props>() const { modelValue: value } = defineProps<Props>()
@ -18,7 +18,7 @@ const editEnabled = inject(EditModeInj, ref(false))
const vModel = computed({ const vModel = computed({
get: () => value, get: () => value,
set: (val) => { set: (val) => {
if (!(column && column.meta && column.meta.validate) || isValidURL(val)) { if (!column?.value?.meta?.validate || isValidURL(val)) {
emit('update:modelValue', val) emit('update:modelValue', val)
} }
}, },
@ -30,7 +30,7 @@ const focus = (el: HTMLInputElement) => el?.focus()
</script> </script>
<template> <template>
<input v-if="editEnabled" :ref="focus" v-model="vModel" class="outline-none" /> <input v-if="editEnabled" :ref="focus" v-model="vModel" class="outline-none" @blur="editEnabled = false" />
<nuxt-link v-else-if="isValid" class="py-2 underline hover:opacity-75" :to="value" target="_blank">{{ value }}</nuxt-link> <nuxt-link v-else-if="isValid" class="py-2 underline hover:opacity-75" :to="value" target="_blank">{{ value }}</nuxt-link>
<span v-else>{{ value }}</span> <span v-else>{{ value }}</span>
</template> </template>

8
packages/nc-gui-v2/components/cell/YearPicker.vue

@ -2,14 +2,14 @@
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { ReadonlyInj } from '~/context' import { ReadonlyInj } from '~/context'
interface Props {
modelValue: number | string | null
}
const { modelValue } = defineProps<Props>() const { modelValue } = defineProps<Props>()
const emit = defineEmits(['update:modelValue']) const emit = defineEmits(['update:modelValue'])
interface Props {
modelValue: number
}
const readOnlyMode = inject(ReadonlyInj, false) const readOnlyMode = inject(ReadonlyInj, false)
let isYearInvalid = $ref(false) let isYearInvalid = $ref(false)

2
packages/nc-gui-v2/components/cell/attachment/utils.ts

@ -78,7 +78,7 @@ export const [useProvideAttachmentCell, useAttachmentCell] = useInjectionState(
try { try {
const data = await api.storage.upload( const data = await api.storage.upload(
{ {
path: [NOCO, project.value.title, meta.value.title, column.title].join('/'), path: [NOCO, project.value.title, meta.value.title, column.value.title].join('/'),
}, },
{ {
files: file, files: file,

9
packages/nc-gui-v2/components/smartsheet-column/AdvancedOptions.vue

@ -77,13 +77,8 @@ formState.value.au = !!formState.value.au
<a-input v-model="formState.dtxs" :disabled="!sqlUi.columnEditable(formState)" size="small" @input="onAlter" /> <a-input v-model="formState.dtxs" :disabled="!sqlUi.columnEditable(formState)" size="small" @input="onAlter" />
</a-form-item> </a-form-item>
<a-form-item :label="$t('placeholder.defaultValue')"> <a-form-item :label="$t('placeholder.defaultValue')">
<a-textarea <a-textarea v-model:value="formState.cdf" size="small" auto-size @input="onAlter(2, true)" />
v-model="formState.cdf" <span class="text-gray-400 text-xs">{{ sqlUi.getDefaultValueForDatatype(formState.dt) }}</span>
:help="sqlUi.getDefaultValueForDatatype(formState.dt)"
size="small"
auto-size
@input="onAlter(2, true)"
/>
</a-form-item> </a-form-item>
</div> </div>
</template> </template>

45
packages/nc-gui-v2/components/smartsheet-column/EditOrAdd.vue

@ -1,10 +1,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { UITypes, isVirtualCol } from 'nocodb-sdk' import { UITypes, isVirtualCol } from 'nocodb-sdk'
import { computed, inject, useColumnCreateStoreOrThrow, useMetas, watchEffect } from '#imports' import { computed, inject, useColumnCreateStoreOrThrow, useMetas, watchEffect } from '#imports'
import { MetaInj } from '~/context' import { MetaInj, ReloadViewDataHookInj } from '~/context'
import { uiTypes } from '~/utils/columnUtils' import { uiTypes } from '~/utils/columnUtils'
import MdiPlusIcon from '~icons/mdi/plus-circle-outline' import MdiPlusIcon from '~icons/mdi/plus-circle-outline'
import MdiMinusIcon from '~icons/mdi/minus-circle-outline' import MdiMinusIcon from '~icons/mdi/minus-circle-outline'
import MdiIdentifierIcon from '~icons/mdi/identifier'
interface Props { interface Props {
editColumnDropdown: boolean editColumnDropdown: boolean
@ -13,11 +14,9 @@ interface Props {
const { editColumnDropdown } = defineProps<Props>() const { editColumnDropdown } = defineProps<Props>()
const emit = defineEmits(['cancel']) const emit = defineEmits(['cancel'])
const meta = inject(MetaInj) const meta = inject(MetaInj)
const reloadDataTrigger = inject(ReloadViewDataHookInj)
const advancedOptions = ref(false) const advancedOptions = ref(false)
const { getMeta } = useMetas() const { getMeta } = useMetas()
const formulaOptionsRef = ref() const formulaOptionsRef = ref()
@ -38,21 +37,23 @@ const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const uiTypesOptions = computed<typeof uiTypes>(() => { const uiTypesOptions = computed<typeof uiTypes>(() => {
return [ return [
...uiTypes.filter((t) => !isEdit || !t.virtual), ...uiTypes.filter((t) => !isEdit.value || !t.virtual),
...(!isEdit && meta?.value?.columns?.every((c) => !c.pk) ...(!isEdit.value && meta?.value?.columns?.every((c) => !c.pk)
? [ ? [
{ {
name: 'ID', name: UITypes.ID,
icon: 'mdi-identifier', icon: MdiIdentifierIcon,
virtual: 0,
}, },
] ]
: []), : []),
] ]
}) })
const reloadMeta = () => { const reloadMetaAndData = () => {
emit('cancel') emit('cancel')
getMeta(meta?.value.id as string, true) getMeta(meta?.value.id as string, true)
reloadDataTrigger?.trigger()
} }
function onCancel() { function onCancel() {
@ -65,7 +66,7 @@ function onCancel() {
// create column meta if it's a new column // create column meta if it's a new column
watchEffect(() => { watchEffect(() => {
if (!isEdit) { if (!isEdit.value) {
generateNewColumnMeta() generateNewColumnMeta()
} }
}) })
@ -107,8 +108,14 @@ watch(
/> />
</a-form-item> </a-form-item>
<a-form-item :label="$t('labels.columnType')"> <a-form-item :label="$t('labels.columnType')">
<a-select v-model:value="formState.uidt" size="small" class="nc-column-name-input" @change="onUidtOrIdTypeChange"> <a-select
<a-select-option v-for="opt in uiTypesOptions" :key="opt.name" :value="opt.name" v-bind="validateInfos.uidt"> v-model:value="formState.uidt"
show-search
size="small"
class="nc-column-name-input"
@change="onUidtOrIdTypeChange"
>
<a-select-option v-for="opt of uiTypesOptions" :key="opt.name" :value="opt.name" v-bind="validateInfos.uidt">
<div class="flex gap-1 align-center text-xs"> <div class="flex gap-1 align-center text-xs">
<component :is="opt.icon" class="text-grey" /> <component :is="opt.icon" class="text-grey" />
{{ opt.name }} {{ opt.name }}
@ -144,7 +151,7 @@ watch(
v-model:checked="formState.meta.validate" v-model:checked="formState.meta.validate"
class="ml-1 mb-1" class="ml-1 mb-1"
> >
<span class="text-xs text-gray-600"> <span class="text-[10px] text-gray-600">
{{ `Accept only valid ${formState.uidt}` }} {{ `Accept only valid ${formState.uidt}` }}
</span> </span>
</a-checkbox> </a-checkbox>
@ -156,7 +163,17 @@ watch(
<!-- Cancel --> <!-- Cancel -->
{{ $t('general.cancel') }} {{ $t('general.cancel') }}
</a-button> </a-button>
<a-button html-type="submit" type="primary" size="small" @click="addOrUpdate(reloadMeta), (advancedOptions = false)"> <a-button
html-type="submit"
type="primary"
size="small"
@click="
() => {
addOrUpdate(reloadMetaAndData)
advancedOptions = false
}
"
>
<!-- Save --> <!-- Save -->
{{ $t('general.save') }} {{ $t('general.save') }}
</a-button> </a-button>

6
packages/nc-gui-v2/components/smartsheet-header/Cell.vue

@ -1,12 +1,12 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, TableType } from 'nocodb-sdk' import type { ColumnType, TableType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { inject } from 'vue' import { inject, toRef } from 'vue'
import { ColumnInj, MetaInj } from '~/context' import { ColumnInj, MetaInj } from '~/context'
import { useProvideColumnCreateStore } from '#imports' import { useProvideColumnCreateStore } from '#imports'
const { column } = defineProps<{ column: ColumnType & { meta: any } }>() const props = defineProps<{ column: ColumnType & { meta: any } }>()
const column = toRef(props, 'column')
provide(ColumnInj, column) provide(ColumnInj, column)
const meta = inject(MetaInj) const meta = inject(MetaInj)

9
packages/nc-gui-v2/components/smartsheet-header/CellIcon.vue

@ -1,5 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { toRef } from 'vue'
import { ColumnInj } from '~/context' import { ColumnInj } from '~/context'
import FilePhoneIcon from '~icons/mdi/file-phone' import FilePhoneIcon from '~icons/mdi/file-phone'
import { useColumn } from '#imports' import { useColumn } from '#imports'
@ -24,14 +26,15 @@ import CurrencyIcon from '~icons/mdi/currency-usd-circle-outline'
import PercentIcon from '~icons/mdi/percent-outline' import PercentIcon from '~icons/mdi/percent-outline'
import DecimalIcon from '~icons/mdi/decimal' import DecimalIcon from '~icons/mdi/decimal'
const { columnMeta } = defineProps<{ columnMeta?: ColumnType }>() const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta) const column = inject(ColumnInj, columnMeta)
const additionalColMeta = useColumn(column as ColumnType) const additionalColMeta = useColumn(column as Ref<ColumnType>)
const icon = computed(() => { const icon = computed(() => {
if (column?.pk) { if (column?.value?.pk) {
return KeyIcon return KeyIcon
} else if (additionalColMeta.isJSON) { } else if (additionalColMeta.isJSON) {
return JSONIcon return JSONIcon

6
packages/nc-gui-v2/components/smartsheet-header/Menu.vue

@ -25,13 +25,13 @@ const { getMeta } = useMetas()
const deleteColumn = () => const deleteColumn = () =>
Modal.confirm({ Modal.confirm({
title: h('div', ['Do you want to delete ', h('span', { class: 'font-weight-bold' }, [column?.title]), ' column ?']), title: h('div', ['Do you want to delete ', h('span', { class: 'font-weight-bold' }, [column?.value?.title]), ' column ?']),
okText: t('general.delete'), okText: t('general.delete'),
okType: 'danger', okType: 'danger',
cancelText: t('general.cancel'), cancelText: t('general.cancel'),
async onOk() { async onOk() {
try { try {
await $api.dbTableColumn.delete(column?.id as string) await $api.dbTableColumn.delete(column?.value?.id as string)
getMeta(meta?.value?.id as string, true) getMeta(meta?.value?.id as string, true)
} catch (e) { } catch (e) {
toast.error(await extractSdkResponseErrorMsg(e)) toast.error(await extractSdkResponseErrorMsg(e))
@ -41,7 +41,7 @@ const deleteColumn = () =>
const setAsPrimaryValue = async () => { const setAsPrimaryValue = async () => {
try { try {
await $api.dbTableColumn.primaryColumnSet(column?.id as string) await $api.dbTableColumn.primaryColumnSet(column?.value?.id as string)
getMeta(meta?.value?.id as string, true) getMeta(meta?.value?.id as string, true)
toast.success('Successfully updated as primary column') toast.success('Successfully updated as primary column')
} catch (e) { } catch (e) {

204
packages/nc-gui-v2/components/smartsheet-header/VirtualCell.vue

@ -1,141 +1,80 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, TableType } from 'nocodb-sdk' import { substituteColumnIdWithAliasInFormula } from 'nocodb-sdk'
import type { ColumnType, FormulaType, LinkToAnotherRecordType, LookupType, RollupType, TableType } from 'nocodb-sdk'
import { toRef } from 'vue'
import { $computed } from 'vue/macros'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { useMetas } from '~/composables'
import { ColumnInj, MetaInj } from '~/context' import { ColumnInj, MetaInj } from '~/context'
import { provide } from '#imports' import { provide, useProvideColumnCreateStore } from '#imports'
const { column } = defineProps<{ column: ColumnType & { meta: any } }>()
const props = defineProps<{ column: ColumnType & { meta: any } }>()
const column = toRef(props, 'column')
provide(ColumnInj, column) provide(ColumnInj, column)
const { metas } = useMetas()
const meta = inject(MetaInj) const meta = inject(MetaInj)
useProvideColumnCreateStore(meta as Ref<TableType>, column) const { isLookup, isBt, isRollup, isMm, isHm, isFormula, isCount } = useVirtualCell(column)
const colOptions = $computed(() => column.value?.colOptions)
const tableTile = $computed(() => meta?.value?.title)
const relationColumnOptions = $computed<LinkToAnotherRecordType | null>(() => {
if (isMm.value || isHm.value || isBt.value) {
return column.value?.colOptions as LinkToAnotherRecordType
} else if ((column?.value?.colOptions as LookupType | RollupType)?.fk_relation_column_id) {
return meta?.value?.columns?.find(
(c) => c.id === (column?.value?.colOptions as LookupType | RollupType)?.fk_relation_column_id,
)?.colOptions as LinkToAnotherRecordType
}
return null
})
const relatedTableMeta = $computed(
() => relationColumnOptions?.fk_related_model_id && metas.value?.[relationColumnOptions?.fk_related_model_id as string],
)
const relatedTableTitle = $computed(() => relatedTableMeta?.title)
const childColumn = $computed(() => {
if (relatedTableMeta?.columns) {
if (isRollup.value) {
const ch = relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as RollupType).fk_rollup_column_id)
return ch
}
if (isLookup.value) {
const ch = relatedTableMeta?.columns.find((c: ColumnType) => c.id === (colOptions as LookupType).fk_lookup_column_id)
return ch
}
}
return ''
})
const tooltipMsg = computed(() => {
if (!column.value) {
return ''
}
if (isHm.value) {
return `'${tableTile}' has many '${relatedTableTitle}'`
} else if (isMm.value) {
return `'${tableTile}' & '${relatedTableTitle}' have many to many relation`
} else if (isBt.value) {
return `'${column?.value?.title}' belongs to '${relatedTableTitle}'`
} else if (isLookup.value) {
return `'${childColumn.title}' from '${relatedTableTitle}' (${childColumn.uidt})`
} else if (isFormula.value) {
const formula = substituteColumnIdWithAliasInFormula(
(column.value?.colOptions as FormulaType)?.formula,
meta?.value?.columns as ColumnType[],
(column.value?.colOptions as any)?.formula_raw,
)
return `Formula - ${formula}`
} else if (isRollup.value) {
return `'${childColumn.title}' of '${relatedTableTitle}' (${childColumn.uidt})`
}
return ''
})
// import { UITypes } from 'nocodb-sdk' useProvideColumnCreateStore(meta as Ref<TableType>, column)
// import { getUIDTIcon } from '../helpers/uiTypes'
// import EditVirtualColumn from '~/components/project/spreadsheet/components/EditVirtualColumn'
//
// export default {
// name: 'VirtualHeaderCell',
// components: { EditVirtualColumn },
// props: ['column', 'nodes', 'meta', 'isForm', 'isPublicView', 'sqlUi', 'required', 'isLocked', 'isVirtual'],
// data: () => ({
// columnDeleteDialog: false,
// editColumnMenu: false,
// rollupIcon: getUIDTIcon('Rollup'),
// rels: ['bt', 'hm', 'mm']
// }),
// computed: {
// alias() {
// // return this.column.lk ? `${this.column.lk._lcn} <small class="grey--text text--darken-1">(from ${this.column.lk._ltn})</small>` : this.column.title
// return this.column.title
// },
// type() {
// if (this.column?.colOptions?.type) {
// return this.column.colOptions.type
// }
// if (this.column?.colOptions?.formula) {
// return 'formula'
// }
// if (this.column.uidt === UITypes.Lookup) {
// return 'lk'
// }
// if (this.column.uidt === UITypes.Rollup) {
// return 'rl'
// }
// return ''
// },
// relation() {
// if (this.rels.includes(this.type)) {
// return this.column
// } else if (this.column.colOptions?.fk_relation_column_id) {
// return this.meta.columns.find(c => c.id === this.column.colOptions?.fk_relation_column_id)
// }
// return undefined
// },
// relationType() {
// return this.relation?.colOptions?.type
// },
// relationMeta() {
// if (this.rels.includes(this.type)) {
// return this.getMeta(this.column.colOptions.fk_related_model_id)
// } else if (this.relation) {
// return this.getMeta(this.relation.colOptions.fk_related_model_id)
// }
// return undefined
// },
// childColumn() {
// if (this.relationMeta?.columns) {
// if (this.type === 'rl') {
// const ch = this.relationMeta.columns.find(c => c.id === this.column.colOptions.fk_rollup_column_id)
// return ch
// }
// if (this.type === 'lk') {
// const ch = this.relationMeta.columns.find(c => c.id === this.column.colOptions.fk_lookup_column_id)
// return ch
// }
// }
// return ''
// },
// childTable() {
// if (this.relationMeta?.title) {
// return this.relationMeta.title
// }
// return ''
// },
// parentTable() {
// if (this.rels.includes(this.type)) {
// return this.meta.title
// }
// return ''
// },
// parentColumn() {
// if (this.rels.includes(this.type)) {
// return this.column.title
// }
// return ''
// },
// tooltipMsg() {
// if (!this.column) {
// return ''
// }
// if (this.type === 'hm') {
// return `'${this.parentTable}' has many '${this.childTable}'`
// } else if (this.type === 'mm') {
// return `'${this.childTable}' & '${this.parentTable}' have <br>many to many relation`
// } else if (this.type === 'bt') {
// return `'${this.column.title}' belongs to '${this.childTable}'`
// } else if (this.type === 'lk') {
// return `'${this.childColumn.title}' from '${this.childTable}' (${this.childColumn.uidt})`
// } else if (this.type === 'formula') {
// return `Formula - ${this.column.colOptions.formula}`
// } else if (this.type === 'rl') {
// return `'${this.childColumn.title}' of '${this.childTable}' (${this.childColumn.uidt})`
// }
// return ''
// }
// },
// methods: {
// getMeta(id) {
// return this.$store.state.meta.metas[id] || {}
// },
// async deleteColumn() {
// try {
// await this.$api.dbTableColumn.delete(this.column.id)
//
// if (this.column.uidt === UITypes.LinkToAnotherRecord && this.column.colOptions) {
// this.$store.dispatch('meta/ActLoadMeta', { force: true, id: this.column.colOptions.fk_related_model_id }).then(() => {})
// }
//
// this.$emit('saved')
// this.columnDeleteDialog = false
// } catch (e) {
// this.$toast.error(await this._extractSdkResponseErrorMsg(e)).goAway(3000)
// }
// }
// }
// }
</script> </script>
<template> <template>
@ -145,7 +84,12 @@ useProvideColumnCreateStore(meta as Ref<TableType>, column)
todo: bring tooltip todo: bring tooltip
--> -->
<SmartsheetHeaderVirtualCellIcon v-if="column" /> <SmartsheetHeaderVirtualCellIcon v-if="column" />
<a-tooltip placement="bottom">
<template #title>
{{ tooltipMsg }}
</template>
<span class="name" style="white-space: nowrap" :title="column.title"> {{ column.title }}</span> <span class="name" style="white-space: nowrap" :title="column.title"> {{ column.title }}</span>
</a-tooltip>
<span v-if="column.rqd" class="error--text text--lighten-1">&nbsp;*</span> <span v-if="column.rqd" class="error--text text--lighten-1">&nbsp;*</span>
<!-- <span class="caption" v-html="tooltipMsg" /> --> <!-- <span class="caption" v-html="tooltipMsg" /> -->

10
packages/nc-gui-v2/components/smartsheet-header/VirtualCellIcon.vue

@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk' import type { ColumnType, LinkToAnotherRecordType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk' import { RelationTypes, UITypes } from 'nocodb-sdk'
import { toRef } from 'vue'
import { ColumnInj } from '~/context' import { ColumnInj } from '~/context'
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'
@ -11,14 +12,15 @@ import RollupIcon from '~icons/mdi/movie-roll'
import CountIcon from '~icons/mdi/counter' import CountIcon from '~icons/mdi/counter'
import SpecificDBTypeIcon from '~icons/mdi/database-settings' import SpecificDBTypeIcon from '~icons/mdi/database-settings'
const { columnMeta } = defineProps<{ columnMeta?: ColumnType }>() const props = defineProps<{ columnMeta?: ColumnType }>()
const columnMeta = toRef(props, 'columnMeta')
const column = inject(ColumnInj, columnMeta) const column = inject(ColumnInj, ref(columnMeta))
const icon = computed(() => { const icon = computed(() => {
switch (column?.uidt) { switch (column?.value?.uidt) {
case UITypes.LinkToAnotherRecord: case UITypes.LinkToAnotherRecord:
switch ((column?.colOptions as LinkToAnotherRecordType)?.type) { switch ((column?.value?.colOptions as LinkToAnotherRecordType)?.type) {
case RelationTypes.MANY_TO_MANY: case RelationTypes.MANY_TO_MANY:
return MMIcon return MMIcon
case RelationTypes.HAS_MANY: case RelationTypes.HAS_MANY:

2
packages/nc-gui-v2/components/smartsheet-toolbar/ColumnFilter.vue

@ -124,7 +124,7 @@ defineExpose({
v-if="!filter.readOnly" v-if="!filter.readOnly"
:key="i" :key="i"
small small
class="nc-filter-item-remove-btn text-grey" class="nc-filter-item-remove-btn cursor-pointer text-grey"
@click.stop="deleteFilter(filter, i)" @click.stop="deleteFilter(filter, i)"
/> />
<span v-else :key="`${i}dummy`" /> <span v-else :key="`${i}dummy`" />

10
packages/nc-gui-v2/components/smartsheet/Cell.vue

@ -16,9 +16,9 @@ interface Emits {
(event: 'update:modelValue', value: any): void (event: 'update:modelValue', value: any): void
} }
const { column, ...props } = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'save', 'navigate', 'update:editEnabled']) const emit = defineEmits(['update:modelValue', 'save', 'navigate', 'update:editEnabled'])
const column = toRef(props, 'column')
provide(ColumnInj, column) provide(ColumnInj, column)
@ -44,11 +44,11 @@ const isAutoSaved = $computed(() => {
UITypes.AutoNumber, UITypes.AutoNumber,
UITypes.SpecificDBType, UITypes.SpecificDBType,
UITypes.Geometry, UITypes.Geometry,
].includes(column.uidt as UITypes) ].includes(column?.value?.uidt as UITypes)
}) })
const isManualSaved = $computed(() => { const isManualSaved = $computed(() => {
return [UITypes.Currency, UITypes.Year, UITypes.Time, UITypes.Duration].includes(column.uidt as UITypes) return [UITypes.Currency, UITypes.Year, UITypes.Time, UITypes.Duration].includes(column?.value?.uidt as UITypes)
}) })
const vModel = computed({ const vModel = computed({
@ -102,7 +102,7 @@ const syncAndNavigate = (dir: NavigateDir) => {
<template> <template>
<div <div
class="nc-cell" class="nc-cell w-full-h-full"
@keydown.stop.left @keydown.stop.left
@keydown.stop.right @keydown.stop.right
@keydown.stop.up @keydown.stop.up

97
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -1,8 +1,10 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onClickOutside, useEventListener } from '@vueuse/core'
import type { ColumnType } from 'nocodb-sdk'
import { isVirtualCol } from 'nocodb-sdk' import { isVirtualCol } from 'nocodb-sdk'
import { message } from 'ant-design-vue'
import { import {
inject, inject,
onKeyStroke,
onMounted, onMounted,
provide, provide,
useGridViewColumnWidth, useGridViewColumnWidth,
@ -10,10 +12,10 @@ import {
useSmartsheetStoreOrThrow, useSmartsheetStoreOrThrow,
useViewData, useViewData,
} from '#imports' } from '#imports'
import type { Row } from '~/composables'
import { import {
ActiveViewInj, ActiveViewInj,
ChangePageInj, ChangePageInj,
EditModeInj,
FieldsInj, FieldsInj,
IsFormInj, IsFormInj,
IsGridInj, IsGridInj,
@ -34,11 +36,12 @@ const fields = inject(FieldsInj, ref([]))
const isLocked = inject(IsLockedInj, false) const isLocked = inject(IsLockedInj, false)
// todo: get from parent ( inject or use prop ) // todo: get from parent ( inject or use prop )
const isPublicView = false const isPublicView = false
const isView = false
const selected = reactive<{ row: number | null; col: number | null }>({ row: null, col: null }) const selected = reactive<{ row: number | null; col: number | null }>({ row: null, col: null })
let editEnabled = $ref(false) let editEnabled = $ref(false)
const { sqlUi } = useProject() const { sqlUi } = useProject()
const { xWhere } = useSmartsheetStoreOrThrow() const { xWhere, isPkAvail } = useSmartsheetStoreOrThrow()
const addColumnDropdown = ref(false) const addColumnDropdown = ref(false)
const contextMenu = ref(false) const contextMenu = ref(false)
const contextMenuTarget = ref(false) const contextMenuTarget = ref(false)
@ -74,12 +77,6 @@ const selectCell = (row: number, col: number) => {
selected.col = col selected.col = col
} }
onKeyStroke(['Enter'], (e) => {
if (selected.row !== null && selected.col !== null) {
editEnabled = true
}
})
watch( watch(
() => (view?.value as any)?.id, () => (view?.value as any)?.id,
async (n?: string, o?: string) => { async (n?: string, o?: string) => {
@ -103,9 +100,7 @@ defineExpose({
}) })
// instantiate column create store // instantiate column create store
// watchEffect(() => {
if (meta) useProvideColumnCreateStore(meta) if (meta) useProvideColumnCreateStore(meta)
// })
// reset context menu target on hide // reset context menu target on hide
watch(contextMenu, () => { watch(contextMenu, () => {
@ -127,8 +122,29 @@ const clearCell = async (ctx: { row: number; col: number }) => {
await updateOrSaveRow(rowObj, columnObj.title) await updateOrSaveRow(rowObj, columnObj.title)
} }
const { copy } = useClipboard()
const makeEditable = (row: Row, col: ColumnType) => {
if (isPublicView || editEnabled || isView) {
return
}
if (!isPkAvail.value && !row.rowMeta.new) {
message.info("Update not allowed for table which doesn't have primary Key")
return
}
if (col.ai) {
message.info('Auto Increment field is not editable')
return
}
if (col.pk && !row.rowMeta.new) {
message.info('Editing primary key not supported')
return
}
return (editEnabled = true)
}
/** handle keypress events */ /** handle keypress events */
onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLeft', 'ArrowRight'], async (e: KeyboardEvent) => { const onKeyDown = async (e: KeyboardEvent) => {
if (selected.row === null || selected.col === null) return if (selected.row === null || selected.col === null) return
/** on tab key press navigate through cells */ /** on tab key press navigate through cells */
switch (e.key) { switch (e.key) {
@ -153,7 +169,7 @@ onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLe
/** on enter key press make cell editable */ /** on enter key press make cell editable */
case 'Enter': case 'Enter':
e.preventDefault() e.preventDefault()
editEnabled = true makeEditable(data.value[selected.row], fields.value[selected.col])
break break
/** on delete key press clear cell */ /** on delete key press clear cell */
case 'Delete': case 'Delete':
@ -177,26 +193,59 @@ onKeyStroke(['Tab', 'Shift', 'Enter', 'Delete', 'ArrowDown', 'ArrowUp', 'ArrowLe
e.preventDefault() e.preventDefault()
if (selected.row < data.value.length - 1) selected.row++ if (selected.row < data.value.length - 1) selected.row++
break break
default:
{
const rowObj = data.value[selected.row]
const columnObj = fields.value[selected.col]
if (e.metaKey || e.ctrlKey) {
switch (e.keyCode) {
// copy - ctrl/cmd +c
case 67:
await copy(rowObj.row[columnObj.title] || '')
break
}
}
if (editEnabled || e.ctrlKey || e.altKey || e.metaKey) {
return
}
/** on letter key press make cell editable and empty */
if (e?.key?.length === 1) {
if (!isPkAvail && !rowObj.rowMeta.new) {
return message.info("Update not allowed for table which doesn't have primary Key")
}
if (makeEditable(rowObj, columnObj)) {
rowObj.row[columnObj.title] = ''
}
// editEnabled = true
}
}
break
} }
}
useEventListener(document, 'keydown', onKeyDown)
/** On clicking outside of table reset active cell */
const smartTable = ref(null)
onClickOutside(smartTable, () => {
selected.row = null
selected.col = null
}) })
const onNavigate = (dir: NavigateDir) => { const onNavigate = (dir: NavigateDir) => {
if (selected.row === null || selected.col === null) return if (selected.row === null || selected.col === null) return
switch (dir) { switch (dir) {
case NavigateDir.NEXT: case NavigateDir.NEXT:
if (selected.col < visibleColLength - 1) { if (selected.row < data.value.length - 1) {
selected.col++
} else if (selected.row < data.value.length - 1) {
selected.row++ selected.row++
selected.col = 0
} }
break break
case NavigateDir.PREV: case NavigateDir.PREV:
if (selected.col > 0) { if (selected.row > 0) {
selected.col--
} else if (selected.row > 0) {
selected.row-- selected.row--
selected.col = visibleColLength - 1
} }
break break
} }
@ -207,7 +256,7 @@ const onNavigate = (dir: NavigateDir) => {
<div class="flex flex-col h-100 min-h-0 w-100"> <div class="flex flex-col h-100 min-h-0 w-100">
<div class="nc-grid-wrapper min-h-0 flex-1 scrollbar-thin-primary"> <div class="nc-grid-wrapper min-h-0 flex-1 scrollbar-thin-primary">
<a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']"> <a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']">
<table class="xc-row-table nc-grid backgroundColorDefault" @contextmenu.prevent="contextMenu = true"> <table ref="smartTable" class="xc-row-table nc-grid backgroundColorDefault" @contextmenu.prevent="contextMenu = true">
<thead> <thead>
<tr class="group"> <tr class="group">
<th> <th>
@ -237,7 +286,7 @@ const onNavigate = (dir: NavigateDir) => {
<!-- v-if="!isLocked && !isVirtual && !isPublicView && _isUIAllowed('add-column')" --> <!-- v-if="!isLocked && !isVirtual && !isPublicView && _isUIAllowed('add-column')" -->
<th v-t="['c:column:add']" @click="addColumnDropdown = true"> <th v-t="['c:column:add']" @click="addColumnDropdown = true">
<a-dropdown v-model:visible="addColumnDropdown" :trigger="['click']"> <a-dropdown v-model:visible="addColumnDropdown" :trigger="['click']">
<div class="h-full w-full flex align-center justify-center"> <div class="h-full w-[60px] flex align-center justify-center">
<MdiPlusIcon class="text-sm" /> <MdiPlusIcon class="text-sm" />
</div> </div>
<template #overlay> <template #overlay>
@ -272,7 +321,7 @@ const onNavigate = (dir: NavigateDir) => {
:data-col="columnObj.id" :data-col="columnObj.id"
:data-title="columnObj.title" :data-title="columnObj.title"
@click="selectCell(rowIndex, colIndex)" @click="selectCell(rowIndex, colIndex)"
@dblclick="editEnabled = true" @dblclick="makeEditable(row, columnObj)"
@contextmenu="contextMenuTarget = { row: rowIndex, col: colIndex }" @contextmenu="contextMenuTarget = { row: rowIndex, col: colIndex }"
> >
<div class="w-full h-full"> <div class="w-full h-full">

8
packages/nc-gui-v2/components/smartsheet/Toolbar.vue

@ -1,3 +1,9 @@
<script setup lang="ts">
import { useSmartsheetStoreOrThrow } from '~/composables'
const { isGrid, isForm } = useSmartsheetStoreOrThrow()
</script>
<template> <template>
<div class="nc-table-toolbar w-full py-1 flex gap-1 items-center" style="z-index: 7"> <div class="nc-table-toolbar w-full py-1 flex gap-1 items-center" style="z-index: 7">
<SmartsheetToolbarSearchData class="flex-shrink" /> <SmartsheetToolbarSearchData class="flex-shrink" />
@ -8,7 +14,7 @@
<SmartsheetToolbarSortListMenu /> <SmartsheetToolbarSortListMenu />
<SmartsheetToolbarShareView /> <SmartsheetToolbarShareView v-if="isForm || isGrid" />
<SmartsheetToolbarMoreActions /> <SmartsheetToolbarMoreActions />

4
packages/nc-gui-v2/components/smartsheet/VirtualCell.vue

@ -14,12 +14,12 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits(['update:modelValue', 'navigate']) const emit = defineEmits(['update:modelValue', 'navigate'])
const { column, modelValue: value } = props
const column = toRef(props, 'column')
const active = toRef(props, 'active', false) const active = toRef(props, 'active', false)
const row = toRef(props, 'row') const row = toRef(props, 'row')
provide(ColumnInj, column) provide(ColumnInj, column)
provide(CellValueInj, value)
provide(ActiveCellInj, active) provide(ActiveCellInj, active)
provide(RowInj, row) provide(RowInj, row)
provide(CellValueInj, toRef(props, 'modelValue')) provide(CellValueInj, toRef(props, 'modelValue'))

8
packages/nc-gui-v2/components/tabs/Smartsheet.vue

@ -37,7 +37,7 @@ provide(ReloadViewDataHookInj, reloadEventHook)
provide(FieldsInj, fields) provide(FieldsInj, fields)
provide(RightSidebarInj, ref(true)) provide(RightSidebarInj, ref(true))
useProvideSmartsheetStore(activeView as Ref<TableType>, meta) const { isGallery, isGrid, isForm } = useProvideSmartsheetStore(activeView as Ref<TableType>, meta)
watch(tabMeta, async (newTabMeta, oldTabMeta) => { watch(tabMeta, async (newTabMeta, oldTabMeta) => {
if (newTabMeta !== oldTabMeta && newTabMeta?.id) await getMeta(newTabMeta.id) if (newTabMeta !== oldTabMeta && newTabMeta?.id) await getMeta(newTabMeta.id)
@ -52,11 +52,11 @@ watch(tabMeta, async (newTabMeta, oldTabMeta) => {
<template v-if="meta"> <template v-if="meta">
<div class="flex flex-1 min-h-0"> <div class="flex flex-1 min-h-0">
<div v-if="activeView" class="h-full flex-grow min-w-0 min-h-0"> <div v-if="activeView" class="h-full flex-grow min-w-0 min-h-0">
<SmartsheetGrid v-if="activeView.type === ViewTypes.GRID" :ref="el" /> <SmartsheetGrid v-if="isGrid" :ref="el" />
<SmartsheetGallery v-else-if="activeView.type === ViewTypes.GALLERY" /> <SmartsheetGallery v-else-if="isGallery" />
<SmartsheetForm v-else-if="activeView.type === ViewTypes.FORM" /> <SmartsheetForm v-else-if="isForm" />
</div> </div>
</div> </div>

3
packages/nc-gui-v2/components/virtual-cell/BelongsTo.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 type { Ref } from 'vue'
import ItemChip from './components/ItemChip.vue' import ItemChip from './components/ItemChip.vue'
import ListItems from './components/ListItems.vue' import ListItems from './components/ListItems.vue'
import { useProvideLTARStore } from '#imports' import { useProvideLTARStore } from '#imports'
@ -15,7 +16,7 @@ const localState = null
const listItemsDlg = ref(false) const listItemsDlg = ref(false)
const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore( const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Required<ColumnType>, column as Ref<Required<ColumnType>>,
row, row,
() => reloadTrigger?.trigger(), () => reloadTrigger?.trigger(),
) )

3
packages/nc-gui-v2/components/virtual-cell/HasMany.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 type { Ref } from 'vue'
import ItemChip from './components/ItemChip.vue' import ItemChip from './components/ItemChip.vue'
import ListChildItems from './components/ListChildItems.vue' import ListChildItems from './components/ListChildItems.vue'
import ListItems from './components/ListItems.vue' import ListItems from './components/ListItems.vue'
@ -17,7 +18,7 @@ const listItemsDlg = ref(false)
const childListDlg = ref(false) const childListDlg = ref(false)
const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore( const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Required<ColumnType>, column as Ref<Required<ColumnType>>,
row, row,
() => reloadTrigger?.trigger(), () => reloadTrigger?.trigger(),
) )

3
packages/nc-gui-v2/components/virtual-cell/ManyToMany.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 type { Ref } from 'vue'
import ItemChip from './components/ItemChip.vue' import ItemChip from './components/ItemChip.vue'
import ListChildItems from './components/ListChildItems.vue' import ListChildItems from './components/ListChildItems.vue'
import ListItems from './components/ListItems.vue' import ListItems from './components/ListItems.vue'
@ -17,7 +18,7 @@ const listItemsDlg = ref(false)
const childListDlg = ref(false) const childListDlg = ref(false)
const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore( const { relatedTableMeta, loadRelatedTableMeta, relatedTablePrimaryValueProp, unlink } = useProvideLTARStore(
column as Required<ColumnType>, column as Ref<Required<ColumnType>>,
row, row,
() => reloadTrigger?.trigger(), () => reloadTrigger?.trigger(),
) )

11
packages/nc-gui-v2/composables/useColumn.ts

@ -1,16 +1,17 @@
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { SqlUiFactory, UITypes, isVirtualCol } from 'nocodb-sdk' import { SqlUiFactory, UITypes, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue'
import { useProject } from '#imports' import { useProject } from '#imports'
export function useColumn(column: ColumnType) { export function useColumn(column: Ref<ColumnType>) {
const { project } = useProject() const { project } = useProject()
const uiDatatype: UITypes = (column && column.uidt) as UITypes const uiDatatype: UITypes = column?.value?.uidt as UITypes
const abstractType = isVirtualCol(column) const abstractType = isVirtualCol(column?.value)
? null ? null
: SqlUiFactory.create(project.value?.bases?.[0]?.config || { client: 'mysql2' }).getAbstractType(column) : SqlUiFactory.create(project.value?.bases?.[0]?.config || { client: 'mysql2' }).getAbstractType(column?.value)
const dataTypeLow = column && column.dt && column.dt.toLowerCase() const dataTypeLow = column?.value?.dt?.toLowerCase()
const isBoolean = abstractType === 'boolean' const isBoolean = abstractType === 'boolean'
const isString = abstractType === 'string' const isString = abstractType === 'string'
const isTextArea = uiDatatype === UITypes.LongText const isTextArea = uiDatatype === UITypes.LongText

40
packages/nc-gui-v2/composables/useColumnCreateStore.ts

@ -20,7 +20,8 @@ const useForm = Form.useForm
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber] const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState((meta: Ref<TableType>, column?: ColumnType) => { const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState(
(meta: Ref<TableType>, column?: Ref<ColumnType>) => {
const { sqlUi } = useProject() const { sqlUi } = useProject()
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
@ -33,9 +34,9 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
const formState = ref<Partial<Record<string, any>>>({ const formState = ref<Partial<Record<string, any>>>({
title: 'title', title: 'title',
uidt: UITypes.SingleLineText, uidt: UITypes.SingleLineText,
...(column || {}), ...(column?.value || {}),
// todo: swagger json update - include meta // todo: swagger json update - include meta
meta: (column as any)?.meta || {}, meta: (column?.value as any)?.meta || {},
}) })
const additionalValidations = ref<Record<string, any>>({}) const additionalValidations = ref<Record<string, any>>({})
@ -90,7 +91,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
} }
const onUidtOrIdTypeChange = () => { const onUidtOrIdTypeChange = () => {
const { isCurrency } = useColumn(formState.value as ColumnType) const { isCurrency } = useColumn(ref(formState.value as ColumnType))
const colProp = sqlUi?.value.getDataTypeForUiType(formState?.value as any, idType as any) const colProp = sqlUi?.value.getDataTypeForUiType(formState?.value as any, idType as any)
formState.value = { formState.value = {
@ -109,8 +110,8 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
formState.value.dtxs = sqlUi.value.getDefaultScaleForDatatype(formState.value.dt) formState.value.dtxs = sqlUi.value.getDefaultScaleForDatatype(formState.value.dt)
const selectTypes = [UITypes.MultiSelect, UITypes.SingleSelect] const selectTypes = [UITypes.MultiSelect, UITypes.SingleSelect]
if (column && selectTypes.includes(formState.value.uidt) && selectTypes.includes(column.uidt as UITypes)) { if (column && selectTypes.includes(formState.value.uidt) && selectTypes.includes(column?.value?.uidt as UITypes)) {
formState.value.dtxp = column.dtxp formState.value.dtxp = column?.value?.dtxp
} }
if (columnToValidate.includes(formState.value.uidt)) { if (columnToValidate.includes(formState.value.uidt)) {
@ -120,9 +121,9 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
} }
if (isCurrency) { if (isCurrency) {
if (column?.uidt === UITypes.Currency) { if (column?.value?.uidt === UITypes.Currency) {
formState.value.dtxp = column.dtxp formState.value.dtxp = column.value.dtxp
formState.value.dtxs = column.dtxs formState.value.dtxs = column.value.dtxs
} else { } else {
formState.value.dtxp = 19 formState.value.dtxp = 19
formState.value.dtxs = 2 formState.value.dtxs = 2
@ -133,7 +134,7 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
} }
const onDataTypeChange = () => { const onDataTypeChange = () => {
const { isCurrency } = useColumn(formState.value as ColumnType) const { isCurrency } = useColumn(ref(formState.value as ColumnType))
formState.value.rqd = false formState.value.rqd = false
if (formState.value.uidt !== UITypes.ID) { if (formState.value.uidt !== UITypes.ID) {
@ -148,14 +149,14 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
formState.value.dtx = 'specificType' formState.value.dtx = 'specificType'
const selectTypes = [UITypes.MultiSelect, UITypes.SingleSelect] const selectTypes = [UITypes.MultiSelect, UITypes.SingleSelect]
if (column && selectTypes.includes(formState.value.uidt) && selectTypes.includes(column.uidt as UITypes)) { if (column?.value && selectTypes.includes(formState.value.uidt) && selectTypes.includes(column?.value.uidt as UITypes)) {
formState.value.dtxp = column.dtxp formState.value.dtxp = column?.value.dtxp
} }
if (isCurrency) { if (isCurrency) {
if (column?.uidt === UITypes.Currency) { if (column?.value?.uidt === UITypes.Currency) {
formState.value.dtxp = column.dtxp formState.value.dtxp = column.value.dtxp
formState.value.dtxs = column.dtxs formState.value.dtxs = column.value.dtxs
} else { } else {
formState.value.dtxp = 19 formState.value.dtxp = 19
formState.value.dtxs = 2 formState.value.dtxs = 2
@ -179,8 +180,8 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
formState.value.table_name = meta.value.table_name formState.value.table_name = meta.value.table_name
formState.value.title = formState.value.column_name formState.value.title = formState.value.column_name
if (column) { if (column?.value) {
await $api.dbTableColumn.update(column.id as string, formState.value) await $api.dbTableColumn.update(column?.value?.id as string, formState.value)
toast.success('Column updated') toast.success('Column updated')
} else { } else {
// todo : set additional meta for auto generated string id // todo : set additional meta for auto generated string id
@ -215,10 +216,11 @@ const [useProvideColumnCreateStore, useColumnCreateStore] = createInjectionState
onAlter, onAlter,
addOrUpdate, addOrUpdate,
generateNewColumnMeta, generateNewColumnMeta,
isEdit: !!column?.id, isEdit: computed(() => !!column?.value?.id),
column, column,
} }
}) },
)
export { useProvideColumnCreateStore } export { useProvideColumnCreateStore }

18
packages/nc-gui-v2/composables/useLTARStore.ts

@ -13,7 +13,7 @@ interface DataApiResponse {
/** Store for managing Link to another cells */ /** Store for managing Link to another cells */
const [useProvideLTARStore, useLTARStore] = useInjectionState( const [useProvideLTARStore, useLTARStore] = useInjectionState(
(column: Required<ColumnType>, row?: Ref<Row>, reloadData = () => {}) => { (column: Ref<Required<ColumnType>>, row?: Ref<Row>, reloadData = () => {}) => {
// state // state
const { metas, getMeta } = useMetas() const { metas, getMeta } = useMetas()
const { project } = useProject() const { project } = useProject()
@ -31,12 +31,12 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
size: 10, size: 10,
}) })
const colOptions = column.colOptions as LinkToAnotherRecordType const colOptions = $computed(() => column?.value.colOptions as LinkToAnotherRecordType)
// getters // getters
const meta = computed(() => metas?.value?.[column.fk_model_id as string]) const meta = computed(() => metas?.value?.[column?.value?.fk_model_id as string])
const relatedTableMeta = computed<TableType>(() => { const relatedTableMeta = computed<TableType>(() => {
return metas.value?.[(column.colOptions as any)?.fk_related_model_id as string] return metas.value?.[colOptions?.fk_related_model_id as string]
}) })
const rowId = computed(() => const rowId = computed(() =>
@ -72,8 +72,8 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
project.value.id as string, project.value.id as string,
meta.value.id, meta.value.id,
rowId.value, rowId.value,
(column.colOptions as LinkToAnotherRecordType).type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column.title, column?.value?.title,
// todo: swagger type correction // todo: swagger type correction
{ {
limit: childrenExcludedListPagination.size, limit: childrenExcludedListPagination.size,
@ -99,7 +99,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
meta.value.id, meta.value.id,
rowId.value, rowId.value,
colOptions.type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column.title, column?.value?.title,
// todo: swagger type correction // todo: swagger type correction
{ {
limit: childrenListPagination.size, limit: childrenListPagination.size,
@ -157,7 +157,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
meta.value.title, meta.value.title,
rowId.value, rowId.value,
colOptions.type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column.title, column?.value?.title,
getRelatedTableRowId(row) as string, getRelatedTableRowId(row) as string,
) )
} catch (e) { } catch (e) {
@ -195,7 +195,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
meta.value.title as string, meta.value.title as string,
rowId.value, rowId.value,
colOptions.type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column.title, column?.value?.title,
getRelatedTableRowId(row) as string, getRelatedTableRowId(row) as string,
) )
} catch (e) { } catch (e) {

15
packages/nc-gui-v2/composables/useSmartsheetStore.ts

@ -1,11 +1,12 @@
import { computed } from '@vue/reactivity' import { computed } from '@vue/reactivity'
import { createInjectionState } from '@vueuse/core' import { ViewTypes } from 'nocodb-sdk'
import type { TableType, ViewType } from 'nocodb-sdk' import type { TableType, ViewType } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { useNuxtApp } from '#app' import { useNuxtApp } from '#app'
import { useProject } from '#imports' import { useProject } from '#imports'
import { useInjectionState } from '~/composables/useInjectionState'
const [useProvideSmartsheetStore, useSmartsheetStore] = createInjectionState((view: Ref<ViewType>, meta: Ref<TableType>) => { const [useProvideSmartsheetStore, useSmartsheetStore] = useInjectionState((view: Ref<ViewType>, meta: Ref<TableType>) => {
const { $api } = useNuxtApp() const { $api } = useNuxtApp()
const { sqlUi } = useProject() const { sqlUi } = useProject()
@ -18,6 +19,10 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = createInjectionState((vi
// getters // getters
const isLocked = computed(() => (view?.value as any)?.lock_type === 'locked') const isLocked = computed(() => (view?.value as any)?.lock_type === 'locked')
const isPkAvail = computed(() => meta?.value?.columns?.some((c) => c.pk))
const isGrid = computed(() => (view?.value as any)?.type === ViewTypes.GRID)
const isForm = computed(() => (view?.value as any)?.type === ViewTypes.FORM)
const isGallery = computed(() => (view?.value as any)?.type === ViewTypes.GALLERY)
const xWhere = computed(() => { const xWhere = computed(() => {
let where let where
const col = meta?.value?.columns?.find(({ id }) => id === search.field) || meta?.value?.columns?.find((v) => v.pv) const col = meta?.value?.columns?.find(({ id }) => id === search.field) || meta?.value?.columns?.find((v) => v.pv)
@ -41,8 +46,12 @@ const [useProvideSmartsheetStore, useSmartsheetStore] = createInjectionState((vi
$api, $api,
search, search,
xWhere, xWhere,
isPkAvail,
isForm,
isGrid,
isGallery,
} }
}) }, 'smartsheet-store')
export { useProvideSmartsheetStore } export { useProvideSmartsheetStore }

15
packages/nc-gui-v2/composables/useTabs.ts

@ -18,15 +18,15 @@ export interface TabItem {
function getPredicate(key: Partial<TabItem>) { function getPredicate(key: Partial<TabItem>) {
return (tab: TabItem) => return (tab: TabItem) =>
(!('id' in key) || tab.id === key.id) && (!('id' in key) || tab.id === key.id) &&
(!('title' in key) || tab.title === key.id) && (!('title' in key) || tab.title === key.title) &&
(!('type' in key) || tab.type === key.id) (!('type' in key) || tab.type === key.type)
} }
export function useTabs() { export function useTabs() {
const tabs = useState<TabItem[]>('tabs', () => []) const tabs = useState<TabItem[]>('tabs', () => [])
const route = useRoute() const route = useRoute()
const router = useRouter()
const { tables } = useProject() const { tables } = useProject()
const activeTabIndex: WritableComputedRef<number> = computed({ const activeTabIndex: WritableComputedRef<number> = computed({
@ -36,6 +36,8 @@ export function useTabs() {
const id = tables.value?.find((t) => t.title === tab.title)?.id const id = tables.value?.find((t) => t.title === tab.title)?.id
if (!id) return -1
tab.id = id as string tab.id = id as string
let index = tabs.value.findIndex((t) => t.id === tab.id) let index = tabs.value.findIndex((t) => t.id === tab.id)
@ -108,7 +110,14 @@ export function useTabs() {
const updateTab = (key: number | Partial<TabItem>, newTabItemProps: Partial<TabItem>) => { const updateTab = (key: number | Partial<TabItem>, newTabItemProps: Partial<TabItem>) => {
const tab = typeof key === 'number' ? tabs.value[key] : tabs.value.find(getPredicate(key)) const tab = typeof key === 'number' ? tabs.value[key] : tabs.value.find(getPredicate(key))
if (tab) { if (tab) {
const isActive = tabs.value.indexOf(tab) === activeTabIndex.value
Object.assign(tab, newTabItemProps) Object.assign(tab, newTabItemProps)
if (isActive && tab.title)
router.replace({
params: {
title: tab.title,
},
})
} }
} }

6
packages/nc-gui-v2/composables/useViewColumns.ts

@ -99,10 +99,12 @@ export function useViewColumns(
}, },
set(v) { set(v) {
if (view?.value?.id) { if (view?.value?.id) {
$api.dbView.update(view.value.id, { $api.dbView
.update(view.value.id, {
// todo: update swagger // todo: update swagger
show_system_fields: v, show_system_fields: v,
} as any) } as any)
.finally(() => reloadData?.())
;(view.value as any).show_system_fields = v ;(view.value as any).show_system_fields = v
} }
}, },
@ -133,7 +135,7 @@ export function useViewColumns(
) { ) {
return false return false
} }
return c.show return c.show && metaColumnById?.value?.[c.fk_column_id!]
}) })
?.sort((a, b) => a.order - b.order) ?.sort((a, b) => a.order - b.order)
?.map((c) => metaColumnById?.value?.[c.fk_column_id!]) || []) as ColumnType[] ?.map((c) => metaColumnById?.value?.[c.fk_column_id!]) || []) as ColumnType[]

2
packages/nc-gui-v2/composables/useViewData.ts

@ -85,7 +85,7 @@ export function useViewData(
.map((c) => row[c.title as string]) .map((c) => row[c.title as string])
.join('___') as string .join('___') as string
return $api.dbViewRow.update( return await $api.dbViewRow.update(
NOCO, NOCO,
project?.value.id as string, project?.value.id as string,
meta?.value.id as string, meta?.value.id as string,

19
packages/nc-gui-v2/composables/useViewFilters.ts

@ -43,19 +43,24 @@ export function useViewFilters(
} }
const deleteFilter = async (filter: FilterType & { status: string }, i: number) => { const deleteFilter = async (filter: FilterType & { status: string }, i: number) => {
// if shared or sync permission not allowed simply remove it from array
if (shared || !isUIAllowed('filterSync')) { if (shared || !isUIAllowed('filterSync')) {
const _filters = unref(filters.value) filters.value.splice(i, 1)
_filters.splice(i, 1) reloadData?.()
filters.value = _filters } else {
} else if (filter.id) { if (filter.id) {
// if auto-apply disabled mark it as disabled
if (!autoApply?.value) { if (!autoApply?.value) {
filter.status = 'delete' filter.status = 'delete'
// if auto-apply enabled invoke delete api and remove from array
} else { } else {
await $api.dbTableFilter.delete(filter.id) await $api.dbTableFilter.delete(filter.id)
const _filters = unref(filters.value)
_filters.splice(i, 1)
filters.value = _filters
reloadData?.() reloadData?.()
filters.value.splice(i, 1)
}
// if not synced yet remove it from array
} else {
filters.value.splice(i, 1)
} }
} }
} }

22
packages/nc-gui-v2/composables/useVirtualCell.ts

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

2
packages/nc-gui-v2/context/index.ts

@ -8,7 +8,7 @@ import type { TabItem } from '~/composables/useTabs'
export const EditEnabledInj: InjectionKey<boolean> = Symbol('edit-enabled') export const EditEnabledInj: InjectionKey<boolean> = Symbol('edit-enabled')
export const ActiveCellInj: InjectionKey<Ref<boolean>> = Symbol('active-cell') export const ActiveCellInj: InjectionKey<Ref<boolean>> = Symbol('active-cell')
export const RowInj: InjectionKey<Ref<Row>> = Symbol('row') export const RowInj: InjectionKey<Ref<Row>> = Symbol('row')
export const ColumnInj: InjectionKey<ColumnType & { meta: any }> = Symbol('column-injection') export const ColumnInj: InjectionKey<Ref<ColumnType & { meta: any }>> = Symbol('column-injection')
export const MetaInj: InjectionKey<ComputedRef<TableType>> = Symbol('meta-injection') export const MetaInj: InjectionKey<ComputedRef<TableType>> = Symbol('meta-injection')
export const TabMetaInj: InjectionKey<ComputedRef<TabItem>> = Symbol('tab-meta-injection') export const TabMetaInj: InjectionKey<ComputedRef<TabItem>> = Symbol('tab-meta-injection')
export const PaginationDataInj: InjectionKey<ReturnType<typeof useViewData>['paginationData']> = export const PaginationDataInj: InjectionKey<ReturnType<typeof useViewData>['paginationData']> =

3
packages/nc-gui-v2/pages/nc/[projectId]/index.vue

@ -4,9 +4,10 @@ import { TabType } from '~/composables'
const route = useRoute() const route = useRoute()
const { loadProject, loadTables } = useProject(route.params.projectId as string) const { loadProject, loadTables } = useProject(route.params.projectId as string)
const { addTab } = useTabs() const { addTab, clearTabs } = useTabs()
const { $state } = useNuxtApp() const { $state } = useNuxtApp()
clearTabs()
if (!route.params.type) { if (!route.params.type) {
addTab({ type: TabType.AUTH, title: 'Team & Auth' }) addTab({ type: TabType.AUTH, title: 'Team & Auth' })
} }

4
packages/nc-gui-v2/pages/nc/[projectId]/index/index.vue

@ -162,4 +162,8 @@ function openQuickImportDialog(type: string) {
:deep(.ant-menu-submenu::after) { :deep(.ant-menu-submenu::after) {
@apply !border-none; @apply !border-none;
} }
:deep(.ant-tabs-nav-add) {
@apply !hidden;
}
</style> </style>

5
scripts/sdk/swagger.json

@ -6778,7 +6778,7 @@
} }
}, },
"Formula": { "Formula": {
"title": "Lookup", "title": "Formula",
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "id": {
@ -6796,6 +6796,9 @@
"formula": { "formula": {
"type": "string" "type": "string"
}, },
"formula_raw": {
"type": "string"
},
"deleted": { "deleted": {
"type": "string" "type": "string"
}, },

Loading…
Cancel
Save