* chore: some missing changes Signed-off-by: mertmit <mertmit99@gmail.com> * chore: sync refactored expanded form Signed-off-by: mertmit <mertmit99@gmail.com> --------- Signed-off-by: mertmit <mertmit99@gmail.com>pull/7885/merge
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 592 B |
After Width: | Height: | Size: 592 B |
After Width: | Height: | Size: 799 B |
After Width: | Height: | Size: 784 B |
After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.0 KiB |
@ -0,0 +1,43 @@
|
||||
<script lang="ts" setup> |
||||
/* interface */ |
||||
|
||||
const props = defineProps<{ |
||||
disabled?: boolean |
||||
tooltip?: string |
||||
items: { |
||||
label: string |
||||
value: string |
||||
}[] |
||||
}>() |
||||
|
||||
const modelValue = defineModel<string>() |
||||
</script> |
||||
|
||||
<template> |
||||
<NcTooltip :disabled="!props.tooltip"> |
||||
<template #title> |
||||
{{ props.tooltip }} |
||||
</template> |
||||
<NcDropdown :disabled="props.disabled" :class="{ 'pointer-events-none opacity-50': props.disabled }"> |
||||
<slot /> |
||||
<template #overlay> |
||||
<div class="flex flex-col gap-1 p-1"> |
||||
<div |
||||
v-for="item of props.items" |
||||
:key="item.value" |
||||
class="flex items-center justify-between px-2 py-1 rounded-md transition-colors hover:bg-gray-100 cursor-pointer" |
||||
:class="{ |
||||
'bg-gray-100': modelValue === item.value, |
||||
}" |
||||
@click="modelValue = item.value" |
||||
> |
||||
<span> |
||||
{{ item.label }} |
||||
</span> |
||||
<GeneralIcon v-if="modelValue === item.value" icon="check" class="text-primary" /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</NcDropdown> |
||||
</NcTooltip> |
||||
</template> |
@ -0,0 +1,66 @@
|
||||
<script lang="ts" setup> |
||||
/* interface */ |
||||
|
||||
const props = defineProps<{ |
||||
disabled?: boolean |
||||
}>() |
||||
|
||||
const modelValue = defineModel<string>() |
||||
|
||||
/* internal text */ |
||||
|
||||
const internalText = ref('') |
||||
|
||||
watch( |
||||
modelValue, |
||||
(value) => { |
||||
internalText.value = value || '' |
||||
}, |
||||
{ immediate: true }, |
||||
) |
||||
|
||||
/* edit mode */ |
||||
|
||||
const inputRef = ref() |
||||
const isInEditMode = ref(false) |
||||
|
||||
function goToEditMode() { |
||||
if (props.disabled) { |
||||
return |
||||
} |
||||
isInEditMode.value = true |
||||
nextTick(() => { |
||||
inputRef.value?.select?.() |
||||
}) |
||||
} |
||||
|
||||
function finishEdit() { |
||||
isInEditMode.value = false |
||||
modelValue.value = internalText.value |
||||
} |
||||
|
||||
function cancelEdit() { |
||||
isInEditMode.value = false |
||||
internalText.value = modelValue.value || '' |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="inline-block"> |
||||
<template v-if="!isInEditMode"> |
||||
<span :class="{ 'cursor-pointer': !disabled }" @dblclick="goToEditMode()"> |
||||
{{ internalText }} |
||||
</span> |
||||
</template> |
||||
<template v-else> |
||||
<a-input |
||||
ref="inputRef" |
||||
v-model:value="internalText" |
||||
class="!rounded-lg !w-72" |
||||
@blur="finishEdit()" |
||||
@keyup.enter="finishEdit()" |
||||
@keyup.esc="cancelEdit()" |
||||
/> |
||||
</template> |
||||
</div> |
||||
</template> |
@ -0,0 +1,310 @@
|
||||
<script lang="ts" setup> |
||||
import { type ColumnType, isLinksOrLTAR, isSystemColumn, isVirtualCol } from 'nocodb-sdk' |
||||
|
||||
const props = withDefaults( |
||||
defineProps<{ |
||||
row: Row |
||||
columns: (ColumnType & { [key: string]: any })[] |
||||
attachmentColumn?: ColumnType |
||||
displayValueColumn?: ColumnType |
||||
isLoading?: boolean |
||||
isSelected?: boolean |
||||
displayValueClassName?: string |
||||
}>(), |
||||
{ |
||||
isLoading: false, |
||||
isSelected: false, |
||||
displayValueClassName: '', |
||||
}, |
||||
) |
||||
|
||||
provide(IsExpandedFormOpenInj, ref(true)) |
||||
|
||||
provide(RowHeightInj, ref(1 as const)) |
||||
|
||||
provide(IsFormInj, ref(false)) |
||||
|
||||
const { row: currentRow, columns: allColumns, isSelected, isLoading } = toRefs(props) |
||||
|
||||
useProvideSmartsheetRowStore(currentRow) |
||||
|
||||
const readOnly = inject(ReadonlyInj, ref(false)) |
||||
|
||||
const { isMobileMode } = useGlobal() |
||||
|
||||
const { getPossibleAttachmentSrc } = useAttachment() |
||||
|
||||
interface Attachment { |
||||
url: string |
||||
title: string |
||||
type: string |
||||
mimetype: string |
||||
} |
||||
|
||||
const displayValueColumn = computed(() => { |
||||
return props.displayValueColumn || (allColumns.value || []).find((c) => c?.pv ?? null) || (allColumns.value || [])?.[0] |
||||
}) |
||||
|
||||
const displayValue = computed(() => { |
||||
return displayValueColumn.value?.title ? currentRow.value.row[displayValueColumn.value?.title] : null |
||||
}) |
||||
|
||||
const attachmentColumn = computed(() => { |
||||
return props.attachmentColumn || (allColumns.value || []).find((c) => isAttachment(c)) |
||||
}) |
||||
|
||||
const attachments: ComputedRef<Attachment[]> = computed(() => { |
||||
try { |
||||
if (attachmentColumn.value?.title && currentRow.value.row[attachmentColumn.value.title]) { |
||||
return typeof currentRow.value.row[attachmentColumn.value.title] === 'string' |
||||
? JSON.parse(currentRow.value.row[attachmentColumn.value.title]) |
||||
: currentRow.value.row[attachmentColumn.value.title] |
||||
} |
||||
return [] |
||||
} catch (e) { |
||||
return [] |
||||
} |
||||
}) |
||||
|
||||
const columnsToRender = computed(() => { |
||||
return allColumns.value |
||||
.filter((c) => { |
||||
const isDisplayValueColumn = c.id === displayValueColumn.value?.id |
||||
|
||||
return ( |
||||
!isDisplayValueColumn && !isSystemColumn(c) && !isPrimary(c) && !isLinksOrLTAR(c) && !isAiButton(c) && !isAttachment(c) |
||||
) |
||||
}) |
||||
.sort((a, b) => { |
||||
return (a.meta?.defaultViewColOrder ?? Infinity) - (b.meta?.defaultViewColOrder ?? Infinity) |
||||
}) |
||||
.slice(0, isMobileMode.value ? 1 : 3) |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="nc-list-item-wrapper group px-[1px] hover:bg-gray-50 border-y-1 border-gray-200 border-t-transparent w-full"> |
||||
<a-card |
||||
tabindex="0" |
||||
class="nc-list-item !outline-none transition-all relative group-hover:bg-gray-50 cursor-auto" |
||||
:class="{ |
||||
'!bg-white': isLoading, |
||||
'!hover:bg-white': readOnly, |
||||
}" |
||||
:body-style="{ padding: '6px 10px !important', borderRadius: 0 }" |
||||
:hoverable="false" |
||||
> |
||||
<div class="flex items-center gap-3"> |
||||
<template v-if="attachmentColumn"> |
||||
<div v-if="attachments && attachments.length"> |
||||
<a-carousel autoplay class="!w-11 !h-11 !max-h-11 !max-w-11"> |
||||
<template #customPaging> </template> |
||||
<template v-for="(attachmentObj, index) in attachments"> |
||||
<LazyCellAttachmentPreviewImage |
||||
v-if="isImage(attachmentObj.title, attachmentObj.mimetype ?? attachmentObj.type)" |
||||
:key="`carousel-${attachmentObj.title}-${index}`" |
||||
class="!w-11 !h-11 !max-h-11 !max-w-11object-cover !rounded-l-xl" |
||||
:srcs="getPossibleAttachmentSrc(attachmentObj, 'tiny')" |
||||
/> |
||||
</template> |
||||
</a-carousel> |
||||
</div> |
||||
<div |
||||
v-else |
||||
class="h-11 w-11 !min-h-11 !min-w-11 !max-h-11 !max-w-11 !flex flex-row items-center !rounded-l-xl justify-center" |
||||
> |
||||
<GeneralIcon class="w-full h-full !text-6xl !leading-10 !text-transparent rounded-lg" icon="fileImage" /> |
||||
</div> |
||||
</template> |
||||
|
||||
<div class="flex-1 flex flex-col gap-1 justify-center overflow-hidden"> |
||||
<div |
||||
v-if="displayValueColumn && displayValue" |
||||
class="flex justify-start font-semibold text-brand-500 nc-display-value" |
||||
:class="displayValueClassName" |
||||
> |
||||
<NcTooltip class="truncate leading-[20px]" show-on-truncate-only> |
||||
<template #title> |
||||
<LazySmartsheetPlainCell |
||||
v-model="displayValue" |
||||
:column="displayValueColumn" |
||||
class="field-config-plain-cell-value" |
||||
/> |
||||
</template> |
||||
|
||||
<LazySmartsheetPlainCell |
||||
v-model="displayValue" |
||||
:column="displayValueColumn" |
||||
class="field-config-plain-cell-value" |
||||
/> |
||||
</NcTooltip> |
||||
</div> |
||||
|
||||
<div v-if="columnsToRender.length > 0" class="flex ml-[-0.25rem] sm:flex-row xs:(flex-col mt-2) gap-4 min-h-5"> |
||||
<div v-for="column in columnsToRender" :key="column.id" class="sm:(w-1/3 max-w-1/3 overflow-hidden)"> |
||||
<div v-if="!isRowEmpty({ row }, column)" class="flex flex-col gap-[-1]"> |
||||
<NcTooltip class="z-10 flex" placement="bottomLeft" :arrow-point-at-center="false"> |
||||
<template #title> |
||||
<LazySmartsheetHeaderVirtualCell |
||||
v-if="isVirtualCol(column)" |
||||
class="text-gray-100 !text-sm nc-link-record-cell-tooltip" |
||||
:column="column" |
||||
hide-menu |
||||
/> |
||||
<LazySmartsheetHeaderCell |
||||
v-else |
||||
class="text-gray-100 !text-sm nc-link-record-cell-tooltip" |
||||
:column="column" |
||||
hide-menu |
||||
/> |
||||
</template> |
||||
<div class="nc-link-record-cell flex w-full max-w-full"> |
||||
<LazySmartsheetVirtualCell |
||||
v-if="isVirtualCol(column)" |
||||
:model-value="currentRow.row[column.title!]" |
||||
:row="currentRow" |
||||
:column="column" |
||||
/> |
||||
<LazySmartsheetCell |
||||
v-else |
||||
:model-value="currentRow.row[column.title!]" |
||||
:column="column" |
||||
:edit-enabled="false" |
||||
read-only |
||||
/> |
||||
</div> |
||||
</NcTooltip> |
||||
</div> |
||||
<div v-else class="flex flex-row w-full max-w-72 h-5 pl-1 items-center justify-start">-</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<slot name="extraRight"> |
||||
<div class="min-w-5 flex-none"> |
||||
<Transition> |
||||
<GeneralIcon v-if="isSelected" icon="circleCheckSolid" class="flex-none text-primary w-5 h-5" /> |
||||
</Transition> |
||||
</div> |
||||
</slot> |
||||
</div> |
||||
</a-card> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss" scoped> |
||||
:deep(.slick-list) { |
||||
@apply rounded-lg; |
||||
} |
||||
.nc-list-item-link-unlink-btn { |
||||
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.06), 0px 5px 3px -2px rgba(0, 0, 0, 0.02); |
||||
} |
||||
|
||||
.nc-link-record-cell { |
||||
:deep(.nc-cell), |
||||
:deep(.nc-virtual-cell) { |
||||
@apply !text-small !text-gray-600 ml-1; |
||||
|
||||
.nc-cell-field, |
||||
.nc-cell-field-link, |
||||
input, |
||||
textarea { |
||||
@apply !text-small !p-0 m-0; |
||||
} |
||||
|
||||
&:not(.nc-display-value-cell) { |
||||
@apply text-gray-600; |
||||
font-weight: 500; |
||||
|
||||
.nc-cell-field, |
||||
input, |
||||
textarea { |
||||
@apply text-gray-600; |
||||
font-weight: 500; |
||||
} |
||||
} |
||||
|
||||
.nc-cell-field, |
||||
a.nc-cell-field-link, |
||||
input, |
||||
textarea { |
||||
@apply !p-0 m-0; |
||||
} |
||||
|
||||
&.nc-cell-longtext { |
||||
@apply leading-[18px]; |
||||
|
||||
textarea { |
||||
@apply pr-2; |
||||
} |
||||
|
||||
.long-text-wrapper { |
||||
@apply !min-h-4; |
||||
|
||||
.nc-rich-text-grid { |
||||
@apply pl-0 -ml-1; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.ant-picker-input { |
||||
@apply text-small leading-4; |
||||
font-weight: 500; |
||||
|
||||
input { |
||||
@apply text-small leading-4; |
||||
font-weight: 500; |
||||
} |
||||
} |
||||
|
||||
.ant-select:not(.ant-select-customize-input) { |
||||
.ant-select-selector { |
||||
@apply !border-none flex-nowrap pr-4.5; |
||||
} |
||||
.ant-select-arrow { |
||||
@apply right-[3px]; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.nc-link-record-cell-tooltip { |
||||
@apply !bg-transparent !hover:bg-transparent; |
||||
|
||||
:deep(.nc-cell-icon) { |
||||
@apply !ml-0; |
||||
} |
||||
:deep(.name) { |
||||
@apply !text-small; |
||||
} |
||||
} |
||||
</style> |
||||
|
||||
<style lang="scss"> |
||||
.nc-list-item { |
||||
@apply border-1 border-transparent; |
||||
|
||||
&:focus-visible { |
||||
@apply border-brand-500; |
||||
box-shadow: 0 0 0 1px #3366ff; |
||||
} |
||||
&:hover { |
||||
.nc-text-area-expand-btn { |
||||
@apply !hidden; |
||||
} |
||||
} |
||||
.long-text-wrapper { |
||||
@apply select-none pointer-events-none; |
||||
.nc-readonly-rich-text-wrapper { |
||||
@apply !min-h-5 !max-h-5; |
||||
} |
||||
.nc-rich-text-embed { |
||||
@apply -mt-0.5; |
||||
.nc-textarea-rich-editor { |
||||
@apply !overflow-hidden; |
||||
.ProseMirror { |
||||
@apply !overflow-hidden line-clamp-1 h-[18px] pt-0.4; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,78 @@
|
||||
<script lang="ts" setup> |
||||
/** |
||||
* @description |
||||
* Tabbed select component |
||||
* |
||||
* @example |
||||
* <NcSelectTab :items="items" v-model="modelValue" /> |
||||
*/ |
||||
|
||||
interface Props { |
||||
disabled?: boolean |
||||
tooltip?: string |
||||
items: { |
||||
icon: keyof typeof iconMap |
||||
title?: string |
||||
value: string |
||||
}[] |
||||
} |
||||
|
||||
const props = defineProps<Props>() |
||||
|
||||
const modelValue = defineModel<string>() |
||||
</script> |
||||
|
||||
<template> |
||||
<NcTooltip :disabled="!props.tooltip"> |
||||
<template #title> |
||||
{{ props.tooltip }} |
||||
</template> |
||||
<div |
||||
class="flex flex-row p-1 bg-gray-200 rounded-lg gap-x-0.5" |
||||
:class="{ |
||||
'!cursor-not-allowed opacity-50': props.disabled, |
||||
}" |
||||
> |
||||
<div |
||||
v-for="item of props.items" |
||||
:key="item.value" |
||||
v-e="[`c:project:mode:${item.value}`]" |
||||
class="tab" |
||||
:class="{ |
||||
'pointer-events-none': props.disabled, |
||||
'active': modelValue === item.value, |
||||
}" |
||||
@click="modelValue = item.value" |
||||
> |
||||
<GeneralIcon :icon="item.icon" class="tab-icon" /> |
||||
<div v-if="item.title" class="tab-title nc-tab"> |
||||
{{ $t(item.title) }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</NcTooltip> |
||||
</template> |
||||
|
||||
<style scoped> |
||||
.tab { |
||||
@apply flex flex-row items-center h-6 justify-center px-2 py-1 rounded-md gap-x-2 text-gray-600 hover:text-black cursor-pointer transition-all duration-300 select-none; |
||||
} |
||||
|
||||
.tab-icon { |
||||
font-size: 1rem !important; |
||||
@apply w-4; |
||||
} |
||||
.tab .tab-title { |
||||
@apply min-w-0; |
||||
word-break: keep-all; |
||||
white-space: nowrap; |
||||
display: inline; |
||||
line-height: 0.95; |
||||
} |
||||
|
||||
.active { |
||||
@apply bg-white text-brand-600 hover:text-brand-600; |
||||
|
||||
box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.06), 0px 5px 3px -2px rgba(0, 0, 0, 0.02); |
||||
} |
||||
</style> |
@ -0,0 +1,3 @@
|
||||
<template> |
||||
<div></div> |
||||
</template> |
@ -0,0 +1,3 @@
|
||||
<template> |
||||
<div></div> |
||||
</template> |
@ -0,0 +1,3 @@
|
||||
<template> |
||||
<div></div> |
||||
</template> |
@ -0,0 +1,3 @@
|
||||
<template> |
||||
<div></div> |
||||
</template> |
@ -0,0 +1,214 @@
|
||||
<script lang="ts" setup> |
||||
import { |
||||
type ColumnType, |
||||
isCreatedOrLastModifiedByCol, |
||||
isCreatedOrLastModifiedTimeCol, |
||||
isLinksOrLTAR, |
||||
isSystemColumn, |
||||
isVirtualCol, |
||||
} from 'nocodb-sdk' |
||||
|
||||
/* interface */ |
||||
|
||||
const props = defineProps<{ |
||||
store: ReturnType<typeof useProvideExpandedFormStore> |
||||
fields: ColumnType[] |
||||
hiddenFields: ColumnType[] |
||||
isLoading: boolean |
||||
forceVerticalMode?: boolean |
||||
}>() |
||||
|
||||
const isLoading = toRef(props, 'isLoading') |
||||
|
||||
const isPublic = inject(IsPublicInj, ref(false)) |
||||
|
||||
/* stores */ |
||||
|
||||
const { changedColumns, isNew, loadRow: _loadRow, row: _row } = props.store |
||||
|
||||
const { isUIAllowed } = useRoles() |
||||
const { isMobileMode } = useGlobal() |
||||
|
||||
/* flags */ |
||||
|
||||
const readOnly = computed(() => !isUIAllowed('dataEdit') || isPublic.value) |
||||
|
||||
/* initial focus and scroll fix */ |
||||
|
||||
const cellWrapperEl = ref() |
||||
const mentionedCell = ref('') |
||||
|
||||
/* hidden fields */ |
||||
|
||||
const showHiddenFields = ref(false) |
||||
|
||||
function toggleHiddenFields() { |
||||
showHiddenFields.value = !showHiddenFields.value |
||||
} |
||||
|
||||
/* utilities */ |
||||
|
||||
function isReadOnlyVirtualCell(column: ColumnType) { |
||||
return ( |
||||
isRollup(column) || |
||||
isFormula(column) || |
||||
isBarcode(column) || |
||||
isLookup(column) || |
||||
isQrCode(column) || |
||||
isSystemColumn(column) || |
||||
isCreatedOrLastModifiedTimeCol(column) || |
||||
isCreatedOrLastModifiedByCol(column) |
||||
) |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div |
||||
ref="expandedFormScrollWrapper" |
||||
class="flex flex-col flex-grow gap-6 h-full max-h-full nc-scrollbar-thin items-center w-full p-4 xs:(px-4 pt-4 pb-2 gap-6) children:max-w-[588px] <lg:(children:max-w-[450px])" |
||||
> |
||||
<div |
||||
v-for="(col, i) of fields" |
||||
v-show="!isVirtualCol(col) || !isNew || isLinksOrLTAR(col)" |
||||
:key="col.title" |
||||
:class="`nc-expand-col-${col.title}`" |
||||
:col-id="col.id" |
||||
:data-testid="`nc-expand-col-${col.title}`" |
||||
class="nc-expanded-form-row w-full" |
||||
> |
||||
<div |
||||
class="flex items-start nc-expanded-cell min-h-[37px]" |
||||
:class="{ |
||||
'flex-row sm:(gap-x-2) <lg:(flex-col w-full)': !props.forceVerticalMode, |
||||
'flex-col w-full': props.forceVerticalMode, |
||||
}" |
||||
> |
||||
<div |
||||
class="flex items-center rounded-lg overflow-hidden" |
||||
:class="{ |
||||
'w-45 <lg:(w-full px-0 mb-1) h-[37px] xs:(h-auto)': !props.forceVerticalMode, |
||||
'w-full px-0 mb-1 h-auto': props.forceVerticalMode, |
||||
}" |
||||
> |
||||
<LazySmartsheetHeaderVirtualCell |
||||
v-if="isVirtualCol(col)" |
||||
:column="col" |
||||
class="nc-expanded-cell-header h-full flex-none" |
||||
/> |
||||
<LazySmartsheetHeaderCell v-else :column="col" class="nc-expanded-cell-header flex-none" /> |
||||
</div> |
||||
|
||||
<template v-if="isLoading"> |
||||
<a-skeleton-input active class="h-[37px] flex-none <lg:!w-full lg:flex-1 !rounded-lg !overflow-hidden" size="small" /> |
||||
</template> |
||||
<template v-else> |
||||
<SmartsheetDivDataCell |
||||
v-if="col.title" |
||||
:ref="(el: any) => { if (i) cellWrapperEl = el }" |
||||
class="bg-white flex-1 <lg:w-full px-1 min-h-[37px] flex items-center relative" |
||||
:class="{ |
||||
'w-full': props.forceVerticalMode, |
||||
'!select-text nc-system-field': isReadOnlyVirtualCell(col), |
||||
'!select-text nc-readonly-div-data-cell': readOnly, |
||||
'nc-mentioned-cell': col.id === mentionedCell, |
||||
}" |
||||
> |
||||
<LazySmartsheetVirtualCell |
||||
v-if="isVirtualCol(col)" |
||||
v-model="_row.row[col.title]" |
||||
:class="{ |
||||
'px-1': isReadOnlyVirtualCell(col), |
||||
}" |
||||
:column="col" |
||||
:read-only="readOnly" |
||||
:row="_row" |
||||
/> |
||||
|
||||
<LazySmartsheetCell |
||||
v-else |
||||
v-model="_row.row[col.title]" |
||||
:active="true" |
||||
:column="col" |
||||
:edit-enabled="true" |
||||
:read-only="readOnly" |
||||
@update:model-value="changedColumns.add(col.title)" |
||||
/> |
||||
</SmartsheetDivDataCell> |
||||
</template> |
||||
</div> |
||||
</div> |
||||
|
||||
<div v-if="hiddenFields.length > 0" class="flex w-full <lg:(px-1) items-center py-6"> |
||||
<div class="flex-grow h-px mr-1 bg-gray-100" /> |
||||
<NcButton |
||||
:size="isMobileMode ? 'medium' : 'small'" |
||||
class="flex-shrink !text-sm overflow-hidden !text-gray-500 !font-weight-500" |
||||
type="secondary" |
||||
@click="toggleHiddenFields" |
||||
> |
||||
{{ showHiddenFields ? `Hide ${hiddenFields.length} hidden` : `Show ${hiddenFields.length} hidden` }} |
||||
{{ hiddenFields.length > 1 ? `fields` : `field` }} |
||||
<GeneralIcon icon="chevronDown" :class="showHiddenFields ? 'transform rotate-180' : ''" class="ml-1" /> |
||||
</NcButton> |
||||
<div class="flex-grow h-px ml-1 bg-gray-100" /> |
||||
</div> |
||||
|
||||
<template v-if="hiddenFields.length > 0 && showHiddenFields"> |
||||
<div |
||||
v-for="(col, i) of hiddenFields" |
||||
v-show="isFormula(col) || !isVirtualCol(col) || !isNew || isLinksOrLTAR(col)" |
||||
:key="`${col.id}-${col.title}`" |
||||
:class="`nc-expand-col-${col.title}`" |
||||
:data-testid="`nc-expand-col-${col.title}`" |
||||
class="nc-expanded-form-row w-full" |
||||
> |
||||
<div class="flex items-start flex-row sm:(gap-x-2) <lg:(flex-col w-full) nc-expanded-cell min-h-[37px]"> |
||||
<div class="w-45 <lg:(w-full px-0) h-[37px] xs:(h-auto) flex items-center rounded-lg overflow-hidden"> |
||||
<LazySmartsheetHeaderVirtualCell |
||||
v-if="isVirtualCol(col)" |
||||
:column="col" |
||||
is-hidden-col |
||||
class="nc-expanded-cell-header flex-none" |
||||
/> |
||||
|
||||
<LazySmartsheetHeaderCell v-else :column="col" is-hidden-col class="nc-expanded-cell-header flex-none" /> |
||||
</div> |
||||
|
||||
<template v-if="isLoading"> |
||||
<a-skeleton-input active class="h-[37px] flex-none <lg:!w-full lg:flex-1 !rounded-lg !overflow-hidden" size="small" /> |
||||
</template> |
||||
<template v-else> |
||||
<LazySmartsheetDivDataCell |
||||
v-if="col.title" |
||||
:ref="(el: any) => { if (i) cellWrapperEl = el }" |
||||
class="bg-white flex-1 <lg:w-full px-1 min-h-[37px] flex items-center relative" |
||||
:class="{ |
||||
'!select-text nc-system-field': isReadOnlyVirtualCell(col), |
||||
'!bg-gray-50 !select-text nc-readonly-div-data-cell': readOnly, |
||||
'nc-mentioned-cell': col.id === mentionedCell, |
||||
}" |
||||
> |
||||
<LazySmartsheetVirtualCell |
||||
v-if="isVirtualCol(col)" |
||||
v-model="_row.row[col.title]" |
||||
:column="col" |
||||
:read-only="readOnly" |
||||
:row="_row" |
||||
/> |
||||
|
||||
<LazySmartsheetCell |
||||
v-else |
||||
v-model="_row.row[col.title]" |
||||
:active="true" |
||||
:column="col" |
||||
:edit-enabled="true" |
||||
:read-only="readOnly" |
||||
@update:model-value="changedColumns.add(col.title)" |
||||
/> |
||||
</LazySmartsheetDivDataCell> |
||||
</template> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
</div> |
||||
</template> |
@ -0,0 +1,58 @@
|
||||
<script setup lang="ts"> |
||||
import { isSystemColumn } from 'nocodb-sdk' |
||||
|
||||
/* interface */ |
||||
|
||||
const props = defineProps<{ |
||||
store: ReturnType<typeof useProvideExpandedFormStore> |
||||
}>() |
||||
|
||||
const meta = inject(MetaInj, ref()) |
||||
|
||||
/* flags */ |
||||
|
||||
const maintainDefaultViewOrder = ref(false) |
||||
const useMetaFields = ref(false) |
||||
|
||||
const { fieldsMap, isLocalMode } = useViewColumnsOrThrow() |
||||
|
||||
/* fields */ |
||||
|
||||
const injectedFields = computedInject(FieldsInj, (_fields) => { |
||||
if (useMetaFields.value) { |
||||
if (maintainDefaultViewOrder.value) { |
||||
return (meta.value?.columns ?? []) |
||||
.filter((col) => !isSystemColumn(col)) |
||||
.sort((a, b) => { |
||||
return (a.meta?.defaultViewColOrder ?? Infinity) - (b.meta?.defaultViewColOrder ?? Infinity) |
||||
}) |
||||
} |
||||
|
||||
return (meta.value?.columns ?? []).filter((col) => !isSystemColumn(col)) |
||||
} |
||||
return _fields?.value ?? [] |
||||
}) |
||||
|
||||
const fields = computed(() => injectedFields.value ?? []) |
||||
|
||||
const hiddenFields = computed(() => { |
||||
// todo: figure out when meta.value is undefined |
||||
return (meta.value?.columns ?? []) |
||||
.filter( |
||||
(col) => |
||||
!fields.value?.includes(col) && |
||||
(isLocalMode.value && col?.id && fieldsMap.value[col.id] ? fieldsMap.value[col.id]?.initialShow : true), |
||||
) |
||||
.filter((col) => !isSystemColumn(col)) |
||||
}) |
||||
</script> |
||||
|
||||
<template> |
||||
<SmartsheetExpandedFormPresentorsFieldsColumns |
||||
:store="props.store" |
||||
:fields="fields" |
||||
:hidden-fields="hiddenFields" |
||||
:is-loading="false" |
||||
force-vertical-mode |
||||
/> |
||||
</template> |
@ -0,0 +1,263 @@
|
||||
<script setup lang="ts"> |
||||
import { type ColumnType } from 'nocodb-sdk' |
||||
|
||||
/* interface */ |
||||
|
||||
const props = defineProps<{ |
||||
store: ReturnType<typeof useProvideExpandedFormStore> |
||||
rowId?: string |
||||
fields: ColumnType[] |
||||
hiddenFields: ColumnType[] |
||||
isUnsavedDuplicatedRecordExist: boolean |
||||
isUnsavedFormExist: boolean |
||||
isLoading: boolean |
||||
isSaving: boolean |
||||
newRecordSubmitBtnText?: string |
||||
}>() |
||||
|
||||
const emits = defineEmits(['copy:record-url', 'delete:row', 'save']) |
||||
|
||||
const rowId = toRef(props, 'rowId') |
||||
const fields = toRef(props, 'fields') |
||||
const hiddenFields = toRef(props, 'hiddenFields') |
||||
const isUnsavedDuplicatedRecordExist = toRef(props, 'isUnsavedDuplicatedRecordExist') |
||||
const isUnsavedFormExist = toRef(props, 'isUnsavedFormExist') |
||||
const isLoading = toRef(props, 'isLoading') |
||||
const isSaving = toRef(props, 'isSaving') |
||||
const newRecordSubmitBtnText = toRef(props, 'newRecordSubmitBtnText') |
||||
|
||||
/* stores */ |
||||
|
||||
const { commentsDrawer, changedColumns, isNew, loadRow: _loadRow, row: _row } = props.store |
||||
|
||||
const { isUIAllowed } = useRoles() |
||||
const { isMobileMode } = useGlobal() |
||||
|
||||
/* flags */ |
||||
|
||||
const showRightSections = computed(() => !isNew.value && commentsDrawer.value && isUIAllowed('commentList')) |
||||
|
||||
const canEdit = computed(() => isUIAllowed('dataEdit')) |
||||
</script> |
||||
|
||||
<script lang="ts"> |
||||
export default { |
||||
name: 'ExpandedFormPresentorsFields', |
||||
} |
||||
</script> |
||||
|
||||
<template> |
||||
<div class="h-full flex flex-row"> |
||||
<div |
||||
class="h-full flex xs:w-full flex-col overflow-hidden" |
||||
:class="{ |
||||
'w-full': !showRightSections, |
||||
'flex-1': showRightSections, |
||||
}" |
||||
> |
||||
<SmartsheetExpandedFormPresentorsFieldsColumns |
||||
:store="props.store" |
||||
:fields="fields" |
||||
:hidden-fields="hiddenFields" |
||||
:is-loading="isLoading" |
||||
/> |
||||
|
||||
<div |
||||
v-if="canEdit" |
||||
class="w-full flex items-center justify-end px-2 xs:(p-0 gap-x-4 justify-between)" |
||||
:class="{ |
||||
'xs(border-t-1 border-gray-200)': !isNew, |
||||
}" |
||||
> |
||||
<div v-if="!isNew && isMobileMode" class="p-2"> |
||||
<NcDropdown placement="bottomRight" class="p-2"> |
||||
<NcButton :disabled="isLoading" class="nc-expand-form-more-actions" type="secondary" size="small"> |
||||
<GeneralIcon :class="isLoading ? 'text-gray-300' : 'text-gray-700'" class="text-md" icon="threeDotVertical" /> |
||||
</NcButton> |
||||
|
||||
<template #overlay> |
||||
<NcMenu> |
||||
<NcMenuItem class="text-gray-700" @click="_loadRow()"> |
||||
<div v-e="['c:row-expand:reload']" class="flex gap-2 items-center" data-testid="nc-expanded-form-reload"> |
||||
<component :is="iconMap.reload" class="cursor-pointer" /> |
||||
{{ $t('general.reload') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
<NcMenuItem v-if="rowId" class="text-gray-700" @click="!isNew ? emits('copy:record-url') : () => {}"> |
||||
<div v-e="['c:row-expand:copy-url']" class="flex gap-2 items-center" data-testid="nc-expanded-form-copy-url"> |
||||
<component :is="iconMap.copy" class="cursor-pointer nc-duplicate-row" /> |
||||
{{ $t('labels.copyRecordURL') }} |
||||
</div> |
||||
</NcMenuItem> |
||||
<NcDivider /> |
||||
<NcMenuItem |
||||
v-if="isUIAllowed('dataEdit') && !isNew" |
||||
v-e="['c:row-expand:delete']" |
||||
class="!text-red-500 !hover:bg-red-50" |
||||
@click="!isNew && emits('delete:row')" |
||||
> |
||||
<div data-testid="nc-expanded-form-delete"> |
||||
<component :is="iconMap.delete" class="cursor-pointer nc-delete-row" /> |
||||
Delete record |
||||
</div> |
||||
</NcMenuItem> |
||||
</NcMenu> |
||||
</template> |
||||
</NcDropdown> |
||||
</div> |
||||
<div v-if="isMobileMode" class="p-2"> |
||||
<NcButton |
||||
v-e="['c:row-expand:save']" |
||||
:disabled="changedColumns.size === 0 && !isUnsavedFormExist" |
||||
:loading="isSaving" |
||||
class="nc-expand-form-save-btn !xs:(text-sm) !px-2" |
||||
:class="{ |
||||
'!h-7': !isMobileMode, |
||||
}" |
||||
data-testid="nc-expanded-form-save" |
||||
type="primary" |
||||
:size="isMobileMode ? 'small' : 'xsmall'" |
||||
@click="emits('save')" |
||||
> |
||||
<div class="xs:px-1">{{ newRecordSubmitBtnText ?? isNew ? 'Create Record' : 'Save Record' }}</div> |
||||
</NcButton> |
||||
</div> |
||||
</div> |
||||
<div v-else class="p-2" /> |
||||
</div> |
||||
<div |
||||
v-if="showRightSections && !isUnsavedDuplicatedRecordExist" |
||||
class="nc-comments-drawer border-l-1 relative border-gray-200 bg-gray-50 w-1/3 max-w-[400px] min-w-0 h-full xs:hidden rounded-br-2xl" |
||||
:class="{ |
||||
active: commentsDrawer && isUIAllowed('commentList'), |
||||
}" |
||||
> |
||||
<SmartsheetExpandedFormSidebar :store="store" /> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<style lang="scss"> |
||||
.nc-drawer-expanded-form { |
||||
@apply xs:my-0; |
||||
|
||||
.ant-drawer-content-wrapper { |
||||
@apply !h-[90vh]; |
||||
.ant-drawer-content { |
||||
@apply rounded-t-2xl; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.nc-expanded-cell-header { |
||||
@apply w-full text-gray-500 !font-weight-500 !text-sm xs:(text-gray-600 mb-2 !text-small) pr-3; |
||||
|
||||
svg.nc-cell-icon, |
||||
svg.nc-virtual-cell-icon { |
||||
@apply !w-3.5 !h-3.5; |
||||
} |
||||
} |
||||
|
||||
.nc-expanded-cell-header > :nth-child(2) { |
||||
@apply !text-sm xs:!text-small; |
||||
} |
||||
.nc-expanded-cell-header > :first-child { |
||||
@apply !text-md pl-2 xs:(pl-0 -ml-0.5); |
||||
} |
||||
.nc-expanded-cell-header:not(.nc-cell-expanded-form-header) > :first-child { |
||||
@apply pl-0; |
||||
} |
||||
|
||||
.nc-drawer-expanded-form .nc-modal { |
||||
@apply !p-0; |
||||
} |
||||
</style> |
||||
|
||||
<style lang="scss" scoped> |
||||
:deep(.ant-select-selector) { |
||||
@apply !xs:(h-full); |
||||
} |
||||
|
||||
.nc-data-cell { |
||||
@apply !rounded-lg; |
||||
transition: all 0.3s; |
||||
|
||||
&:not(.nc-readonly-div-data-cell):not(.nc-system-field):not(.nc-attachment-cell):not(.nc-virtual-cell-button) { |
||||
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08); |
||||
} |
||||
&:not(:focus-within):hover:not(.nc-readonly-div-data-cell):not(.nc-system-field):not(.nc-virtual-cell-button) { |
||||
@apply !border-1; |
||||
&:not(.nc-attachment-cell):not(.nc-virtual-cell-button) { |
||||
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.24); |
||||
} |
||||
} |
||||
|
||||
&.nc-readonly-div-data-cell, |
||||
&.nc-system-field { |
||||
@apply !border-gray-200; |
||||
|
||||
.nc-cell, |
||||
.nc-virtual-cell { |
||||
@apply text-gray-400; |
||||
} |
||||
} |
||||
&.nc-readonly-div-data-cell:focus-within, |
||||
&.nc-system-field:focus-within { |
||||
@apply !border-gray-200; |
||||
} |
||||
|
||||
&:focus-within:not(.nc-readonly-div-data-cell):not(.nc-system-field) { |
||||
@apply !shadow-selected; |
||||
} |
||||
|
||||
&:has(.nc-virtual-cell-qrcode .nc-qrcode-container), |
||||
&:has(.nc-virtual-cell-barcode .nc-barcode-container) { |
||||
@apply !border-none px-0 !rounded-none; |
||||
:deep(.nc-virtual-cell-qrcode), |
||||
:deep(.nc-virtual-cell-barcode) { |
||||
@apply px-0; |
||||
& > div { |
||||
@apply !px-0; |
||||
} |
||||
.barcode-wrapper { |
||||
@apply ml-0; |
||||
} |
||||
} |
||||
:deep(.nc-virtual-cell-qrcode) { |
||||
img { |
||||
@apply !h-[84px] border-1 border-solid border-gray-200 rounded; |
||||
} |
||||
} |
||||
:deep(.nc-virtual-cell-barcode) { |
||||
.nc-barcode-container { |
||||
@apply border-1 rounded-lg border-gray-200 h-[64px] max-w-full p-2; |
||||
svg { |
||||
@apply !h-full; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.nc-mentioned-cell { |
||||
box-shadow: 0px 0px 0px 2px var(--ant-primary-color-outline) !important; |
||||
@apply !border-brand-500 !border-1; |
||||
} |
||||
|
||||
.nc-data-cell:focus-within { |
||||
@apply !border-1 !border-brand-500; |
||||
} |
||||
|
||||
:deep(.nc-system-field input) { |
||||
@apply bg-transparent; |
||||
} |
||||
:deep(.nc-data-cell .nc-cell .nc-cell-field) { |
||||
@apply px-2; |
||||
} |
||||
:deep(.nc-data-cell .nc-virtual-cell .nc-cell-field) { |
||||
@apply px-2; |
||||
} |
||||
:deep(.nc-data-cell .nc-cell-field.nc-lookup-cell .nc-cell-field) { |
||||
@apply px-0; |
||||
} |
||||
</style> |
@ -0,0 +1,29 @@
|
||||
--- |
||||
title: 'World Clock' |
||||
description: 'Display multiple time zone clocks with in NocoDB' |
||||
tags: ['Extensions', 'World Clock', 'Time Zone', 'Clock', 'Time'] |
||||
keywords: ['World Clock', 'Time Zone Clock', 'Clock', 'Time Zone', 'Time'] |
||||
--- |
||||
|
||||
## Overview |
||||
The World Clock extension in NocoDB allows you to display multiple time zone clocks within your NocoDB instance. This feature is particularly useful when you need to track time across different regions or collaborate with team members in different time zones. The extension enhances the user experience by providing a visual representation of time zones, making it easier to manage global operations and schedules. |
||||
|
||||
<img src="/img/v2/extensions/world-clock-2.png" alt="Analog" width="380"/> |
||||
<img src="/img/v2/extensions/world-clock-1.png" alt="Digital" width="420"/> |
||||
|
||||
### Adding Clock |
||||
To add a clock, expand World clock extension, click `+ Add City` and select City from the dropdown available. You can add a maximum of 4 clocks per instance of an extension. |
||||
|
||||
- **Clock Name**: Enter a name for the clock to identify it easily. It defaults to the selected City name. |
||||
- **Theme**: Choose one amongst the available themes for the clock display. |
||||
|
||||
### Clock Display Settings |
||||
The following are the global settings that will be applied to all the clocks configured. |
||||
- **Clock Type**: Choose between **Analog**, **Digital**, or **Both** for the display format. |
||||
- **Show Numbers**: For analog clocks, you can additionally configure if hour numbers are to be displayed on the clock dial. |
||||
- **Time Format**: Choose between 12H and 24H format for the clock display. |
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
{ |
||||
"label": "Extensions ☁", |
||||
"collapsible": true, |
||||
"collapsed": true, |
||||
"link": { |
||||
"type": "generated-index" |
||||
} |
||||
} |
Before Width: | Height: | Size: 756 KiB After Width: | Height: | Size: 718 KiB |
After Width: | Height: | Size: 82 KiB |
After Width: | Height: | Size: 124 KiB |
@ -0,0 +1,127 @@
|
||||
import { expect, test } from '@playwright/test'; |
||||
import { DashboardPage } from '../../../pages/Dashboard'; |
||||
import setup, { unsetup } from '../../../setup'; |
||||
|
||||
// Todo: Enable this test once we enable this feature for all
|
||||
test.describe.skip('Expanded form files mode', () => { |
||||
let dashboard: DashboardPage; |
||||
let context: any; |
||||
|
||||
test.beforeEach(async ({ page }) => { |
||||
context = await setup({ page, isEmptyProject: false }); |
||||
dashboard = new DashboardPage(page, context.base); |
||||
}); |
||||
|
||||
test.afterEach(async () => { |
||||
await unsetup(context); |
||||
}); |
||||
|
||||
async function addFileToRow(rowIndex: number, filePathAppned: string[]) { |
||||
await dashboard.grid.cell.attachment.addFile({ |
||||
index: rowIndex, |
||||
columnHeader: 'testAttach', |
||||
filePath: filePathAppned.map(filePath => `${process.cwd()}/fixtures/sampleFiles/${filePath}`), |
||||
}); |
||||
|
||||
await dashboard.rootPage.waitForTimeout(500); |
||||
|
||||
await dashboard.grid.cell.attachment.verifyFileCount({ |
||||
index: rowIndex, |
||||
columnHeader: 'testAttach', |
||||
count: filePathAppned.length, |
||||
}); |
||||
} |
||||
|
||||
test('Mode switch and functionality', async () => { |
||||
test.slow(); |
||||
|
||||
await dashboard.treeView.openTable({ title: 'Country' }); |
||||
await dashboard.grid.column.create({ |
||||
title: 'testAttach', |
||||
type: 'Attachment', |
||||
}); |
||||
|
||||
await addFileToRow(0, ['1.json']); |
||||
await addFileToRow(2, ['1.json', '2.json']); |
||||
|
||||
await dashboard.grid.openExpandedRow({ index: 0 }); |
||||
await dashboard.expandedForm.verifyTableNameShown({ name: 'Country' }); |
||||
|
||||
await dashboard.expandedForm.verifyIsInFieldsMode(); |
||||
await dashboard.expandedForm.switchToFilesMode(); |
||||
await dashboard.expandedForm.verifyIsInFilesMode(); |
||||
|
||||
await expect(dashboard.expandedForm.cnt_filesModeContainer).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesNoAttachmentField).not.toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesAttachmentHeader).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentAttachmentTitle).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesNoAttachment).not.toBeVisible(); |
||||
|
||||
await expect(dashboard.expandedForm.cnt_filesCurrentFieldTitle).toHaveText('testAttach'); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentAttachmentTitle).toHaveText('1.json'); |
||||
|
||||
await dashboard.expandedForm.verifyFilesViewerMode({ mode: 'unsupported' }); |
||||
}); |
||||
|
||||
test('Various file types correct rendering', async () => { |
||||
test.slow(); |
||||
|
||||
await dashboard.treeView.openTable({ title: 'Country' }); |
||||
await dashboard.grid.column.create({ |
||||
title: 'testAttach', |
||||
type: 'Attachment', |
||||
}); |
||||
|
||||
await addFileToRow(1, ['1.json', 'sampleImage.jpeg', 'sample.mp4', 'sample.pdf', 'sample.mp3']); |
||||
await addFileToRow(2, ['1.json']); |
||||
|
||||
await dashboard.grid.openExpandedRow({ index: 0 }); |
||||
|
||||
await dashboard.expandedForm.switchToFilesMode(); |
||||
await dashboard.expandedForm.verifyIsInFilesMode(); |
||||
|
||||
await expect(dashboard.expandedForm.cnt_filesModeContainer).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesNoAttachmentField).not.toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesAttachmentHeader).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentAttachmentTitle).not.toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesNoAttachment).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentFieldTitle).toHaveText('testAttach'); |
||||
|
||||
await dashboard.expandedForm.close(); |
||||
await dashboard.grid.openExpandedRow({ index: 1 }); |
||||
|
||||
await dashboard.expandedForm.verifyIsInFieldsMode(); |
||||
await dashboard.expandedForm.switchToFilesMode(); |
||||
await dashboard.expandedForm.verifyIsInFilesMode(); |
||||
|
||||
await expect(dashboard.expandedForm.cnt_filesModeContainer).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesNoAttachmentField).not.toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesAttachmentHeader).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentAttachmentTitle).toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesNoAttachment).not.toBeVisible(); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentFieldTitle).toHaveText('testAttach'); |
||||
|
||||
await dashboard.expandedForm.verifyPreviewCellsCount({ count: 5 }); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentAttachmentTitle).toHaveText('1.json'); |
||||
await dashboard.expandedForm.verifyFilesViewerMode({ mode: 'unsupported' }); |
||||
|
||||
await dashboard.expandedForm.selectNthFilePreviewCell({ index: 1 }); |
||||
await dashboard.expandedForm.verifyFilesViewerMode({ mode: 'image' }); |
||||
|
||||
await dashboard.expandedForm.selectNthFilePreviewCell({ index: 2 }); |
||||
await dashboard.expandedForm.verifyFilesViewerMode({ mode: 'video' }); |
||||
|
||||
await dashboard.expandedForm.selectNthFilePreviewCell({ index: 3 }); |
||||
await dashboard.expandedForm.verifyFilesViewerMode({ mode: 'pdf' }); |
||||
|
||||
await dashboard.expandedForm.selectNthFilePreviewCell({ index: 4 }); |
||||
await dashboard.expandedForm.verifyFilesViewerMode({ mode: 'audio' }); |
||||
|
||||
await dashboard.expandedForm.moveToNextField(); |
||||
await dashboard.expandedForm.verifyIsInFilesMode(); |
||||
|
||||
await dashboard.expandedForm.verifyPreviewCellsCount({ count: 5 }); |
||||
await expect(dashboard.expandedForm.cnt_filesCurrentAttachmentTitle).toHaveText('1.json'); |
||||
await dashboard.expandedForm.verifyFilesViewerMode({ mode: 'unsupported' }); |
||||
}); |
||||
}); |