Browse Source

Merge pull request #4968 from nocodb/fix/misc

Miscellaneous bug fixes and improvements
pull/4999/head
Raju Udava 2 years ago committed by GitHub
parent
commit
7514ad6de3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components/cell/Url.vue
  2. 4
      packages/nc-gui/components/dlg/TableRename.vue
  3. 25
      packages/nc-gui/components/smartsheet/Cell.vue
  4. 16
      packages/nc-gui/components/smartsheet/Grid.vue
  5. 7
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  6. 103
      packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue
  7. 7
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  8. 6
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  9. 41
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  10. 8
      packages/nc-gui/composables/useMultiSelect/index.ts
  11. 5
      packages/nc-gui/composables/useProject.ts
  12. 38
      packages/nc-gui/composables/useSelectedCellKeyupListener/index.ts
  13. 2
      packages/nc-gui/pages/index/index.vue
  14. 16
      packages/nc-gui/pages/index/index/create.vue
  15. 1
      packages/nc-gui/utils/columnUtils.ts

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

@ -79,7 +79,7 @@ watch(
v-if="editEnabled"
:ref="focus"
v-model="vModel"
class="outline-none text-sm w-full px-2"
class="outline-none text-sm w-full px-2 bg-transparent h-full"
@blur="editEnabled = false"
@keydown.down.stop
@keydown.left.stop

4
packages/nc-gui/components/dlg/TableRename.vue

@ -80,9 +80,7 @@ const validators = computed(() => {
return reject(new Error('Leading or trailing whitespace not allowed in table name'))
}
if (
!(tables?.value || []).every(
(t) => t.id === tableMeta.id || t.table_name.toLowerCase() !== (value || '').toLowerCase(),
)
!(tables?.value || []).every((t) => t.id === tableMeta.id || t.title.toLowerCase() !== (value || '').toLowerCase())
) {
return reject(new Error('Duplicate table alias'))
}

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

@ -76,6 +76,8 @@ provide(ReadonlyInj, readOnly)
const isForm = inject(IsFormInj, ref(false))
const isGrid = inject(IsGridInj, ref(false))
const isPublic = inject(IsPublicInj, ref(false))
const isLocked = inject(IsLockedInj, ref(false))
@ -123,14 +125,26 @@ const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => {
if (!isForm.value) e.stopImmediatePropagation()
}
const isNumericField = computed(() => {
return (
isInt(column.value, abstractType.value) ||
isFloat(column.value, abstractType.value) ||
isDecimal(column.value) ||
isCurrency(column.value) ||
isPercent(column.value) ||
isDuration(column.value)
)
})
</script>
<template>
<div
class="nc-cell w-full"
class="nc-cell w-full h-full"
:class="[
`nc-cell-${(column?.uidt || 'default').toLowerCase()}`,
{ 'text-blue-600': isPrimary(column) && !props.virtual && !isForm },
{ 'nc-grid-numeric-cell': isGrid && !isForm && isNumericField },
]"
@keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)"
@ -167,3 +181,12 @@ const syncAndNavigate = (dir: NavigateDir, e: KeyboardEvent) => {
</template>
</div>
</template>
<style scoped lang="scss">
.nc-grid-numeric-cell {
@apply text-right;
:deep(input) {
@apply text-right;
}
}
</style>

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

@ -97,7 +97,6 @@ const contextMenuTarget = ref<{ row: number; col: number } | null>(null)
const expandedFormDlg = ref(false)
const expandedFormRow = ref<Row>()
const expandedFormRowState = ref<Record<string, any>>()
const tbodyEl = ref<HTMLElement>()
const gridWrapper = ref<HTMLElement>()
const tableHead = ref<HTMLElement>()
@ -182,6 +181,8 @@ const {
clearSelectedRange,
copyValue,
isCellActive,
tbodyEl,
resetSelectedRange,
} = useMultiSelect(
meta,
fields,
@ -279,6 +280,17 @@ const {
if (isAddingEmptyRowAllowed) {
$e('c:shortcut', { key: 'ALT + R' })
addEmptyRow()
activeCell.row = data.value.length - 1
activeCell.col = 0
resetSelectedRange()
makeEditable(data.value[activeCell.row], fields.value[activeCell.col])
nextTick(() => {
;(
document.querySelector('td.cell.active')?.querySelector('input,textarea') as
| HTMLInputElement
| HTMLTextAreaElement
)?.focus()
})
}
break
}
@ -702,7 +714,7 @@ const rowHeight = computed(() => {
@contextmenu="showContextMenu"
>
<thead ref="tableHead">
<tr class="nc-grid-header border-1 bg-gray-100 sticky top[-1px]">
<tr class="nc-grid-header border-1 bg-gray-100 sticky top[-1px] !z-4">
<th data-testid="grid-id-column">
<div class="w-full h-full bg-gray-100 flex min-w-[70px] pl-5 pr-1 items-center" data-testid="nc-check-all">
<template v-if="!readOnly">

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

@ -5,6 +5,7 @@ import {
CellValueInj,
ColumnInj,
IsFormInj,
IsGridInj,
RowInj,
inject,
isBarcode,
@ -40,7 +41,10 @@ provide(ActiveCellInj, active)
provide(RowInj, row)
provide(CellValueInj, toRef(props, 'modelValue'))
const isGrid = inject(IsGridInj, ref(false))
const isForm = inject(IsFormInj, ref(false))
function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
emit('navigate', dir)
@ -50,7 +54,8 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
<template>
<div
class="nc-virtual-cell w-full"
class="nc-virtual-cell w-full flex items-center"
:class="{ 'text-right justify-end': isGrid && !isForm && isRollup(column) }"
@keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
>

103
packages/nc-gui/components/smartsheet/column/AdvancedOptions.vue

@ -4,6 +4,7 @@ import { computed, useColumnCreateStoreOrThrow, useVModel } from '#imports'
const props = defineProps<{
value: any
advancedDbOptions: boolean
}>()
const emit = defineEmits(['update:value'])
@ -40,62 +41,64 @@ vModel.value.au = !!vModel.value.au
<template>
<div class="p-4 border-[2px] radius-1 border-grey w-full flex flex-col gap-2">
<div class="flex justify-between w-full gap-1">
<a-form-item label="NN">
<a-checkbox
v-model:checked="vModel.rqd"
:disabled="vModel.pk || !sqlUi.columnEditable(vModel)"
class="nc-column-checkbox-NN"
@change="onAlter"
/>
</a-form-item>
<a-form-item label="PK">
<a-checkbox
v-model:checked="vModel.pk"
:disabled="!sqlUi.columnEditable(vModel)"
class="nc-column-checkbox-PK"
@change="onAlter"
/>
<template v-if="props.advancedDbOptions">
<div class="flex justify-between w-full gap-1">
<a-form-item label="NN">
<a-checkbox
v-model:checked="vModel.rqd"
:disabled="vModel.pk || !sqlUi.columnEditable(vModel)"
class="nc-column-checkbox-NN"
@change="onAlter"
/>
</a-form-item>
<a-form-item label="PK">
<a-checkbox
v-model:checked="vModel.pk"
:disabled="!sqlUi.columnEditable(vModel)"
class="nc-column-checkbox-PK"
@change="onAlter"
/>
</a-form-item>
<a-form-item label="AI">
<a-checkbox
v-model:checked="vModel.ai"
:disabled="sqlUi.colPropUNDisabled(vModel) || !sqlUi.columnEditable(vModel)"
class="nc-column-checkbox-AI"
@change="onAlter"
/>
</a-form-item>
<a-form-item label="UN" :disabled="sqlUi.colPropUNDisabled(vModel) || !sqlUi.columnEditable(vModel)" @change="onAlter">
<a-checkbox v-model:checked="vModel.un" class="nc-column-checkbox-UN" />
</a-form-item>
<a-form-item label="AU" :disabled="sqlUi.colPropAuDisabled(vModel) || !sqlUi.columnEditable(vModel)" @change="onAlter">
<a-checkbox v-model:checked="vModel.au" class="nc-column-checkbox-AU" />
</a-form-item>
</div>
<a-form-item :label="$t('labels.databaseType')" v-bind="validateInfos.dt">
<a-select v-model:value="vModel.dt" dropdown-class-name="nc-dropdown-db-type" @change="onDataTypeChange">
<a-select-option v-for="type in dataTypes" :key="type" :value="type">
{{ type }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="AI">
<a-checkbox
v-model:checked="vModel.ai"
:disabled="sqlUi.colPropUNDisabled(vModel) || !sqlUi.columnEditable(vModel)"
class="nc-column-checkbox-AI"
@change="onAlter"
<a-form-item v-if="!hideLength" :label="$t('labels.lengthValue')">
<a-input
v-model:value="vModel.dtxp"
:disabled="sqlUi.getDefaultLengthIsDisabled(vModel.dt) || !sqlUi.columnEditable(vModel)"
@input="onAlter"
/>
</a-form-item>
<a-form-item label="UN" :disabled="sqlUi.colPropUNDisabled(vModel) || !sqlUi.columnEditable(vModel)" @change="onAlter">
<a-checkbox v-model:checked="vModel.un" class="nc-column-checkbox-UN" />
</a-form-item>
<a-form-item label="AU" :disabled="sqlUi.colPropAuDisabled(vModel) || !sqlUi.columnEditable(vModel)" @change="onAlter">
<a-checkbox v-model:checked="vModel.au" class="nc-column-checkbox-AU" />
<a-form-item v-if="sqlUi.showScale(vModel)" label="Scale">
<a-input v-model:value="vModel.dtxs" :disabled="!sqlUi.columnEditable(vModel)" @input="onAlter" />
</a-form-item>
</div>
<a-form-item :label="$t('labels.databaseType')" v-bind="validateInfos.dt">
<a-select v-model:value="vModel.dt" dropdown-class-name="nc-dropdown-db-type" @change="onDataTypeChange">
<a-select-option v-for="type in dataTypes" :key="type" :value="type">
{{ type }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item v-if="!hideLength" :label="$t('labels.lengthValue')">
<a-input
v-model:value="vModel.dtxp"
:disabled="sqlUi.getDefaultLengthIsDisabled(vModel.dt) || !sqlUi.columnEditable(vModel)"
@input="onAlter"
/>
</a-form-item>
<a-form-item v-if="sqlUi.showScale(vModel)" label="Scale">
<a-input v-model:value="vModel.dtxs" :disabled="!sqlUi.columnEditable(vModel)" @input="onAlter" />
</a-form-item>
</template>
<a-form-item :label="$t('placeholder.defaultValue')">
<a-textarea v-model:value="vModel.cdf" auto-size @input="onAlter(2, true)" />

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

@ -51,6 +51,8 @@ const reloadDataTrigger = inject(ReloadViewDataHookInj)
const advancedOptions = ref(false)
const advancedDbOptions = ref(false)
const columnToValidate = [UITypes.Email, UITypes.URL, UITypes.PhoneNumber]
const onlyNameUpdateOnEditColumns = [UITypes.LinkToAnotherRecord, UITypes.Lookup, UITypes.Rollup]
@ -196,8 +198,9 @@ useEventListener('keydown', (e: KeyboardEvent) => {
<div
v-if="!isVirtualCol(formState.uidt)"
class="text-xs cursor-pointer text-grey nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
class="text-xs cursor-pointer text-gray-400 nc-more-options mb-1 mt-4 flex items-center gap-1 justify-end"
@click="advancedOptions = !advancedOptions"
@dblclick="advancedDbOptions = !advancedDbOptions"
>
{{ advancedOptions ? $t('general.hideAll') : $t('general.showMore') }}
<component :is="advancedOptions ? MdiMinusIcon : MdiPlusIcon" />
@ -220,7 +223,7 @@ useEventListener('keydown', (e: KeyboardEvent) => {
v-model:value="formState"
/>
<LazySmartsheetColumnAdvancedOptions v-model:value="formState" />
<LazySmartsheetColumnAdvancedOptions v-model:value="formState" :advanced-db-options="advancedDbOptions" />
</div>
</Transition>

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

@ -21,7 +21,7 @@ const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, validateInfos, isPg, isMysql } = useColumnCreateStoreOrThrow()
let options = $ref<Option[]>([])
let options = $ref<(Option & { status?: 'remove' })[]>([])
let renderedOptions = $ref<(Option & { status?: 'remove' })[]>([])
let savedDefaultOption = $ref<Option | null>(null)
let savedCdf = $ref<string | null>(null)
@ -41,13 +41,15 @@ const validators = {
validator: (_: any, _opt: any) => {
return new Promise<void>((resolve, reject) => {
for (const opt of options) {
if ((opt as any).status === 'remove') continue
if (!opt.title.length) {
return reject(new Error("Select options can't be null"))
}
if (vModel.value.uidt === UITypes.MultiSelect && opt.title.includes(',')) {
return reject(new Error("MultiSelect columns can't have commas(',')"))
}
if (options.filter((el) => el.title === opt.title).length !== 1) {
if (options.filter((el) => el.title === opt.title && (el as any).status !== 'remove').length > 1) {
return reject(new Error("Select options can't have duplicates"))
}
}

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

@ -9,6 +9,7 @@ import {
projectRoleTagColors,
projectRoles,
ref,
useActiveKeyupListener,
useCopy,
useDashboard,
useI18n,
@ -44,7 +45,7 @@ const { copy } = useCopy()
const { dashboardUrl } = $(useDashboard())
const usersData = $ref<Users>({ emails: undefined, role: ProjectRole.Viewer, invitationToken: undefined })
let usersData = $ref<Users>({ emails: undefined, role: ProjectRole.Viewer, invitationToken: undefined })
const formRef = ref()
@ -80,6 +81,11 @@ onMounted(() => {
}
})
const close = () => {
emit('closed')
usersData = { role: ProjectRole.Viewer }
}
const saveUser = async () => {
$e('a:user:invite', { role: usersData.role })
@ -95,7 +101,7 @@ const saveUser = async () => {
project_id: project.value.id,
projectName: project.value.title,
})
emit('closed')
close()
} else {
const res = await $api.auth.projectUserAdd(project.value.id, {
roles: usersData.role,
@ -135,9 +141,28 @@ const clickInviteMore = () => {
usersData.emails = undefined
}
const emailField = (inputEl: typeof Input) => {
inputEl?.$el?.focus()
}
const emailField = ref<typeof Input>()
useActiveKeyupListener(
computed(() => show),
(e: KeyboardEvent) => {
if (e.key === 'Escape') {
close()
}
},
{ immediate: true },
)
watch(
() => show,
async (val) => {
if (val) {
await nextTick()
emailField.value?.$el?.focus()
}
},
{ immediate: true },
)
</script>
<template>
@ -149,7 +174,7 @@ const emailField = (inputEl: typeof Input) => {
:closable="false"
width="max(50vw, 44rem)"
wrap-class-name="nc-modal-invite-user-and-share-base"
@cancel="emit('closed')"
@cancel="close"
>
<div class="flex flex-col" data-testid="invite-user-and-share-base-modal">
<div class="flex flex-row justify-between items-center pb-1.5 mb-2 border-b-1 w-full">
@ -159,7 +184,7 @@ const emailField = (inputEl: typeof Input) => {
type="text"
class="!rounded-md mr-1 -mt-1.5"
data-testid="invite-user-and-share-base-modal-close-btn"
@click="emit('closed')"
@click="close"
>
<template #icon>
<MaterialSymbolsCloseRounded class="flex mx-auto" />
@ -233,7 +258,7 @@ const emailField = (inputEl: typeof Input) => {
<div class="ml-1 mb-1 text-xs text-gray-500">{{ $t('datatype.Email') }}:</div>
<a-input
:ref="emailField"
ref="emailField"
v-model:value="usersData.emails"
validate-trigger="onBlur"
:placeholder="$t('labels.email')"

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

@ -42,6 +42,8 @@ export function useMultiSelect(
) {
const meta = ref(_meta)
const tbodyEl = ref<HTMLElement>()
const { t } = useI18n()
const { copy } = useCopy()
@ -372,10 +374,12 @@ export function useMultiSelect(
}
}
const resetSelectedRange = () => selectedRange.clear()
const clearSelectedRange = selectedRange.clear.bind(selectedRange)
useEventListener(document, 'keydown', handleKeyDown)
useEventListener(document, 'mouseup', handleMouseUp)
useEventListener(tbodyEl, 'mouseup', handleMouseUp)
return {
isCellActive,
@ -386,5 +390,7 @@ export function useMultiSelect(
isCellSelected,
activeCell,
handleCellClick,
tbodyEl,
resetSelectedRange,
}
}

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

@ -86,6 +86,10 @@ const [setup, use] = useInjectionState(() => {
return getBaseType(baseId) === 'pg'
}
function isXcdbBase(baseId?: string) {
return bases.value.find((base) => base.id === baseId)?.is_meta
}
const isSharedBase = computed(() => projectType === 'base')
async function loadProjectMetaInfo(force?: boolean) {
@ -195,6 +199,7 @@ const [setup, use] = useInjectionState(() => {
reset,
isLoading,
lastOpenedViewMap,
isXcdbBase,
}
}, 'useProject')

38
packages/nc-gui/composables/useSelectedCellKeyupListener/index.ts

@ -1,21 +1,31 @@
import { isClient } from '@vueuse/core'
import type { Ref } from 'vue'
export function useSelectedCellKeyupListener(selected: Ref<boolean>, handler: (e: KeyboardEvent) => void) {
function useSelectedCellKeyupListener(
selected: Ref<boolean>,
handler: (e: KeyboardEvent) => void,
{ immediate = false }: { immediate?: boolean } = {},
) {
if (isClient) {
watch(selected, (nextVal, _, cleanup) => {
// bind listener when `selected` is truthy
if (nextVal) {
document.addEventListener('keydown', handler, true)
// if `selected` is falsy then remove the event handler
} else {
document.removeEventListener('keydown', handler, true)
}
watch(
selected,
(nextVal: boolean, _: boolean, cleanup) => {
// bind listener when `selected` is truthy
if (nextVal) {
document.addEventListener('keydown', handler, true)
// if `selected` is falsy then remove the event handler
} else {
document.removeEventListener('keydown', handler, true)
}
// cleanup is called whenever the watcher is re-evaluated or stopped
cleanup(() => {
document.removeEventListener('keydown', handler, true)
})
})
// cleanup is called whenever the watcher is re-evaluated or stopped
cleanup(() => {
document.removeEventListener('keydown', handler, true)
})
},
{ immediate },
)
}
}
export { useSelectedCellKeyupListener, useSelectedCellKeyupListener as useActiveKeyupListener }

2
packages/nc-gui/pages/index/index.vue

@ -18,7 +18,7 @@ useSidebar('nc-left-sidebar', { hasSidebar: false })
</div>
<div class="min-w-2/4 xl:max-w-2/4 w-full mx-auto">
<NuxtPage />
<NuxtPage :transition="false" />
</div>
<div

16
packages/nc-gui/pages/index/index/create.vue

@ -1,11 +1,14 @@
<script lang="ts" setup>
import type { Form } from 'ant-design-vue'
import type { Form, Input } from 'ant-design-vue'
import type { RuleObject } from 'ant-design-vue/es/form'
import type { VNodeRef } from '@vue/runtime-core'
import {
extractSdkResponseErrorMsg,
generateUniqueName,
message,
navigateTo,
nextTick,
onMounted,
projectTitleValidator,
reactive,
ref,
@ -47,7 +50,14 @@ const createProject = async () => {
}
}
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
const input: VNodeRef = ref<typeof Input>()
onMounted(async () => {
formState.title = await generateUniqueName()
await nextTick()
input.value?.$el?.focus()
input.value?.$el?.select()
})
</script>
<template>
@ -76,7 +86,7 @@ const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
@finish="createProject"
>
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules" class="m-10">
<a-input :ref="focus" v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
<a-input ref="input" v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item>
<div class="text-center">

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

@ -214,6 +214,7 @@ const isTypableInputColumn = (colOrUidt: ColumnType | UITypes) => {
UITypes.Percent,
UITypes.Duration,
UITypes.JSON,
UITypes.URL,
].includes(uidt)
}

Loading…
Cancel
Save