Browse Source

Merge branch 'develop' into test/quick-run

pull/6544/head
Raju Udava 1 year ago
parent
commit
21c2aa15da
  1. 16
      packages/nc-gui/assets/nc-icons/project.svg
  2. 12
      packages/nc-gui/assets/nc-icons/record.svg
  3. 16
      packages/nc-gui/assets/nc-icons/table.svg
  4. 3
      packages/nc-gui/components/cell/Text.vue
  5. 5
      packages/nc-gui/components/cell/TextArea.vue
  6. 4
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  7. 2
      packages/nc-gui/components/dashboard/settings/Metadata.vue
  8. 5
      packages/nc-gui/components/dashboard/settings/UIAcl.vue
  9. 2
      packages/nc-gui/components/dlg/TableDelete.vue
  10. 2
      packages/nc-gui/components/general/ProjectIcon.vue
  11. 1
      packages/nc-gui/components/general/TableIcon.vue
  12. 27
      packages/nc-gui/components/general/UserIcon.vue
  13. 3
      packages/nc-gui/components/smartsheet/Cell.vue
  14. 2
      packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue
  15. 21
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  16. 115
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  17. 15
      packages/nc-gui/components/virtual-cell/Links.vue
  18. 4
      packages/nc-gui/components/virtual-cell/components/Header.vue
  19. 168
      packages/nc-gui/components/virtual-cell/components/ListChildItems.vue
  20. 9
      packages/nc-gui/components/virtual-cell/components/ListItems.vue
  21. 3
      packages/nc-gui/lang/en.json
  22. 9
      packages/nc-gui/utils/iconUtils.ts
  23. 22
      tests/playwright/pages/Dashboard/ExpandedForm/index.ts
  24. 29
      tests/playwright/tests/db/features/expandedFormUrl.spec.ts
  25. 4
      tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

16
packages/nc-gui/assets/nc-icons/project.svg

@ -1,9 +1,9 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 1073 1073" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99294 14.8095L14.548 12.28C14.8177 12.1572 14.9569 11.9975 14.9659 11.8367H1C1.00896 11.9975 1.14826 12.1572 1.4179 12.28L6.97294 14.8095C7.53075 15.0635 8.43514 15.0635 8.99294 14.8095Z" fill="#142966"/> <mask id="mask0_1749_80944" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="94" y="40" width="885" height="993">
<path d="M14.9999 9.27893H1.00513V11.8367H14.9999V9.27893Z" fill="#142966"/> <path d="M978.723 40H94V1033H978.723V40Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99294 12.2518L14.548 9.72223C14.8177 9.59946 14.9569 9.4398 14.9659 9.27893H1C1.00896 9.4398 1.14826 9.59946 1.4179 9.72223L6.97294 12.2518C7.53075 12.5058 8.43514 12.5058 8.99294 12.2518Z" fill="#142966"/> </mask>
<path d="M14.9999 6.72107H1.00513V9.27885H14.9999V6.72107Z" fill="#142966"/> <g mask="url(#mask0_1749_80944)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.99294 10.9729L14.548 8.44332C14.8177 8.32054 14.9569 8.16088 14.9659 8H1C1.00896 8.16088 1.14826 8.32054 1.4179 8.44332L6.97294 10.9729C7.53075 11.2269 8.43514 11.2269 8.99294 10.9729Z" fill="#36BFFF"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M638.951 291.265L936.342 462.949C966.129 480.145 980.256 502.958 978.723 525.482V774.266C980.256 796.789 966.129 819.602 936.342 836.798L638.951 1008.48C582.292 1041.19 490.431 1041.19 433.773 1008.48L136.381 836.798C106.595 819.602 92.4675 796.789 93.9999 774.266L93.9999 525.482C92.4675 502.957 106.595 480.145 136.381 462.949L433.773 291.265C490.431 258.556 582.292 258.556 638.951 291.265Z" fill="#142966"/>
<path d="M14.9997 4.16309H1.00491V8.00309H14.9997V4.16309Z" fill="#36BFFF"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M638.951 65.0055L936.342 236.69C966.129 253.886 980.256 276.699 978.723 299.222V548.006C980.256 570.529 966.129 593.343 936.342 610.538L638.951 782.223C582.292 814.931 490.431 814.931 433.773 782.223L136.381 610.538C106.595 593.343 92.4675 570.529 93.9999 548.006L93.9999 299.222C92.4675 276.699 106.595 253.886 136.381 236.69L433.773 65.0055C490.431 32.2968 582.292 32.2968 638.951 65.0055Z" fill="#36BFFF"/>
<path d="M14.5484 4.63991L8.99337 7.16947C8.43561 7.42348 7.53121 7.42348 6.9734 7.16947L1.41836 4.63991C0.860546 4.3859 0.860546 3.97408 1.41836 3.72007L6.9734 1.19051C7.53121 0.936498 8.43561 0.936498 8.99337 1.19051L14.5484 3.72007C15.1063 3.97408 15.1063 4.3859 14.5484 4.63991Z" fill="#36BFFF"/> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

12
packages/nc-gui/assets/nc-icons/record.svg

@ -0,0 +1,12 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1409_68546)">
<path d="M12.3571 5.96842L4.64282 10.4219" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="1.15184" width="9.06208" height="9.06208" rx="1.335" transform="matrix(0.866044 -0.499967 0.866044 0.499967 -0.345705 8.77119)" stroke="#1F293A" stroke-width="1.33"/>
<path d="M4 6.33984L11.7143 10.7933" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g>
<defs>
<clipPath id="clip0_1409_68546">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 712 B

16
packages/nc-gui/assets/nc-icons/table.svg

@ -1,12 +1,12 @@
<svg width="17" height="16" viewBox="0 0 17 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1409_68546)"> <g clip-path="url(#clip0_1613_80692)">
<path d="M12.3571 5.96842L4.64282 10.4219" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M11.8571 5.96903L4.14285 10.4225" stroke="#374151" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<rect x="1.15184" width="9.06208" height="9.06208" rx="1.335" transform="matrix(0.866044 -0.499967 0.866044 0.499967 -0.345705 8.77119)" stroke="#1F293A" stroke-width="1.33"/> <rect x="1.15184" width="9.06208" height="9.06208" rx="1.335" transform="matrix(0.866044 -0.499967 0.866044 0.499967 -0.845705 8.77156)" stroke="#374151" stroke-width="1.33"/>
<path d="M4 6.33984L11.7143 10.7933" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M3.5 6.34009L11.2143 10.7935" stroke="#374151" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</g> </g>
<defs> <defs>
<clipPath id="clip0_1409_68546"> <clipPath id="clip0_1613_80692">
<rect width="16" height="16" fill="white" transform="translate(0.5)"/> <rect width="16" height="16" fill="white"/>
</clipPath> </clipPath>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 712 B

After

Width:  |  Height:  |  Size: 686 B

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

@ -34,6 +34,9 @@ const focus: VNodeRef = (el) => !isExpandedFormOpen.value && !isEditColumn.value
v-model="vModel" v-model="vModel"
class="h-full w-full outline-none p-2 bg-transparent" class="h-full w-full outline-none p-2 bg-transparent"
:placeholder="isEditColumn ? $t('labels.optional') : ''" :placeholder="isEditColumn ? $t('labels.optional') : ''"
:class="{
'px-1': isExpandedFormOpen,
}"
@blur="editEnabled = false" @blur="editEnabled = false"
@keydown.down.stop @keydown.down.stop
@keydown.left.stop @keydown.left.stop

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

@ -86,6 +86,7 @@ onClickOutside(inputWrapperRef, (e) => {
:class="{ :class="{
'p-2': editEnabled, 'p-2': editEnabled,
'py-1 h-full': isForm, 'py-1 h-full': isForm,
'px-1': isExpandedFormOpen,
}" }"
:style="{ :style="{
minHeight: `${height}px`, minHeight: `${height}px`,
@ -112,8 +113,8 @@ onClickOutside(inputWrapperRef, (e) => {
<span v-else>{{ vModel }}</span> <span v-else>{{ vModel }}</span>
<div <div
v-if="active" v-if="active && !isExpandedFormOpen"
class="!absolute right-0 bottom-0 h-6 w-5 group cursor-pointer flex justify-end gap-1 items-center active:(ring ring-accent ring-opacity-100) rounded border-none p-1 hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)" class="!absolute right-0 bottom-0 h-6 w-5 group cursor-pointer flex justify-end gap-1 items-center rounded border-none p-1 hover:(bg-primary bg-opacity-10) dark:(!bg-slate-500)"
:class="{ 'right-2 bottom-2': editEnabled }" :class="{ 'right-2 bottom-2': editEnabled }"
data-testid="attachment-cell-file-picker-button" data-testid="attachment-cell-file-picker-button"
@click.stop="isVisible = !isVisible" @click.stop="isVisible = !isVisible"

4
packages/nc-gui/components/dashboard/TreeView/TableNode.vue

@ -204,8 +204,8 @@ const isTableOpened = computed(() => {
<template #title> <template #title>
{{ $t('general.changeIcon') }} {{ $t('general.changeIcon') }}
</template> </template>
<component
<MdiTable :is="iconMap.table"
v-if="table.type === 'table'" v-if="table.type === 'table'"
class="flex w-5 !text-gray-500 text-sm" class="flex w-5 !text-gray-500 text-sm"
:class="{ :class="{

2
packages/nc-gui/components/dashboard/settings/Metadata.vue

@ -165,7 +165,7 @@ const columns = [
<div v-if="column.key === 'table_name'"> <div v-if="column.key === 'table_name'">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<div class="min-w-5 flex items-center justify-center"> <div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="record" class="text-gray-500"></GeneralTableIcon> <GeneralTableIcon :meta="record" class="text-gray-500" />
</div> </div>
<span class="overflow-ellipsis min-w-0 shrink-1">{{ record.title || record.table_name }}</span> <span class="overflow-ellipsis min-w-0 shrink-1">{{ record.title || record.table_name }}</span>
</div> </div>

5
packages/nc-gui/components/dashboard/settings/UIAcl.vue

@ -171,10 +171,7 @@ const columns = [
<div v-if="column.name === 'table_name'"> <div v-if="column.name === 'table_name'">
<div class="flex items-center gap-1"> <div class="flex items-center gap-1">
<div class="min-w-5 flex items-center justify-center"> <div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon <GeneralTableIcon :meta="{ meta: record.table_meta, type: record.ptype }" class="text-gray-500" />
:meta="{ meta: record.table_meta, type: record.ptype }"
class="text-gray-500"
></GeneralTableIcon>
</div> </div>
<GeneralTruncateText> <GeneralTruncateText>
<span class="overflow-ellipsis min-w-0 shrink-1">{{ record._ptn }}</span> <span class="overflow-ellipsis min-w-0 shrink-1">{{ record._ptn }}</span>

2
packages/nc-gui/components/dlg/TableDelete.vue

@ -110,7 +110,7 @@ const onDelete = async () => {
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.table')" :on-delete="onDelete"> <GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.table')" :on-delete="onDelete">
<template #entity-preview> <template #entity-preview>
<div v-if="table" class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4"> <div v-if="table" class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<GeneralTableIcon :meta="table" class="nc-view-icon"></GeneralTableIcon> <GeneralTableIcon :meta="table" class="nc-view-icon" />
<div <div
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75" class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75"
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" :style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }"

2
packages/nc-gui/components/general/ProjectIcon.vue

@ -7,7 +7,7 @@ const { hoverable } = defineProps<{
<template> <template>
<GeneralIcon <GeneralIcon
icon="ncDatabase" icon="project"
class="text-[#2824FB] nc-project-icon" class="text-[#2824FB] nc-project-icon"
:class="{ :class="{
'nc-project-icon-hoverable': hoverable, 'nc-project-icon-hoverable': hoverable,

1
packages/nc-gui/components/general/TableIcon.vue

@ -16,7 +16,6 @@ const { meta: tableMeta } = defineProps<{
:emoji="tableMeta.meta?.icon" :emoji="tableMeta.meta?.icon"
readonly readonly
/> />
<component :is="iconMap.eye" v-else-if="tableMeta?.type === 'view'" class="w-5 mx-0.75" /> <component :is="iconMap.eye" v-else-if="tableMeta?.type === 'view'" class="w-5 mx-0.75" />
<component :is="iconMap.table" v-else class="w-5 mx-0.5" /> <component :is="iconMap.table" v-else class="w-5 mx-0.5" />
</template> </template>

27
packages/nc-gui/components/general/UserIcon.vue

@ -1,12 +1,27 @@
<script lang="ts" setup> <script lang="ts" setup>
const props = defineProps<{ const props = withDefaults(
size?: 'small' | 'medium' | 'base' | 'large' | 'xlarge' defineProps<{
name?: string size?: 'small' | 'medium' | 'base' | 'large' | 'xlarge'
}>() name?: string
email?: string
}>(),
{
email: '',
},
)
const { user } = useGlobal() const { user } = useGlobal()
const backgroundColor = computed(() => (user.value?.id ? stringToColour(user.value?.id) : '#FFFFFF')) const emailProp = toRef(props, 'email')
const backgroundColor = computed(() => {
// in comments we need to generate user icon from email
if (emailProp.value.length) {
return stringToColour(emailProp.value)
}
return user.value?.email ? stringToColour(user.value?.email) : '#FFFFFF'
})
const size = computed(() => props.size || 'medium') const size = computed(() => props.size || 'medium')
@ -31,7 +46,7 @@ const usernameInitials = computed(() => {
<template> <template>
<div <div
class="flex nc-user-avatar" class="flex nc-user-avatar font-bold"
:class="{ :class="{
'min-w-4 min-h-4': size === 'small', 'min-w-4 min-h-4': size === 'small',
'min-w-6 min-h-6': size === 'medium', 'min-w-6 min-h-6': size === 'medium',

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

@ -22,6 +22,7 @@ import {
isDate, isDate,
isDateTime, isDateTime,
isDecimal, isDecimal,
isDrawerExist,
isDuration, isDuration,
isEmail, isEmail,
isFloat, isFloat,
@ -207,7 +208,7 @@ onUnmounted(() => {
'h-10': isForm && !isSurveyForm && !isAttachment(column) && !props.virtual, 'h-10': isForm && !isSurveyForm && !isAttachment(column) && !props.virtual,
'nc-grid-numeric-cell-left': (isForm && isNumericField && isExpandedFormOpen) || isEditColumnMenu, 'nc-grid-numeric-cell-left': (isForm && isNumericField && isExpandedFormOpen) || isEditColumnMenu,
'!min-h-30 resize-y': isTextArea(column) && (isForm || isSurveyForm), '!min-h-30 resize-y': isTextArea(column) && (isForm || isSurveyForm),
'!border-2 !border-brand-500': props.editEnabled && (isSurveyForm || isForm), '!border-2 !border-brand-500': props.editEnabled && (isSurveyForm || isForm) && !isDrawerExist(),
}, },
]" ]"
@keydown.enter.exact="navigate(NavigateDir.NEXT, $event)" @keydown.enter.exact="navigate(NavigateDir.NEXT, $event)"

2
packages/nc-gui/components/smartsheet/column/LinkedToAnotherRecordOptions.vue

@ -80,7 +80,7 @@ const isLinks = computed(() => vModel.value.uidt === UITypes.Links)
<a-select-option v-for="table of refTables" :key="table.title" :value="table.id"> <a-select-option v-for="table of refTables" :key="table.title" :value="table.id">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="min-w-5 flex items-center justify-center"> <div class="min-w-5 flex items-center justify-center">
<GeneralTableIcon :meta="table" class="text-gray-500"></GeneralTableIcon> <GeneralTableIcon :meta="table" class="text-gray-500" />
</div> </div>
<span class="overflow-ellipsis min-w-0 shrink-1">{{ table.title }}</span> <span class="overflow-ellipsis min-w-0 shrink-1">{{ table.title }}</span>

21
packages/nc-gui/components/smartsheet/expanded-form/Comments.vue

@ -8,7 +8,9 @@ const { loadCommentsAndLogs, commentsAndLogs, saveComment, comment, updateCommen
const commentsWrapperEl = ref<HTMLDivElement>() const commentsWrapperEl = ref<HTMLDivElement>()
await loadCommentsAndLogs() onMounted(async () => {
await loadCommentsAndLogs()
})
const { user } = useGlobal() const { user } = useGlobal()
@ -91,7 +93,7 @@ const value = computed({
}) })
watch( watch(
commentsAndLogs, [commentsAndLogs, tab],
() => { () => {
setTimeout(() => { setTimeout(() => {
if (commentsWrapperEl.value) commentsWrapperEl.value.scrollTop = commentsWrapperEl.value?.scrollHeight if (commentsWrapperEl.value) commentsWrapperEl.value.scrollTop = commentsWrapperEl.value?.scrollHeight
@ -148,23 +150,24 @@ const processedAudit = (log: string) => {
'pb-2': tab !== 'comments', 'pb-2': tab !== 'comments',
}" }"
> >
<div v-if="tab === 'comments'" ref="commentsWrapperEl" class="flex flex-col h-full"> <div v-if="tab === 'comments'" class="flex flex-col h-full">
<div v-if="comments.length === 0" class="flex flex-col my-1 text-center justify-center h-full"> <div v-if="comments.length === 0" class="flex flex-col my-1 text-center justify-center h-full">
<div class="text-center text-3xl text-gray-700"> <div class="text-center text-3xl text-gray-700">
<GeneralIcon icon="commentHere" /> <GeneralIcon icon="commentHere" />
</div> </div>
<div class="font-medium text-center my-6 text-gray-500">{{ $t('activity.startCommenting') }}</div> <div class="font-medium text-center my-6 text-gray-500">{{ $t('activity.startCommenting') }}</div>
</div> </div>
<div v-else class="flex flex-col h-full py-2 pl-2 pr-1 space-y-2 nc-scrollbar-md"> <div v-else ref="commentsWrapperEl" class="flex flex-col h-full py-2 pl-2 pr-1 space-y-2 nc-scrollbar-md">
<div v-for="log of comments" :key="log.id"> <div v-for="log of comments" :key="log.id">
<div class="bg-white rounded-xl group border-1 gap-2 border-gray-200"> <div class="bg-white rounded-xl group border-1 gap-2 border-gray-200">
<div class="flex flex-col p-4 gap-3"> <div class="flex flex-col p-4 gap-3">
<div class="flex justify-between"> <div class="flex justify-between">
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<GeneralUserIcon size="base" :name="log.display_name ?? log.user" /> <GeneralUserIcon size="base" :name="log.display_name ?? log.user" :email="log.user" />
<div class="flex flex-col"> <div class="flex flex-col">
<span class="truncate font-bold max-w-42"> <span class="truncate font-bold max-w-42">
{{ log.display_name ?? log.user.split('@')[0].slice(0, 2) ?? 'Shared base' }} {{ log.display_name ?? log.user.split('@')[0] ?? 'Shared base' }}
</span> </span>
<div v-if="log.id !== editLog?.id" class="text-xs text-gray-500"> <div v-if="log.id !== editLog?.id" class="text-xs text-gray-500">
{{ log.created_at !== log.updated_at ? `Edited ${timeAgo(log.updated_at)}` : timeAgo(log.created_at) }} {{ log.created_at !== log.updated_at ? `Edited ${timeAgo(log.updated_at)}` : timeAgo(log.created_at) }}
@ -237,11 +240,11 @@ const processedAudit = (log: string) => {
<div class="bg-white rounded-xl border-1 gap-3 border-gray-200"> <div class="bg-white rounded-xl border-1 gap-3 border-gray-200">
<div class="flex flex-col p-4 gap-3"> <div class="flex flex-col p-4 gap-3">
<div class="flex justify-between"> <div class="flex justify-between">
<div class="flex font-bold items-center gap-2"> <div class="flex items-center gap-2">
<GeneralUserIcon size="base" :name="log.display_name ?? log.user" /> <GeneralUserIcon size="base" :name="log.display_name ?? log.user" :email="log.user" />
<div class="flex flex-col"> <div class="flex flex-col">
<span class="truncate max-w-50"> <span class="truncate max-w-50 font-bold">
{{ log.display_name ?? log.user.split('@')[0].slice(0, 2) ?? 'Shared base' }} {{ log.display_name ?? log.user.split('@')[0].slice(0, 2) ?? 'Shared base' }}
</span> </span>
<div v-if="log.id !== editLog?.id" class="text-xs text-gray-500"> <div v-if="log.id !== editLog?.id" class="text-xs text-gray-500">

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

@ -3,7 +3,6 @@ import type { TableType, ViewType } from 'nocodb-sdk'
import { isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' import { isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import MdiChevronDown from '~icons/mdi/chevron-down' import MdiChevronDown from '~icons/mdi/chevron-down'
import TableIcon from '~icons/nc-icons/table'
import { import {
CellClickHookInj, CellClickHookInj,
@ -15,6 +14,7 @@ import {
ReloadRowDataHookInj, ReloadRowDataHookInj,
computedInject, computedInject,
createEventHook, createEventHook,
iconMap,
inject, inject,
message, message,
provide, provide,
@ -66,6 +66,9 @@ const router = useRouter()
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
// to check if a expanded form which is not yet saved exist or not
const isUnsavedFormExist = ref(false)
const { isUIAllowed } = useRoles() const { isUIAllowed } = useRoles()
const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook()) const reloadTrigger = inject(ReloadRowDataHookInj, createEventHook())
@ -151,6 +154,7 @@ const onClose = () => {
const onDuplicateRow = () => { const onDuplicateRow = () => {
duplicatingRowInProgress.value = true duplicatingRowInProgress.value = true
isUnsavedFormExist.value = true
const oldRow = { ...row.value.row } const oldRow = { ...row.value.row }
delete oldRow.ncRecordId delete oldRow.ncRecordId
const newRow = Object.assign( const newRow = Object.assign(
@ -168,25 +172,38 @@ const onDuplicateRow = () => {
}, 500) }, 500)
} }
const save = async () => {
if (isNew.value) {
const data = await _save(rowState.value)
await syncLTARRefs(data)
reloadTrigger?.trigger()
} else {
await _save()
reloadTrigger?.trigger()
}
isUnsavedFormExist.value = false
}
const isPreventChangeModalOpen = ref(false)
const discardPreventModal = () => {
emits('next')
isPreventChangeModalOpen.value = false
}
const onNext = async () => { const onNext = async () => {
if (changedColumns.value.size > 0) { if (changedColumns.value.size > 0) {
await Modal.confirm({ isPreventChangeModalOpen.value = true
title: 'Do you want to save the changes?',
okText: 'Save',
cancelText: 'Discard',
onOk: async () => {
await save()
emits('next')
},
onCancel: () => {
emits('next')
},
})
} else { } else {
emits('next') emits('next')
} }
} }
const saveChanges = async () => {
isUnsavedFormExist.value = false
await save()
emits('next')
}
const reloadParentRowHook = inject(ReloadRowDataHookInj, createEventHook()) const reloadParentRowHook = inject(ReloadRowDataHookInj, createEventHook())
// override reload trigger and use it to reload grid and the form itself // override reload trigger and use it to reload grid and the form itself
@ -208,6 +225,10 @@ if (isKanban.value) {
} }
} }
watch(isUnsavedFormExist, () => {
console.log(isUnsavedFormExist.value, 'HEHEH')
})
provide(IsExpandedFormOpenInj, isExpanded) provide(IsExpandedFormOpenInj, isExpanded)
const cellWrapperEl = ref() const cellWrapperEl = ref()
@ -230,18 +251,6 @@ const addNewRow = () => {
isExpanded.value = true isExpanded.value = true
}, 500) }, 500)
} }
const save = async () => {
if (isNew.value) {
const data = await _save(rowState.value)
await syncLTARRefs(data)
reloadTrigger?.trigger()
} else {
await _save()
reloadTrigger?.trigger()
}
}
// attach keyboard listeners to switch between rows // attach keyboard listeners to switch between rows
// using alt + left/right arrow keys // using alt + left/right arrow keys
useActiveKeyupListener( useActiveKeyupListener(
@ -325,14 +334,9 @@ const onConfirmDeleteRowClick = async () => {
showDeleteRowModal.value = false showDeleteRowModal.value = false
await deleteRowById(primaryKey.value) await deleteRowById(primaryKey.value)
message.success('Row deleted') message.success('Row deleted')
// if (!props.lastRow) {
// await onNext()
// } else if (!props.firstRow) {
// emits('prev')
// } else {
// }
reloadTrigger.trigger() reloadTrigger.trigger()
onClose() onClose()
showDeleteRowModal.value = false
} }
watch( watch(
@ -378,7 +382,7 @@ export default {
<div class="h-[85vh] xs:(max-h-full) max-h-215 flex flex-col p-6"> <div class="h-[85vh] xs:(max-h-full) max-h-215 flex flex-col p-6">
<div class="flex h-8 flex-shrink-0 w-full items-center nc-expanded-form-header relative mb-4 justify-between"> <div class="flex h-8 flex-shrink-0 w-full items-center nc-expanded-form-header relative mb-4 justify-between">
<template v-if="!isMobileMode"> <template v-if="!isMobileMode">
<div class="flex gap-3"> <div class="flex gap-3 w-100">
<div class="flex gap-2"> <div class="flex gap-2">
<NcButton <NcButton
v-if="props.showNextPrevIcons" v-if="props.showNextPrevIcons"
@ -399,13 +403,9 @@ export default {
<MdiChevronDown class="text-md" /> <MdiChevronDown class="text-md" />
</NcButton> </NcButton>
</div> </div>
<div v-if="displayValue" class="flex items-center truncate max-w-32 font-bold text-gray-800 text-xl"> <div v-if="displayValue" class="flex items-center truncate font-bold text-gray-800 text-xl">
{{ displayValue }} {{ displayValue }}
</div> </div>
<div class="bg-gray-100 px-2 gap-1 flex my-1 items-center rounded-lg text-gray-800 font-medium">
<TableIcon class="w-6 h-6 text-sm" />
All {{ meta.title }}
</div>
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<NcDropdown v-if="!isNew"> <NcDropdown v-if="!isNew">
@ -438,7 +438,7 @@ export default {
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew" v-if="isUIAllowed('dataEdit') && !isNew"
v-e="['c:row-expand:delete']" v-e="['c:row-expand:delete']"
class="!text-red-500" class="!text-red-500 !hover:bg-red-50"
@click="!isNew && onDeleteRowClick()" @click="!isNew && onDeleteRowClick()"
> >
<component :is="iconMap.delete" data-testid="nc-expanded-form-delete" class="cursor-pointer nc-delete-row" /> <component :is="iconMap.delete" data-testid="nc-expanded-form-delete" class="cursor-pointer nc-delete-row" />
@ -595,7 +595,7 @@ export default {
<NcMenuItem <NcMenuItem
v-if="isUIAllowed('dataEdit') && !isNew" v-if="isUIAllowed('dataEdit') && !isNew"
v-e="['c:row-expand:delete']" v-e="['c:row-expand:delete']"
class="!text-red-500" class="!text-red-500 !hover:bg-red-50"
@click="!isNew && onDeleteRowClick()" @click="!isNew && onDeleteRowClick()"
> >
<div data-testid="nc-expanded-form-delete"> <div data-testid="nc-expanded-form-delete">
@ -624,6 +624,7 @@ export default {
type="primary" type="primary"
size="medium" size="medium"
class="nc-expand-form-save-btn !xs:(text-base)" class="nc-expand-form-save-btn !xs:(text-base)"
:disabled="changedColumns.size === 0 && !isUnsavedFormExist"
@click="save" @click="save"
> >
<div class="xs:px-1">Save</div> <div class="xs:px-1">Save</div>
@ -642,16 +643,32 @@ export default {
</div> </div>
</NcModal> </NcModal>
<NcModal v-model:visible="showDeleteRowModal" class="!w-[25rem] !xs-"> <GeneralDeleteModal v-model:visible="showDeleteRowModal" entity-name="Record" :on-delete="() => onConfirmDeleteRowClick()">
<div class=""> <template #entity-preview>
<div class="prose-xl font-bold self-center">Delete row ?</div> <span>
<div class="flex flex-row items-center py-2.25 px-2.5 bg-gray-50 rounded-lg text-gray-700 mb-4">
<div class="mt-4">Are you sure you want to delete this row?</div> <component :is="iconMap.table" class="nc-view-icon" />
</div> <div class="capitalize text-ellipsis overflow-hidden select-none w-full pl-1.75 break-keep whitespace-nowrap">
<div class="flex flex-row gap-x-2 mt-4 pt-1.5 justify-end pt-4 gap-x-3"> {{ meta.title }}
<NcButton v-if="isMobileMode" type="secondary" @click="showDeleteRowModal = false">{{ $t('general.cancel') }} </NcButton> </div>
</div>
<NcButton v-e="['a:row-expand:delete']" @click="onConfirmDeleteRowClick">{{ $t('general.confirm') }} </NcButton> </span>
</template>
</GeneralDeleteModal>
<!-- Prevent unsaved change modal -->
<NcModal v-model:visible="isPreventChangeModalOpen" size="small">
<template #header>
<div class="flex flex-row items-center gap-x-2">Do you want to save the changes ?</div>
</template>
<div class="mt-2">
<div class="flex flex-row justify-end gap-x-2 mt-6">
<NcButton type="secondary" @click="discardPreventModal">{{ $t('general.quit') }}</NcButton>
<NcButton key="submit" type="primary" label="Rename Table" loading-label="Renaming Table" @click="saveChanges">
{{ $t('activity.saveAndQuit') }}
</NcButton>
</div>
</div> </div>
</NcModal> </NcModal>
</template> </template>

15
packages/nc-gui/components/virtual-cell/Links.vue

@ -64,6 +64,20 @@ const textVal = computed(() => {
} }
}) })
const toatlRecordsLinked = computed(() => {
if (isForm?.value) {
return state.value?.[colTitle.value]?.length
}
const parsedValue = +value?.value || 0
if (!parsedValue) {
return 0
} else if (parsedValue === 1) {
return 1
} else {
return parsedValue
}
})
const onAttachRecord = () => { const onAttachRecord = () => {
childListDlg.value = false childListDlg.value = false
listItemsDlg.value = true listItemsDlg.value = true
@ -121,6 +135,7 @@ const localCellValue = computed<any[]>(() => {
<LazyVirtualCellComponentsListChildItems <LazyVirtualCellComponentsListChildItems
v-model="childListDlg" v-model="childListDlg"
:items="toatlRecordsLinked"
:column="relatedTableDisplayColumn" :column="relatedTableDisplayColumn"
:cell-value="localCellValue" :cell-value="localCellValue"
@attach-record="onAttachRecord" @attach-record="onAttachRecord"

4
packages/nc-gui/components/virtual-cell/components/Header.vue

@ -59,7 +59,9 @@ const relationMeta = computed(() => {
<div class="flex justify-end"> <div class="flex justify-end">
<div class="flex flex-shrink-0 rounded-md gap-1 text-brand-500 items-center bg-gray-100 px-2 py-1"> <div class="flex flex-shrink-0 rounded-md gap-1 text-brand-500 items-center bg-gray-100 px-2 py-1">
<FileIcon class="w-4 h-4" /> <FileIcon class="w-4 h-4" />
{{ displayValue }} <GeneralTruncateText placement="top" length="25">
{{ displayValue }}
</GeneralTruncateText>
</div> </div>
</div> </div>
<NcTooltip class="flex-shrink-0"> <NcTooltip class="flex-shrink-0">

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

@ -18,7 +18,14 @@ import {
useVModel, useVModel,
} from '#imports' } from '#imports'
const props = defineProps<{ modelValue?: boolean; cellValue: any; column: any }>() interface Prop {
modelValue?: boolean
cellValue: any
column: any
items: number
}
const props = defineProps<Prop>()
const emit = defineEmits(['update:modelValue', 'attachRecord']) const emit = defineEmits(['update:modelValue', 'attachRecord'])
@ -49,6 +56,8 @@ const {
displayValueProp, displayValueProp,
} = useLTARStoreOrThrow() } = useLTARStoreOrThrow()
isChildrenLoading.value = true
const { isNew, state, removeLTARRef, addLTARRef } = useSmartsheetRowStoreOrThrow() const { isNew, state, removeLTARRef, addLTARRef } = useSmartsheetRowStoreOrThrow()
watch( watch(
@ -121,6 +130,46 @@ watch(expandedFormDlg, () => {
onKeyStroke('Escape', () => { onKeyStroke('Escape', () => {
vModel.value = false vModel.value = false
}) })
/*
to render same number of skelton as the number of cards
displayed
*/
const skeltonCount = computed(() => {
if (props.items < 10 && childrenListPagination.page === 1) {
return props.items
}
if (childrenListCount.value < 10 && childrenListPagination.page === 1) {
return childrenListCount.value || 10
}
const totalRows = Math.ceil(childrenListCount.value / 10)
if (totalRows === childrenListPagination.page) {
return childrenListCount.value % 10
}
return 10
})
const totalItemsToShow = computed(() => {
if (isChildrenLoading) {
return props.items
}
return childrenListCount.value
})
const isDataExist = computed<boolean>(() => {
return childrenList.value?.pageInfo?.totalRows || (isNew.value && state.value?.[colTitle.value]?.length)
})
const linkOrUnLink = (rowRef: Record<string, string>, id: string) => {
if (isPublic.value && !isForm.value) return
if (isNew.value || isChildrenListLinked.value[parseInt(id)]) {
unlinkRow(rowRef, parseInt(id))
} else {
linkRow(rowRef, parseInt(id))
}
}
</script> </script>
<template> <template>
@ -142,8 +191,6 @@ onKeyStroke('Escape', () => {
:related-table-title="relatedTableMeta?.title" :related-table-title="relatedTableMeta?.title"
:display-value="row.row[displayValueProp]" :display-value="row.row[displayValueProp]"
/> />
<div v-if="!isForm" class="m-4 bg-gray-50 border-gray-50 border-b-2"></div>
<div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2"> <div v-if="!isForm" class="flex mt-2 mb-2 items-center gap-2">
<div <div
class="flex items-center border-1 p-1 rounded-md w-full border-gray-200" class="flex items-center border-1 p-1 rounded-md w-full border-gray-200"
@ -166,72 +213,61 @@ onKeyStroke('Escape', () => {
</div> </div>
</div> </div>
<template v-if="(isNew && state?.[colTitle]?.length) || childrenList?.pageInfo?.totalRows"> <div v-if="isDataExist || isChildrenLoading" class="mt-2 mb-2">
<div class="mt-2 mb-2"> <div
<div :class="{
:class="{ 'h-105': !isForm,
'h-[420px]': !isForm, 'h-62.5': isForm,
'h-[250px]': isForm, }"
}" class="overflow-scroll nc-scrollbar-md cursor-pointer pr-1"
class="overflow-scroll nc-scrollbar-md cursor-pointer pr-1" >
> <template v-if="isChildrenLoading">
<template v-if="isChildrenLoading"> <div
<div v-for="(_, i) in Array.from({ length: skeltonCount })"
v-for="(x, i) in Array.from({ length: 10 })" :key="i"
:key="i" class="border-2 flex flex-row gap-2 mb-2 transition-all rounded-xl relative border-gray-200 hover:bg-gray-50"
class="!border-2 flex flex-row gap-2 mb-2 transition-all !rounded-xl relative !border-gray-200 hover:bg-gray-50" >
> <a-skeleton-image class="h-24 w-24 !rounded-xl" />
<a-skeleton-image class="h-24 w-24 !rounded-xl" /> <div class="flex flex-col m-2 gap-2 flex-grow justify-center">
<div class="flex flex-col m-[.5rem] gap-2 flex-grow justify-center"> <a-skeleton-input class="!w-48 !rounded-xl" active size="small" />
<a-skeleton-input class="!w-48 !rounded-xl" active size="small" /> <div class="flex flex-row gap-6 w-10/12">
<div class="flex flex-row gap-6 w-10/12"> <div class="flex flex-col gap-0.5">
<div class="flex flex-col gap-0.5"> <a-skeleton-input class="!h-4 !w-12" active size="small" />
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input class="!h-4 !w-24" active size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" /> </div>
</div> <div class="flex flex-col gap-0.5">
<div class="flex flex-col gap-0.5"> <a-skeleton-input class="!h-4 !w-12" active size="small" />
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input class="!h-4 !w-24" active size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" /> </div>
</div> <div class="flex flex-col gap-0.5">
<div class="flex flex-col gap-0.5"> <a-skeleton-input class="!h-4 !w-12" active size="small" />
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input class="!h-4 !w-24" active size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" /> </div>
</div> <div class="flex flex-col gap-0.5">
<div class="flex flex-col gap-0.5"> <a-skeleton-input class="!h-4 !w-12" active size="small" />
<a-skeleton-input class="!h-4 !w-12" active size="small" /> <a-skeleton-input class="!h-4 !w-24" active size="small" />
<a-skeleton-input class="!h-4 !w-24" active size="small" />
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </div>
<template v-else> </template>
<LazyVirtualCellComponentsListItem <template v-else>
v-for="(refRow, id) in childrenList?.list ?? state?.[colTitle] ?? []" <LazyVirtualCellComponentsListItem
:key="id" v-for="(refRow, id) in childrenList?.list ?? state?.[colTitle] ?? []"
:row="refRow" :key="id"
:fields="fields" :row="refRow"
data-testid="nc-child-list-item" :fields="fields"
:attachment="attachmentCol" data-testid="nc-child-list-item"
:related-table-display-value-prop="relatedTableDisplayValueProp" :attachment="attachmentCol"
:is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true" :related-table-display-value-prop="relatedTableDisplayValueProp"
:is-loading="isChildrenListLoading[Number.parseInt(id)]" :is-linked="childrenList?.list ? isChildrenListLinked[Number.parseInt(id)] : true"
@expand="onClick(refRow)" :is-loading="isChildrenListLoading[Number.parseInt(id)]"
@click=" @expand="onClick(refRow)"
() => { @click="linkOrUnLink(refRow, id)"
if (isPublic && !isForm) return />
isNew </template>
? unlinkRow(refRow, Number.parseInt(id))
: isChildrenListLinked[Number.parseInt(id)]
? unlinkRow(refRow, Number.parseInt(id))
: linkRow(refRow, Number.parseInt(id))
}
"
/>
</template>
</div>
</div> </div>
</template> </div>
<div <div
v-else v-else
:class="{ :class="{
@ -259,7 +295,7 @@ onKeyStroke('Escape', () => {
<div class="flex flex-row justify-between bg-white relative pt-1"> <div class="flex flex-row justify-between bg-white relative pt-1">
<div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50"> <div v-if="!isForm" class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">
{{ childrenListCount || 0 }} {{ $t('objects.records') }} {{ childrenListCount !== 0 ? $t('general.are') : '' }} {{ totalItemsToShow || 0 }} {{ $t('objects.records') }} {{ totalItemsToShow !== 0 ? $t('general.are') : '' }}
{{ $t('general.linked') }} {{ $t('general.linked') }}
</div> </div>
<div v-else class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50"> <div v-else class="flex items-center justify-center px-2 rounded-md text-gray-500 bg-brand-50">

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

@ -49,6 +49,8 @@ const { addLTARRef, isNew, removeLTARRef, state: rowState } = useSmartsheetRowSt
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
isChildrenExcludedLoading.value = true
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
const saveRow = inject(SaveRowInj, () => {}) const saveRow = inject(SaveRowInj, () => {})
@ -213,7 +215,7 @@ onKeyStroke('Escape', () => {
</NcButton> </NcButton>
</div> </div>
<template v-if="childrenExcludedList?.pageInfo?.totalRows"> <template v-if="childrenExcludedList?.pageInfo?.totalRows || isChildrenExcludedLoading">
<div class="pb-2 pt-1"> <div class="pb-2 pt-1">
<div class="h-[420px] overflow-scroll nc-scrollbar-md pr-1 cursor-pointer"> <div class="h-[420px] overflow-scroll nc-scrollbar-md pr-1 cursor-pointer">
<template v-if="isChildrenExcludedLoading"> <template v-if="isChildrenExcludedLoading">
@ -274,7 +276,10 @@ onKeyStroke('Escape', () => {
</div> </div>
</div> </div>
</template> </template>
<div v-else class="py-2 h-[420px] flex flex-col gap-3 items-center justify-center text-gray-500"> <div
v-if="!isChildrenExcludedLoading && !childrenExcludedList?.pageInfo?.totalRows"
class="py-2 h-105 flex flex-col gap-3 items-center justify-center text-gray-500"
>
<InboxIcon class="w-16 h-16 mx-auto" /> <InboxIcon class="w-16 h-16 mx-auto" />
<p> <p>
{{ $t('msg.thereAreNoRecordsInTable') }} {{ $t('msg.thereAreNoRecordsInTable') }}

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

@ -39,6 +39,7 @@
} }
}, },
"general": { "general": {
"quit": "Quit",
"home": "Home", "home": "Home",
"load": "Load", "load": "Load",
"open": "Open", "open": "Open",
@ -400,6 +401,7 @@
"addHeader": "Add Header", "addHeader": "Add Header",
"enterDefaultUrlOptional": "Enter default URL (Optional)", "enterDefaultUrlOptional": "Enter default URL (Optional)",
"negative": "Negative", "negative": "Negative",
"discard": "Discard",
"default": "Default", "default": "Default",
"defaultNumberPercent": "Default Number (%)", "defaultNumberPercent": "Default Number (%)",
"durationFormat": "Duration Format", "durationFormat": "Duration Format",
@ -624,6 +626,7 @@
"deleteProject": "Delete Project", "deleteProject": "Delete Project",
"refreshProject": "Refresh projects", "refreshProject": "Refresh projects",
"saveProject": "Save Project", "saveProject": "Save Project",
"saveAndQuit": "Save & Quit",
"deleteKanbanStack": "Delete stack?", "deleteKanbanStack": "Delete stack?",
"createProjectExtended": { "createProjectExtended": {
"extDB": "Create By Connecting <br>To An External Database", "extDB": "Create By Connecting <br>To An External Database",

9
packages/nc-gui/utils/iconUtils.ts

@ -13,7 +13,6 @@ import MdiThumbUp from '~icons/mdi/thumb-up'
import MdiThumbUpOutline from '~icons/mdi/thumb-up-outline' import MdiThumbUpOutline from '~icons/mdi/thumb-up-outline'
import MdiFlag from '~icons/mdi/flag' import MdiFlag from '~icons/mdi/flag'
import MdiFlagOutline from '~icons/mdi/flag-outline' import MdiFlagOutline from '~icons/mdi/flag-outline'
import MdiTable from '~icons/mdi/table'
import MsMove from '~icons/material-symbols/drive-file-move-outline' import MsMove from '~icons/material-symbols/drive-file-move-outline'
import MSCloseRounded from '~icons/material-symbols/close-rounded' import MSCloseRounded from '~icons/material-symbols/close-rounded'
import MdiTableLarge from '~icons/mdi/table-large' import MdiTableLarge from '~icons/mdi/table-large'
@ -81,7 +80,9 @@ import LcSend from '~icons/lucide/send'
import NcCommentHere from '~icons/nc-icons/comment-here' import NcCommentHere from '~icons/nc-icons/comment-here'
import NcAddDataSource from '~icons/nc-icons/add-data-source' import NcAddDataSource from '~icons/nc-icons/add-data-source'
import NcDatabaseIcon from '~icons/nc-icons/database' import NcDatabaseIcon from '~icons/nc-icons/database'
import Project from '~icons/nc-icons/project'
import TableIcon from '~icons/nc-icons/table'
import RecordIcon from '~icons/nc-icons/record'
// Roles // Roles
import MaterialSymbolsManageAccountsOutline from '~icons/material-symbols/manage-accounts-outline' import MaterialSymbolsManageAccountsOutline from '~icons/material-symbols/manage-accounts-outline'
// account // account
@ -237,6 +238,7 @@ import MaterialSymbolsBlock from '~icons/material-symbols/block'
} as const */ } as const */
export const iconMap = { export const iconMap = {
record: RecordIcon,
workspaceDefault: MsGroup, workspaceDefault: MsGroup,
search: NcSearch, search: NcSearch,
error: h('span', { class: 'material-symbols' }, 'error'), error: h('span', { class: 'material-symbols' }, 'error'),
@ -245,6 +247,7 @@ export const iconMap = {
addOutlineBox: MsAddBoxOutline, addOutlineBox: MsAddBoxOutline,
loading: h('span', { class: 'material-symbols' }, 'autorenew'), loading: h('span', { class: 'material-symbols' }, 'autorenew'),
arrowCollapse: Up, arrowCollapse: Up,
project: Project,
markerAlert: h('span', { class: 'material-symbols' }, 'warning'), markerAlert: h('span', { class: 'material-symbols' }, 'warning'),
appStore: h('span', { class: 'material-symbols' }, 'apps'), appStore: h('span', { class: 'material-symbols' }, 'apps'),
chevronLeft: h('span', { class: 'material-symbols' }, 'chevron_left'), chevronLeft: h('span', { class: 'material-symbols' }, 'chevron_left'),
@ -322,7 +325,7 @@ export const iconMap = {
// threeDotHorizontal: h('span', { class: 'material-symbols' }, 'more_horiz'), // threeDotHorizontal: h('span', { class: 'material-symbols' }, 'more_horiz'),
threeDotVertical: MdiDotsVertical, threeDotVertical: MdiDotsVertical,
threeDotHorizontal: MdiDotsHorizontal, threeDotHorizontal: MdiDotsHorizontal,
table: MdiTable, table: TableIcon,
excel: PhExcelThin, // h('span', { class: 'material-symbols' }, 'grid_on'), excel: PhExcelThin, // h('span', { class: 'material-symbols' }, 'grid_on'),
csv: PhCsvThin, // h('span', { class: 'material-symbols' }, 'grid_on'), csv: PhCsvThin, // h('span', { class: 'material-symbols' }, 'grid_on'),
code: Code, code: Code,

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

@ -47,7 +47,7 @@ export class ExpandedFormPage extends BasePage {
async clickDeleteRow() { async clickDeleteRow() {
await this.click3DotsMenu('Delete Record'); await this.click3DotsMenu('Delete Record');
await this.rootPage.locator('.ant-btn-primary:has-text("Confirm")').click(); await this.rootPage.locator('.ant-btn-danger:has-text("Delete Record")').click();
} }
async isDisabledDuplicateRow() { async isDisabledDuplicateRow() {
@ -125,18 +125,22 @@ export class ExpandedFormPage extends BasePage {
}); });
} }
await this.get().press('Escape');
await this.get().waitFor({ state: 'hidden' });
await this.verifyToast({ message: `updated successfully.` }); await this.verifyToast({ message: `updated successfully.` });
await this.rootPage.locator('[data-testid="grid-load-spinner"]').waitFor({ state: 'hidden' }); await this.rootPage.locator('[data-testid="grid-load-spinner"]').waitFor({ state: 'hidden' });
// removing focus from toast
await this.rootPage.locator('.nc-modal').click();
await this.get().press('Escape');
await this.get().waitFor({ state: 'hidden' });
} }
async verify({ header, url }: { header: string; url?: string }) { // check for the expanded form header table name
await expect(this.get().locator(`.nc-expanded-form-header`).last()).toContainText(header);
if (url) { // async verify({ header, url }: { header: string; url?: string }) {
await expect.poll(() => this.rootPage.url()).toContain(url); // await expect(this.get().locator(`.nc-expanded-form-header`).last()).toContainText(header);
} // if (url) {
} // await expect.poll(() => this.rootPage.url()).toContain(url);
// }
// }
async escape() { async escape() {
await this.rootPage.keyboard.press('Escape'); await this.rootPage.keyboard.press('Escape');

29
tests/playwright/tests/db/features/expandedFormUrl.spec.ts

@ -47,11 +47,6 @@ test.describe('Expanded form URL', () => {
const url = await dashboard.rootPage.url(); const url = await dashboard.rootPage.url();
await dashboard.expandedForm.escape(); await dashboard.expandedForm.escape();
await dashboard.rootPage.goto(url); await dashboard.rootPage.goto(url);
await dashboard.expandedForm.verify({
header: 'Row 0 All Test Table',
url,
});
} }
async function viewTestSakila(viewType: string) { async function viewTestSakila(viewType: string) {
@ -79,10 +74,6 @@ test.describe('Expanded form URL', () => {
// expand row & verify URL // expand row & verify URL
await viewObj.openExpandedRow({ index: 0 }); await viewObj.openExpandedRow({ index: 0 });
await dashboard.expandedForm.verify({
header: 'Afghanistan',
url: 'rowId=1',
});
// // verify copied URL in clipboard // // verify copied URL in clipboard
// await dashboard.expandedForm.copyUrlButton.click(); // await dashboard.expandedForm.copyUrlButton.click();
@ -92,10 +83,7 @@ test.describe('Expanded form URL', () => {
// access a new rowID using URL // access a new rowID using URL
await dashboard.expandedForm.escape(); await dashboard.expandedForm.escape();
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: '2' }); await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: '2' });
await dashboard.expandedForm.verify({
header: 'Algeria',
url: 'rowId=2',
});
await dashboard.expandedForm.escape(); await dashboard.expandedForm.escape();
// visit invalid rowID // visit invalid rowID
@ -107,28 +95,19 @@ test.describe('Expanded form URL', () => {
// Nested URL // Nested URL
await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: '1' }); await dashboard.expandedForm.gotoUsingUrlAndRowId({ rowId: '1' });
await dashboard.expandedForm.verify({
header: 'Afghanistan',
url: 'rowId=1',
});
await dashboard.expandedForm.openChildCard({ await dashboard.expandedForm.openChildCard({
column: 'Cities', column: 'Cities',
title: 'Kabul', title: 'Kabul',
}); });
await dashboard.rootPage.waitForTimeout(1000); await dashboard.rootPage.waitForTimeout(1000);
await dashboard.expandedForm.verify({
header: 'Kabul',
url: 'rowId=1',
});
await dashboard.expandedForm.verifyCount({ count: 2 }); await dashboard.expandedForm.verifyCount({ count: 2 });
// close child card // close child card
await dashboard.expandedForm.close(); await dashboard.expandedForm.close();
await dashboard.childList.close(); await dashboard.childList.close();
await dashboard.expandedForm.verify({
header: 'Afghanistan',
url: 'rowId=1',
});
await dashboard.expandedForm.close(); await dashboard.expandedForm.close();
} }

4
tests/playwright/tests/db/features/keyboardShortcuts.spec.ts

@ -130,9 +130,7 @@ test.describe('Verify shortcuts', () => {
// Space to open expanded row and Meta + Space to save // Space to open expanded row and Meta + Space to save
await grid.cell.click({ index: 1, columnHeader: 'Country' }); await grid.cell.click({ index: 1, columnHeader: 'Country' });
await page.keyboard.press('Space'); await page.keyboard.press('Space');
await dashboard.expandedForm.verify({
header: 'Algeria',
});
await dashboard.expandedForm.fillField({ columnTitle: 'Country', value: 'NewAlgeria' }); await dashboard.expandedForm.fillField({ columnTitle: 'Country', value: 'NewAlgeria' });
await dashboard.expandedForm.save(); await dashboard.expandedForm.save();
await dashboard.expandedForm.escape(); await dashboard.expandedForm.escape();

Loading…
Cancel
Save