Browse Source

Merge pull request #6416 from nocodb/feat/gallery-ui

feat: gallery ui revamp
pull/6447/head
Raju Udava 1 year ago committed by GitHub
parent
commit
eebfd7a356
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/nc-gui/components.d.ts
  2. 12
      packages/nc-gui/components/cell/Checkbox.vue
  3. 14
      packages/nc-gui/components/cell/attachment/index.vue
  4. 130
      packages/nc-gui/components/smartsheet/Gallery.vue
  5. 2
      packages/nc-gui/components/virtual-cell/Formula.vue
  6. 2
      packages/nc-gui/components/virtual-cell/Links.vue
  7. 7
      packages/nc-gui/components/virtual-cell/QrCode.vue
  8. 16
      packages/nc-gui/components/virtual-cell/barcode/JsBarcodeWrapper.vue
  9. 16
      packages/nocodb/src/helpers/getAst.ts
  10. 2
      tests/playwright/pages/Dashboard/ViewSidebar/index.ts

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

@ -120,6 +120,7 @@ declare module '@vue/runtime-core' {
MdiChatProcessingOutline: typeof import('~icons/mdi/chat-processing-outline')['default'] MdiChatProcessingOutline: typeof import('~icons/mdi/chat-processing-outline')['default']
MdiCheck: typeof import('~icons/mdi/check')['default'] MdiCheck: typeof import('~icons/mdi/check')['default']
MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default'] MdiChevronDown: typeof import('~icons/mdi/chevron-down')['default']
MdiChevronLeft: typeof import('~icons/mdi/chevron-left')['default']
MdiChevronRight: typeof import('~icons/mdi/chevron-right')['default'] MdiChevronRight: typeof import('~icons/mdi/chevron-right')['default']
MdiCircleMedium: typeof import('~icons/mdi/circle-medium')['default'] MdiCircleMedium: typeof import('~icons/mdi/circle-medium')['default']
MdiClose: typeof import('~icons/mdi/close')['default'] MdiClose: typeof import('~icons/mdi/close')['default']

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

@ -36,6 +36,8 @@ const isForm = inject(IsFormInj)
const isEditColumnMenu = inject(EditColumnInj, ref(false)) const isEditColumnMenu = inject(EditColumnInj, ref(false))
const isGallery = inject(IsGalleryInj, ref(false))
const readOnly = inject(ReadonlyInj) const readOnly = inject(ReadonlyInj)
const checkboxMeta = computed(() => { const checkboxMeta = computed(() => {
@ -80,18 +82,14 @@ useSelectedCellKeyupListener(active, (e) => {
<div <div
class="flex cursor-pointer w-full h-full" class="flex cursor-pointer w-full h-full"
:class="{ :class="{
'justify-center': !isForm, 'justify-center': !isForm || !isGallery,
'w-full': isForm, 'w-full flex-start': isForm || isGallery,
'nc-cell-hover-show': !vModel && !readOnly, 'nc-cell-hover-show': !vModel && !readOnly,
'opacity-0': readOnly && !vModel, 'opacity-0': readOnly && !vModel,
}" }"
@click="onClick(false, $event)" @click="onClick(false, $event)"
> >
<div <div class="items-center" :class="{ 'w-full justify-start': isEditColumnMenu || isGallery }" @click="onClick(true)">
class="items-center"
:class="{ '!ml-[-8px]': readOnly, 'w-full justify-start': isEditColumnMenu }"
@click="onClick(true)"
>
<div :class="{ 'bg-gray-100 rounded-full ': !vModel }"> <div :class="{ 'bg-gray-100 rounded-full ': !vModel }">
<Transition name="layout" mode="out-in" :duration="100"> <Transition name="layout" mode="out-in" :duration="100">
<component <component

14
packages/nc-gui/components/cell/attachment/index.vue

@ -7,6 +7,7 @@ import {
CurrentCellInj, CurrentCellInj,
DropZoneRef, DropZoneRef,
IsExpandedFormOpenInj, IsExpandedFormOpenInj,
IsGalleryInj,
RowHeightInj, RowHeightInj,
iconMap, iconMap,
inject, inject,
@ -43,6 +44,8 @@ const currentCellRef = inject(CurrentCellInj, dropZoneInjection.value)
const isLockedMode = inject(IsLockedInj, ref(false)) const isLockedMode = inject(IsLockedInj, ref(false))
const isGallery = inject(IsGalleryInj, ref(false))
const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false)) const isExpandedForm = inject(IsExpandedFormOpenInj, ref(false))
const { isSharedForm } = useSmartsheetStoreOrThrow()! const { isSharedForm } = useSmartsheetStoreOrThrow()!
@ -200,8 +203,8 @@ const rowHeight = inject(RowHeightInj, ref())
<template v-if="visibleItems.length"> <template v-if="visibleItems.length">
<div <div
ref="sortableRef" ref="sortableRef"
:class="{ dragging, 'justify-center': !isExpandedForm }" :class="{ 'justify-center': !isExpandedForm && !isGallery }"
class="flex cursor-pointer items-center flex-wrap gap-2 py-1.5 scrollbar-thin-dull overflow-hidden mt-0 items-start" class="flex cursor-pointer w-full items-center flex-wrap gap-2 py-1.5 scrollbar-thin-dull overflow-hidden mt-0 items-start"
:style="{ :style="{
maxHeight: isForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`, maxHeight: isForm ? undefined : `max(${(rowHeight || 1) * 1.8}rem, 41px)`,
}" }"
@ -215,7 +218,12 @@ const rowHeight = inject(RowHeightInj, ref())
<div <div
class="nc-attachment flex items-center flex-col flex-wrap justify-center" class="nc-attachment flex items-center flex-col flex-wrap justify-center"
:class="{ 'ml-2': active }" :class="{ 'ml-2': active }"
@click.stop="selectedImage = item" @click="
() => {
if (isGallery) return
selectedImage = item
}
"
> >
<LazyCellAttachmentImage <LazyCellAttachmentImage
:alt="item.title || `#${i}`" :alt="item.title || `#${i}`"

130
packages/nc-gui/components/smartsheet/Gallery.vue

@ -19,7 +19,7 @@ import {
extractPkFromRow, extractPkFromRow,
inject, inject,
isImage, isImage,
isLTAR, isPrimary,
nextTick, nextTick,
provide, provide,
ref, ref,
@ -59,6 +59,8 @@ provide(IsFormInj, ref(false))
provide(IsGalleryInj, ref(true)) provide(IsGalleryInj, ref(true))
provide(IsGridInj, ref(false)) provide(IsGridInj, ref(false))
provide(RowHeightInj, ref(1 as const))
const isPublic = inject(IsPublicInj, ref(false)) const isPublic = inject(IsPublicInj, ref(false))
const fields = inject(FieldsInj, ref([])) const fields = inject(FieldsInj, ref([]))
@ -69,7 +71,9 @@ const router = useRouter()
const { getPossibleAttachmentSrc } = useAttachment() const { getPossibleAttachmentSrc } = useAttachment()
const fieldsWithoutCover = computed(() => fields.value.filter((f) => f.id !== galleryData.value?.fk_cover_image_col_id)) const fieldsWithoutDisplay = computed(() => fields.value.filter((f) => !isPrimary(f)))
const displayField = computed(() => meta.value?.columns?.find((c) => c.pv) ?? null)
const coverImageColumn: any = computed(() => const coverImageColumn: any = computed(() =>
meta.value?.columnsById meta.value?.columnsById
@ -142,9 +146,8 @@ const expandForm = (row: RowType, state?: Record<string, any>) => {
const expandFormClick = async (e: MouseEvent, row: RowType) => { const expandFormClick = async (e: MouseEvent, row: RowType) => {
const target = e.target as HTMLElement const target = e.target as HTMLElement
if (target && !target.closest('.gallery-carousel')) { if (target.closest('.arrow') || target.closest('.slick-dots')) return
expandForm(row) expandForm(row)
}
} }
openNewRecordFormHook?.on(async () => { openNewRecordFormHook?.on(async () => {
@ -229,7 +232,7 @@ watch(
</template> </template>
<div <div
class="flex flex-col w-full nc-gallery nc-scrollbar-md" class="flex flex-col w-full nc-gallery nc-scrollbar-md bg-[#fbfbfb]"
data-testid="nc-gallery-wrapper" data-testid="nc-gallery-wrapper"
style="height: calc(100% - var(--topbar-height) + 0.7rem)" style="height: calc(100% - var(--topbar-height) + 0.7rem)"
:class="{ :class="{
@ -241,19 +244,23 @@ watch(
<a-skeleton-input v-for="index of Array(20)" :key="index" class="!min-w-60.5 !h-96 !rounded-md overflow-hidden" /> <a-skeleton-input v-for="index of Array(20)" :key="index" class="!min-w-60.5 !h-96 !rounded-md overflow-hidden" />
</div> </div>
</div> </div>
<div v-else class="nc-gallery-container grid gap-2 my-4 px-3"> <div v-else class="nc-gallery-container grid gap-3 my-4 px-3">
<div v-for="(record, rowIndex) in data" :key="`record-${record.row.id}`"> <div v-for="(record, rowIndex) in data" :key="`record-${record.row.id}`">
<LazySmartsheetRow :row="record"> <LazySmartsheetRow :row="record">
<a-card <a-card
hoverable class="!rounded-lg h-full border-gray-200 border-1 group overflow-hidden break-all max-w-[450px] shadow-sm hover:shadow-md"
class="!rounded-lg h-full overflow-hidden break-all max-w-[450px]" :body-style="{ padding: '0px' }"
:data-testid="`nc-gallery-card-${record.row.id}`" :data-testid="`nc-gallery-card-${record.row.id}`"
:style="isPublic ? { cursor: 'default' } : { cursor: 'pointer' }" :style="isPublic ? { cursor: 'default' } : { cursor: 'pointer' }"
@click="expandFormClick($event, record)" @click="expandFormClick($event, record)"
@contextmenu="showContextMenu($event, { row: rowIndex })" @contextmenu="showContextMenu($event, { row: rowIndex })"
> >
<template v-if="galleryData?.fk_cover_image_col_id" #cover> <template v-if="galleryData?.fk_cover_image_col_id" #cover>
<a-carousel v-if="!reloadAttachments && attachments(record).length" autoplay class="gallery-carousel" arrows> <a-carousel
v-if="!reloadAttachments && attachments(record).length"
class="gallery-carousel !border-b-1 !border-gray-200"
arrows
>
<template #customPaging> <template #customPaging>
<a> <a>
<div class="pt-[12px]"> <div class="pt-[12px]">
@ -263,41 +270,73 @@ watch(
</template> </template>
<template #prevArrow> <template #prevArrow>
<div style="z-index: 1"></div> <div class="z-10 arrow">
<MdiChevronLeft
class="text-gray-700 w-6 h-6 absolute left-1.5 bottom-[-90px] !opacity-0 !group-hover:opacity-100 !bg-white border-1 border-gray-200 rounded-md transition"
/>
</div>
</template> </template>
<template #nextArrow> <template #nextArrow>
<div style="z-index: 1"></div> <div class="z-10 arrow">
<MdiChevronRight
class="text-gray-700 w-6 h-6 absolute right-1.5 bottom-[-90px] !opacity-0 !group-hover:opacity-100 !bg-white border-1 border-gray-200 rounded-md transition"
/>
</div>
</template> </template>
<template v-for="(attachment, index) in attachments(record)"> <template v-for="(attachment, index) in attachments(record)">
<LazyCellAttachmentImage <LazyCellAttachmentImage
v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)" v-if="isImage(attachment.title, attachment.mimetype ?? attachment.type)"
:key="`carousel-${record.row.id}-${index}`" :key="`carousel-${record.row.id}-${index}`"
class="h-52 object-contain" class="h-52 object-cover"
:srcs="getPossibleAttachmentSrc(attachment)" :srcs="getPossibleAttachmentSrc(attachment)"
@click="expandFormClick($event, record)"
/> />
</template> </template>
</a-carousel> </a-carousel>
<div v-else class="h-52 w-full !flex flex-row items-center justify-center"> <div v-else class="h-52 w-full !flex flex-row !border-b-1 !border-gray-200 items-center justify-center">
<img class="object-contain w-[48px] h-[48px]" src="~assets/icons/FileIconImageBox.png" /> <img class="object-contain w-[48px] h-[48px]" src="~assets/icons/FileIconImageBox.png" />
</div> </div>
</template> </template>
<h2 v-if="displayField" class="text-base mt-6 mx-3 font-bold">
<div v-for="col in fieldsWithoutCover" :key="`record-${record.row.id}-${col.id}`"> <LazySmartsheetVirtualCell
<div v-if="isVirtualCol(displayField)"
v-if="!isRowEmpty(record, col) || isLTAR(col.uidt, col.colOptions)" v-model="record.row[displayField.title]"
class="flex flex-col space-y-1 px-4 mb-6 bg-gray-50 rounded-lg w-full" class="!text-gray-600"
> :column="displayField"
<div class="flex flex-row w-full justify-start border-b-1 border-gray-100 py-2.5"> :row="record"
<div class="w-full text-gray-600"> />
<LazySmartsheetHeaderVirtualCell v-if="isVirtualCol(col)" :column="col" :hide-menu="true" />
<LazySmartsheetCell
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" /> v-else
v-model="record.row[displayField.title]"
class="!text-gray-600"
:column="displayField"
:edit-enabled="false"
:read-only="true"
/>
</h2>
<div v-for="col in fieldsWithoutDisplay" :key="`record-${record.row.id}-${col.id}`">
<div class="flex flex-col ml-2 !pr-3.5 !mb-[0.75rem] rounded-lg w-full">
<div class="flex flex-row w-full justify-start scale-75">
<div class="w-full pb-1 text-gray-300">
<LazySmartsheetHeaderVirtualCell
v-if="isVirtualCol(col)"
:column="col"
:hide-menu="true"
:hide-icon="true"
/>
<LazySmartsheetHeaderCell v-else :column="col" :hide-menu="true" :hide-icon="true" />
</div> </div>
</div> </div>
<div class="flex flex-row w-full pb-3 pt-2 pl-2 items-center justify-start"> <div
v-if="!isRowEmpty(record, col)"
class="flex flex-row w-full text-gray-700 px-1 mt-[-0.25rem] items-center justify-start"
>
<LazySmartsheetVirtualCell <LazySmartsheetVirtualCell
v-if="isVirtualCol(col)" v-if="isVirtualCol(col)"
v-model="record.row[col.title]" v-model="record.row[col.title]"
@ -313,6 +352,9 @@ watch(
:read-only="true" :read-only="true"
/> />
</div> </div>
<div v-else class="flex flex-row w-full h-[1.375rem] pl-1 items-center justify-start">
<span class="bg-gray-200 h-2 w-16 rounded-md"></span>
</div>
</div> </div>
</div> </div>
</a-card> </a-card>
@ -352,51 +394,35 @@ watch(
<style scoped> <style scoped>
.nc-gallery-container { .nc-gallery-container {
grid-auto-rows: 1fr; @apply auto-rows-[1fr] grid-cols-[repeat(auto-fit,minmax(250px,1fr))];
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
} }
:deep(.slick-dots li button) { :deep(.slick-dots li button) {
background-color: black; @apply !bg-black;
} }
.ant-carousel.gallery-carousel :deep(.slick-dots) { .ant-carousel.gallery-carousel :deep(.slick-dots) {
position: relative; @apply !w-auto absolute h-auto bottom-[-15px] absolute h-auto;
height: auto; height: auto;
bottom: 0;
} }
.ant-carousel.gallery-carousel :deep(.slick-dots li div > div) { .ant-carousel.gallery-carousel :deep(.slick-dots li div > div) {
background: #000; @apply rounded-full border-0 cursor-pointer block opacity-100 p-0 outline-none transition-all duration-500 text-transparent h-2 w-2 bg-[#d9d9d9];
border: 0;
border-radius: 1px;
color: transparent;
cursor: pointer;
display: block;
font-size: 0; font-size: 0;
height: 3px;
opacity: 0.3;
outline: none;
padding: 0;
transition: all 0.5s;
width: 100%;
} }
.ant-carousel.gallery-carousel :deep(.slick-dots li.slick-active div > div) { .ant-carousel.gallery-carousel :deep(.slick-dots li.slick-active div > div) {
opacity: 1; @apply bg-brand-500 opacity-100;
} }
.ant-carousel.gallery-carousel :deep(.slick-dots li) {
@apply !w-auto;
}
.ant-carousel.gallery-carousel :deep(.slick-prev) { .ant-carousel.gallery-carousel :deep(.slick-prev) {
left: 0; @apply left-0;
height: 100%;
top: 12px;
width: 50%;
} }
.ant-carousel.gallery-carousel :deep(.slick-next) { .ant-carousel.gallery-carousel :deep(.slick-next) {
right: 0; @apply right-0;
height: 100%;
top: 12px;
width: 50%;
} }
</style> </style>

2
packages/nc-gui/components/virtual-cell/Formula.vue

@ -29,7 +29,7 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning, activ
<span>ERR!</span> <span>ERR!</span>
</a-tooltip> </a-tooltip>
<div v-else class="p-2" @dblclick="activateShowEditNonEditableFieldWarning"> <div v-else class="py-2" @dblclick="activateShowEditNonEditableFieldWarning">
<div v-if="urls" v-html="urls" /> <div v-if="urls" v-html="urls" />
<div v-else>{{ result }}</div> <div v-else>{{ result }}</div>

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

@ -97,7 +97,7 @@ const localCellValue = computed<any[]>(() => {
<component <component
:is="isLocked || isUnderLookup ? 'span' : 'a'" :is="isLocked || isUnderLookup ? 'span' : 'a'"
:title="textVal" :title="textVal"
class="text-center pl-3 nc-datatype-link underline-transparent" class="text-center nc-datatype-link underline-transparent"
:class="{ '!text-gray-300': !textVal }" :class="{ '!text-gray-300': !textVal }"
@click.stop.prevent="openChildList" @click.stop.prevent="openChildList"
> >

7
packages/nc-gui/components/virtual-cell/QrCode.vue

@ -1,12 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useQRCode } from '@vueuse/integrations/useQRCode' import { useQRCode } from '@vueuse/integrations/useQRCode'
import type QRCode from 'qrcode' import type QRCode from 'qrcode'
import { RowHeightInj, computed, inject, ref } from '#imports' import { IsGalleryInj, RowHeightInj, computed, inject, ref } from '#imports'
const maxNumberOfAllowedCharsForQrValue = 2000 const maxNumberOfAllowedCharsForQrValue = 2000
const cellValue = inject(CellValueInj) const cellValue = inject(CellValueInj)
const isGallery = inject(IsGalleryInj, ref(false))
const qrValue = computed(() => String(cellValue?.value)) const qrValue = computed(() => String(cellValue?.value))
const tooManyCharsForQrCode = computed(() => qrValue?.value.length > maxNumberOfAllowedCharsForQrValue) const tooManyCharsForQrCode = computed(() => qrValue?.value.length > maxNumberOfAllowedCharsForQrValue)
@ -36,6 +38,7 @@ const qrCodeLarge = useQRCode(qrValue, {
const modalVisible = ref(false) const modalVisible = ref(false)
const showQrModal = (ev: MouseEvent) => { const showQrModal = (ev: MouseEvent) => {
if (isGallery.value) return
ev.stopPropagation() ev.stopPropagation()
modalVisible.value = true modalVisible.value = true
} }
@ -65,7 +68,7 @@ const { showEditNonEditableFieldWarning, showClearNonEditableFieldWarning } = us
</div> </div>
<img <img
v-if="showQrCode && rowHeight" v-if="showQrCode && rowHeight"
class="mx-auto" :class="{ 'mx-auto': !isGallery }"
:style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem` }" :style="{ height: rowHeight ? `${rowHeight * 1.4}rem` : `1.4rem` }"
:src="qrCode" :src="qrCode"
alt="QR Code" alt="QR Code"

16
packages/nc-gui/components/virtual-cell/barcode/JsBarcodeWrapper.vue

@ -1,6 +1,6 @@
<script lang="ts" setup> <script lang="ts" setup>
import JsBarcode from 'jsbarcode' import JsBarcode from 'jsbarcode'
import { onMounted } from '#imports' import { IsGalleryInj, onMounted } from '#imports'
const props = defineProps({ const props = defineProps({
barcodeValue: { type: String, required: true }, barcodeValue: { type: String, required: true },
@ -10,6 +10,8 @@ const props = defineProps({
const emit = defineEmits(['onClickBarcode']) const emit = defineEmits(['onClickBarcode'])
const isGallery = inject(IsGalleryInj, ref(false))
const barcodeSvgRef = ref<HTMLElement>() const barcodeSvgRef = ref<HTMLElement>()
const errorForCurrentInput = ref(false) const errorForCurrentInput = ref(false)
@ -33,6 +35,7 @@ const generate = () => {
} }
const onBarcodeClick = (ev: MouseEvent) => { const onBarcodeClick = (ev: MouseEvent) => {
if (isGallery.value) return
ev.stopPropagation() ev.stopPropagation()
emit('onClickBarcode') emit('onClickBarcode')
} }
@ -42,6 +45,15 @@ onMounted(generate)
</script> </script>
<template> <template>
<svg v-show="!errorForCurrentInput" ref="barcodeSvgRef" class="w-full" data-testid="barcode" @click="onBarcodeClick"></svg> <svg
v-show="!errorForCurrentInput"
ref="barcodeSvgRef"
:class="{
'w-full': !isGallery,
'w-auto': isGallery,
}"
data-testid="barcode"
@click="onBarcodeClick"
></svg>
<slot v-if="errorForCurrentInput" name="barcodeRenderError" /> <slot v-if="errorForCurrentInput" name="barcodeRenderError" />
</template> </template>

16
packages/nocodb/src/helpers/getAst.ts

@ -5,7 +5,7 @@ import type {
LookupColumn, LookupColumn,
Model, Model,
} from '~/models'; } from '~/models';
import { View } from '~/models'; import { GalleryView, View } from '~/models';
const getAst = async ({ const getAst = async ({
query, query,
@ -32,6 +32,14 @@ const getAst = async ({
dependencyFields.nested = dependencyFields.nested || {}; dependencyFields.nested = dependencyFields.nested || {};
dependencyFields.fieldsSet = dependencyFields.fieldsSet || new Set(); dependencyFields.fieldsSet = dependencyFields.fieldsSet || new Set();
let coverImageId;
if (view) {
const gallery = await GalleryView.get(view.id);
if (gallery) {
coverImageId = gallery.fk_cover_image_col_id;
}
}
if (!model.columns?.length) await model.getColumns(); if (!model.columns?.length) await model.getColumns();
// extract only pk and pv // extract only pk and pv
@ -59,7 +67,7 @@ const getAst = async ({
} }
let allowedCols = null; let allowedCols = null;
if (view) if (view) {
allowedCols = (await View.getColumns(view.id)).reduce( allowedCols = (await View.getColumns(view.id)).reduce(
(o, c) => ({ (o, c) => ({
...o, ...o,
@ -67,6 +75,10 @@ const getAst = async ({
}), }),
{}, {},
); );
if (coverImageId) {
allowedCols[coverImageId] = 1;
}
}
const ast = await model.columns.reduce(async (obj, col: Column) => { const ast = await model.columns.reduce(async (obj, col: Column) => {
let value: number | boolean | { [key: string]: any } = 1; let value: number | boolean | { [key: string]: any } = 1;

2
tests/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -59,7 +59,7 @@ export class ViewSidebarPage extends BasePage {
await this.rootPage.locator('input[id="form_item_title"]:visible').waitFor({ state: 'visible' }); await this.rootPage.locator('input[id="form_item_title"]:visible').waitFor({ state: 'visible' });
await this.rootPage.locator('input[id="form_item_title"]:visible').fill(title); await this.rootPage.locator('input[id="form_item_title"]:visible').fill(title);
const submitAction = () => const submitAction = () =>
this.rootPage.locator('.ant-modal-content').locator('button.ant-btn.ant-btn-primary').click(); this.rootPage.locator('.ant-modal-content').locator('button.ant-btn.ant-btn-primary').click({ force: true });
await this.waitForResponse({ await this.waitForResponse({
httpMethodsToMatch: ['POST'], httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/api/v1/db/meta/tables/', requestUrlPathToMatch: '/api/v1/db/meta/tables/',

Loading…
Cancel
Save