Browse Source

Merge pull request #5271 from nocodb/5260-ui-bug-fixes-and-enhancement

UI enhancement and fixes
pull/5291/head
Raju Udava 2 years ago committed by GitHub
parent
commit
0cfc630b72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/components/cell/Checkbox.vue
  2. 2
      packages/nc-gui/components/cell/ClampedText.vue
  3. 1
      packages/nc-gui/components/cell/Currency.vue
  4. 4
      packages/nc-gui/components/cell/Text.vue
  5. 5
      packages/nc-gui/components/cell/TextArea.vue
  6. 10
      packages/nc-gui/components/smartsheet/Cell.vue
  7. 77
      packages/nc-gui/components/smartsheet/expanded-form/Header.vue
  8. 19
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  9. 13
      packages/nc-gui/components/smartsheet/toolbar/ColumnFilter.vue
  10. 26
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  11. 1
      packages/nc-gui/components/webhook/Editor.vue
  12. 4
      packages/nc-gui/composables/useExpandedFormStore.ts
  13. 17
      tests/playwright/pages/Dashboard/ExpandedForm/index.ts
  14. 3
      tests/playwright/pages/Dashboard/Grid/index.ts
  15. 7
      tests/playwright/pages/Dashboard/common/Cell/index.ts

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

@ -89,7 +89,7 @@ useSelectedCellKeyupListener(active, (e) => {
<style scoped lang="scss"> <style scoped lang="scss">
.nc-cell-hover-show { .nc-cell-hover-show {
opacity: 0; opacity: 0.3;
transition: 0.3s opacity; transition: 0.3s opacity;
&:hover { &:hover {

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

@ -29,7 +29,7 @@ onMounted(() => {
--> -->
<text-clamp <text-clamp
:key="`clamp-${key}-${props.value?.toString().length || 0}`" :key="`clamp-${key}-${props.value?.toString().length || 0}`"
class="w-full h-full break-all" class="w-full h-full break-word"
:text="`${props.value || ' '}`" :text="`${props.value || ' '}`"
:max-lines="props.lines" :max-lines="props.lines"
/> />

1
packages/nc-gui/components/cell/Currency.vue

@ -81,6 +81,7 @@ onMounted(() => {
@keydown.delete.stop @keydown.delete.stop
@selectstart.capture.stop @selectstart.capture.stop
@mousedown.stop @mousedown.stop
@contextmenu.stop
/> />
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span> <span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>

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

@ -14,6 +14,8 @@ const { showNull } = useGlobal()
const editEnabled = inject(EditModeInj) const editEnabled = inject(EditModeInj)
const rowHeight = inject(RowHeightInj)
const readonly = inject(ReadonlyInj, ref(false)) const readonly = inject(ReadonlyInj, ref(false))
const vModel = useVModel(props, 'modelValue', emits) const vModel = useVModel(props, 'modelValue', emits)
@ -42,5 +44,5 @@ const focus: VNodeRef = (el) => {
<span v-else-if="vModel === null && showNull" class="nc-null">NULL</span> <span v-else-if="vModel === null && showNull" class="nc-null">NULL</span>
<LazyCellClampedText v-else :value="vModel" :lines="1" /> <LazyCellClampedText v-else :value="vModel" :lines="rowHeight" />
</template> </template>

5
packages/nc-gui/components/cell/TextArea.vue

@ -45,3 +45,8 @@ const focus: VNodeRef = (el) => (el as HTMLTextAreaElement)?.focus()
<span v-else>{{ vModel }}</span> <span v-else>{{ vModel }}</span>
</template> </template>
<style>
textarea:focus {
box-shadow: none;
}
</style>

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

@ -139,6 +139,15 @@ const isNumericField = computed(() => {
isDuration(column.value) isDuration(column.value)
) )
}) })
// disable contexxtmenu event propagation when cell is in
// editable state and typable (e.g. text area)
// this is to prevent the custom grid view context menu from opening
const onContextmenu = (e: MouseEvent) => {
if (props.editEnabled && isTypableInputColumn(column.value)) {
e.stopPropagation()
}
}
</script> </script>
<template> <template>
@ -151,6 +160,7 @@ const isNumericField = computed(() => {
]" ]"
@keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="syncAndNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)" @keydown.shift.enter.exact="syncAndNavigate(NavigateDir.PREV, $event)"
@contextmenu="onContextmenu"
> >
<template v-if="column"> <template v-if="column">
<LazyCellTextArea v-if="isTextArea(column)" v-model="vModel" /> <LazyCellTextArea v-if="isTextArea(column)" v-model="vModel" />

77
packages/nc-gui/components/smartsheet/expanded-form/Header.vue

@ -101,17 +101,6 @@ const onConfirmDeleteRowClick = async () => {
</h5> </h5>
<div class="flex-1" /> <div class="flex-1" />
<a-tooltip placement="bottom">
<template #title>
<div class="text-center w-full">{{ $t('general.reload') }}</div>
</template>
<mdi-reload
v-if="!isNew"
class="nc-icon-transition cursor-pointer select-none text-gray-500 mx-1 min-w-4"
@click="loadRow"
/>
</a-tooltip>
<a-tooltip placement="bottom"> <a-tooltip placement="bottom">
<template #title> <template #title>
<!-- todo: i18n --> <!-- todo: i18n -->
@ -137,32 +126,6 @@ const onConfirmDeleteRowClick = async () => {
/> />
</a-tooltip> </a-tooltip>
<a-tooltip v-if="!isSqlView" placement="bottom">
<!-- Duplicate row -->
<template #title>
<div class="text-center w-full">{{ $t('activity.duplicateRow') }}</div>
</template>
<MdiContentCopy
v-if="isUIAllowed('xcDatatableEditable') && !isNew"
v-e="['c:row-expand:duplicate']"
class="nc-icon-transition cursor-pointer select-none nc-duplicate-row text-gray-500 mx-1 min-w-4"
@click="!isNew && emit('duplicateRow')"
/>
</a-tooltip>
<a-tooltip v-if="!isSqlView" placement="bottom">
<!-- Delete row -->
<template #title>
<div class="text-center w-full">{{ $t('activity.deleteRow') }}</div>
</template>
<MdiDeleteOutline
v-if="isUIAllowed('xcDatatableEditable') && !isNew"
v-e="['c:row-expand:delete']"
class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4"
@click="!isNew && onDeleteRowClick()"
/>
</a-tooltip>
<a-dropdown-button class="nc-expand-form-save-btn" type="primary" :disabled="!isUIAllowed('tableRowUpdate')" @click="save"> <a-dropdown-button class="nc-expand-form-save-btn" type="primary" :disabled="!isUIAllowed('tableRowUpdate')" @click="save">
<template #icon><MdiMenuDown /></template> <template #icon><MdiMenuDown /></template>
@ -192,17 +155,39 @@ const onConfirmDeleteRowClick = async () => {
</div> </div>
</a-dropdown-button> </a-dropdown-button>
<a-tooltip placement="bottom"> <a-dropdown>
<!-- Close --> <MdiDotsVertical class="nc-icon-transition" />
<template #title> <template #overlay>
<div class="text-center w-full">{{ $t('general.close') }}</div> <a-menu>
</template> <a-menu-item v-if="!isNew" @click="loadRow">
<div v-e="['c:row-expand:reload']" class="py-2 flex gap-2 items-center">
<mdi-reload class="nc-icon-transition cursor-pointer select-none text-gray-500 mx-1 min-w-4" />
{{ $t('general.reload') }}
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && emit('duplicateRow')">
<div v-e="['c:row-expand:duplicate']" class="py-2 flex gap-2 a">
<MdiContentCopy class="nc-icon-transition cursor-pointer select-none nc-duplicate-row text-gray-500 mx-1 min-w-4" />
{{ $t('activity.duplicateRow') }}
</div>
</a-menu-item>
<a-menu-item v-if="isUIAllowed('xcDatatableEditable') && !isNew" @click="!isNew && onDeleteRowClick()">
<div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center">
<MdiDeleteOutline class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4" />
{{ $t('activity.deleteRow') }}
</div>
</a-menu-item>
<a-menu-item @click="emit('cancel')">
<div v-e="['c:row-expand:delete']" class="py-2 flex gap-2 items-center">
<MdiCloseCircleOutline <MdiCloseCircleOutline
class="nc-icon-transition cursor-pointer select-none nc-close-form text-gray-500 mx-1 min-w-4" class="nc-icon-transition cursor-pointer select-none nc-delete-row text-gray-500 mx-1 min-w-4"
@click="emit('cancel')"
/> />
</a-tooltip> {{ $t('general.close') }}
</div>
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-modal v-model:visible="showDeleteRowModal" title="Delete row?" @ok="onConfirmDeleteRowClick"> <a-modal v-model:visible="showDeleteRowModal" title="Delete row?" @ok="onConfirmDeleteRowClick">
<p>Are you sure you want to delete this row?</p> <p>Are you sure you want to delete this row?</p>
</a-modal> </a-modal>

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

@ -141,8 +141,22 @@ const onDuplicateRow = () => {
} }
const onNext = async () => { const onNext = async () => {
if (changedColumns.value.size > 0) {
await Modal.confirm({
title: 'Do you want to save the changes?',
okText: 'Save',
cancelText: 'Discard',
onOk: async () => {
await save() await save()
emits('next') emits('next')
},
onCancel: () => {
emits('next')
},
})
} else {
emits('next')
}
} }
const reloadParentRowHook = inject(ReloadRowDataHookInj, createEventHook()) const reloadParentRowHook = inject(ReloadRowDataHookInj, createEventHook())
@ -196,7 +210,7 @@ useActiveKeyupListener(
emits('prev') emits('prev')
} else if (e.key === 'ArrowRight') { } else if (e.key === 'ArrowRight') {
e.stopPropagation() e.stopPropagation()
emits('next') onNext()
} }
// on alt + s save record // on alt + s save record
else if (e.code === 'KeyS') { else if (e.code === 'KeyS') {
@ -319,7 +333,8 @@ export default {
:ref="i ? null : (el) => (cellWrapperEl = el)" :ref="i ? null : (el) => (cellWrapperEl = el)"
class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2 relative" class="!bg-white rounded px-1 min-h-[35px] flex items-center mt-2 relative"
> >
<LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row" :column="col" /> <LazySmartsheetVirtualCell v-if="isVirtualCol(col)" v-model="row.row[col.title]" :row="row"
:column="col" />
<LazySmartsheetCell <LazySmartsheetCell
v-else v-else

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

@ -198,10 +198,15 @@ defineExpose({
<template> <template>
<div <div
class="p-4 menu-filter-dropdown bg-gray-50 !border mt-4" class="p-4 menu-filter-dropdown bg-gray-50 !border"
:class="{ 'shadow min-w-[430px] max-h-[max(80vh,500px)] overflow-auto': !nested, 'border-1 w-full': nested }" :class="{ 'min-w-[430px]': filters.length, 'shadow max-h-[max(80vh,500px)] overflow-auto': !nested, 'border-1 w-full': nested }"
>
<div
v-if="filters && filters.length"
class="nc-filter-grid mb-2"
:class="{ 'max-h-420px overflow-y-auto': !nested }"
@click.stop
> >
<div v-if="filters && filters.length" class="nc-filter-grid mb-2" @click.stop>
<template v-for="(filter, i) in filters" :key="i"> <template v-for="(filter, i) in filters" :key="i">
<template v-if="filter.status !== 'delete'"> <template v-if="filter.status !== 'delete'">
<template v-if="filter.is_group"> <template v-if="filter.is_group">
@ -230,7 +235,7 @@ defineExpose({
</a-select> </a-select>
</div> </div>
<span class="col-span-3" /> <span class="col-span-3" />
<div class="col-span-5"> <div class="col-span-6">
<LazySmartsheetToolbarColumnFilter <LazySmartsheetToolbarColumnFilter
v-if="filter.id || filter.children" v-if="filter.id || filter.children"
:key="filter.id ?? i" :key="filter.id ?? i"

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

@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { nextTick } from '@vue/runtime-core'
import type { ColumnType } from 'nocodb-sdk' import type { ColumnType } from 'nocodb-sdk'
import { import {
ActiveViewInj, ActiveViewInj,
@ -22,7 +23,17 @@ const reloadDataHook = inject(ReloadViewDataHookInj)
const { eventBus } = useSmartsheetStoreOrThrow() const { eventBus } = useSmartsheetStoreOrThrow()
const { sorts, saveOrUpdate, loadSorts, addSort, deleteSort } = useViewSorts(view, () => reloadDataHook?.trigger()) const { sorts, saveOrUpdate, loadSorts, addSort: _addSort, deleteSort } = useViewSorts(view, () => reloadDataHook?.trigger())
const removeIcon = ref<HTMLElement>()
const addSort = () => {
_addSort()
nextTick(() => {
console.log(removeIcon.value)
removeIcon.value?.[removeIcon.value?.length - 1]?.$el?.scrollIntoView()
})
}
eventBus.on((event) => { eventBus.on((event) => {
if (event === SmartsheetStoreEvents.SORT_RELOAD) { if (event === SmartsheetStoreEvents.SORT_RELOAD) {
@ -75,12 +86,18 @@ useMenuCloseOnEsc(open)
</div> </div>
<template #overlay> <template #overlay>
<div <div
class="bg-gray-50 p-6 shadow-lg menu-filter-dropdown min-w-[400px] max-h-[max(80vh,500px)] overflow-auto !border" :class="{ ' min-w-[400px]': sorts.length }"
class="bg-gray-50 p-6 shadow-lg menu-filter-dropdown max-h-[max(80vh,500px)] overflow-auto !border"
data-testid="nc-sorts-menu" data-testid="nc-sorts-menu"
> >
<div v-if="sorts?.length" class="sort-grid mb-2" @click.stop> <div v-if="sorts?.length" class="sort-grid mb-2 max-h-420px overflow-y-auto" @click.stop>
<template v-for="(sort, i) of sorts" :key="i"> <template v-for="(sort, i) of sorts" :key="i">
<MdiCloseBox class="nc-sort-item-remove-btn text-grey self-center" small @click.stop="deleteSort(sort, i)" /> <MdiCloseBox
ref="removeIcon"
class="nc-sort-item-remove-btn text-grey self-center"
small
@click.stop="deleteSort(sort, i)"
/>
<LazySmartsheetToolbarFieldListAutoCompleteDropdown <LazySmartsheetToolbarFieldListAutoCompleteDropdown
v-model="sort.fk_column_id" v-model="sort.fk_column_id"
@ -92,6 +109,7 @@ useMenuCloseOnEsc(open)
/> />
<a-select <a-select
ref=""
v-model:value="sort.direction" v-model:value="sort.direction"
class="shrink grow-0 nc-sort-dir-select !text-xs" class="shrink grow-0 nc-sort-dir-select !text-xs"
:label="$t('labels.operation')" :label="$t('labels.operation')"

1
packages/nc-gui/components/webhook/Editor.vue

@ -651,6 +651,7 @@ onMounted(loadPluginList)
</a-checkbox> </a-checkbox>
<LazySmartsheetToolbarColumnFilter <LazySmartsheetToolbarColumnFilter
class="mt-4"
v-if="hook.condition" v-if="hook.condition"
ref="filterRef" ref="filterRef"
:auto-save="false" :auto-save="false"

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

@ -30,13 +30,13 @@ const [useProvideExpandedFormStore, useExpandedFormStore] = useInjectionState((m
const { t } = useI18n() const { t } = useI18n()
const commentsOnly = ref(false) const commentsOnly = ref(true)
const commentsAndLogs = ref<any[]>([]) const commentsAndLogs = ref<any[]>([])
const comment = ref('') const comment = ref('')
const commentsDrawer = ref(false) const commentsDrawer = ref(true)
const saveRowAndStay = ref(0) const saveRowAndStay = ref(0)

17
tests/playwright/pages/Dashboard/ExpandedForm/index.ts

@ -24,16 +24,25 @@ export class ExpandedFormPage extends BasePage {
return this.dashboard.get().locator(`.nc-drawer-expanded-form`); return this.dashboard.get().locator(`.nc-drawer-expanded-form`);
} }
async clickDuplicateRow() { async click3DotsMenu(menuItem: string) {
await this.duplicateRowButton.click(); await this.get().locator('.nc-icon-transition.ant-dropdown-trigger').last().click();
// add delay; wait for the menu to appear
await this.rootPage.waitForTimeout(500);
const popUpMenu = await this.rootPage.locator('.ant-dropdown');
await popUpMenu.locator(`.ant-dropdown-menu-item:has-text("${menuItem}")`).click();
}
async clickDuplicateRow() {
await this.click3DotsMenu('Duplicate Row');
// wait for loader to disappear // wait for loader to disappear
// await this.dashboard.waitForLoaderToDisappear(); // await this.dashboard.waitForLoaderToDisappear();
await this.rootPage.waitForTimeout(2000); await this.rootPage.waitForTimeout(2000);
} }
async clickDeleteRow() { async clickDeleteRow() {
await this.deleteRowButton.click(); await this.click3DotsMenu('Delete Row');
await this.rootPage.locator('.ant-btn-primary:has-text("OK")').click(); await this.rootPage.locator('.ant-btn-primary:has-text("OK")').click();
} }
@ -142,7 +151,7 @@ export class ExpandedFormPage extends BasePage {
} }
async close() { async close() {
await this.get().locator('.nc-close-form').last().click(); await this.click3DotsMenu('Close');
} }
async openChildCard(param: { column: string; title: string }) { async openChildCard(param: { column: string; title: string }) {

3
tests/playwright/pages/Dashboard/Grid/index.ts

@ -283,6 +283,9 @@ export class GridPage extends BasePage {
}); });
await expect(await cell.locator('input')).toBeVisible(); await expect(await cell.locator('input')).toBeVisible();
// press escape to exit edit mode
await cell.press('Escape');
// right click menu // right click menu
await this.get().locator(`td[data-testid="cell-${columnHeader}-0"]`).click({ await this.get().locator(`td[data-testid="cell-${columnHeader}-0"]`).click({
button: 'right', button: 'right',

7
tests/playwright/pages/Dashboard/common/Cell/index.ts

@ -289,8 +289,11 @@ export class CellPageObject extends BasePage {
// editable cell // editable cell
await cell.dblclick(); await cell.dblclick();
await expect(await cell.locator(`input`)).toHaveCount(param.role === 'creator' || param.role === 'editor' ? 1 : 0); await expect(await cell.locator(`input`)).toHaveCount(param.role === 'creator' || param.role === 'editor' ? 1 : 0);
// right click context menu
await cell.click({ button: 'right' }); // press escape to close the input
await cell.press('Escape');
await cell.click({ button: 'right', clickCount: 1 });
await expect(await this.rootPage.locator(`.nc-dropdown-grid-context-menu:visible`)).toHaveCount( await expect(await this.rootPage.locator(`.nc-dropdown-grid-context-menu:visible`)).toHaveCount(
param.role === 'creator' || param.role === 'editor' ? 1 : 0 param.role === 'creator' || param.role === 'editor' ? 1 : 0
); );

Loading…
Cancel
Save