Browse Source

Nc fix/shared view UI changes (#8615)

* fix(nc-gui): update shared grid view

* fix(nc-gui): shared gallery view padding issue

* fix(nc-gui): Shared kanban view padding issue

* fix(nc-gui): reduce calender shared view padding

* fix(nc-gui): reduce shared form view padding

* fix(nc-gui): update shared view password modal

* fix(nc-gui): shared view password input error handling

* fix(nc-gui): reduce expanded form modal width if comment section is not present

* fix(nc-gui): small changes

* fix(nc-gui): add export download view in topbar of shared view

* fix(nc-gui): small changes

* fix(nc-gui): add blur bg image for shared view password modal

* fix(nc-gui): download shared view dropdown ui changes

* fix(nc-gui): expanded form scroll issue

* fix(nc-gui): click anywhere in card should open expanded form

* fix(nc-gui): hide action icon on gallery/kanban card hover

* fix(nc-gui): expanded form cell hover effect

* fix(nc-gui): add sign up for free btn in shared view

* test: update shared view test cases

* test: update calendar test cases

* fix(nc-gui): remove readonly prefix from attachment modal

* fix(nc-gui): remove focus border effect if field is readonly

* fix(nc-gui): shared view groupby pagination size should be 10

* fix(nc-gui): remove field modal input shadow if field is disabled

* fix(nc-gui): add shadow on expanded form fields

* fix(nc-gui): calendar shared view background color update

* fix(nc-gui): shared view download btn text color

* fix(nc-gui): update url, link, email grid text color if cell is active and remove hover effect

* fix(nc-gui): pr review changes
pull/8640/head
Ramesh Mane 6 months ago committed by GitHub
parent
commit
575ff920ef
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. BIN
      packages/nc-gui/assets/img/views/calendar.png
  2. BIN
      packages/nc-gui/assets/img/views/form.png
  3. BIN
      packages/nc-gui/assets/img/views/gallery.png
  4. BIN
      packages/nc-gui/assets/img/views/grid.png
  5. BIN
      packages/nc-gui/assets/img/views/kanban.png
  6. 5
      packages/nc-gui/assets/nc-icons/key.svg
  7. 2
      packages/nc-gui/components/cell/DatePicker.vue
  8. 2
      packages/nc-gui/components/cell/DateTimePicker.vue
  9. 2
      packages/nc-gui/components/cell/Email.vue
  10. 4
      packages/nc-gui/components/cell/Json.vue
  11. 2
      packages/nc-gui/components/cell/PhoneNumber.vue
  12. 2
      packages/nc-gui/components/cell/TimePicker.vue
  13. 4
      packages/nc-gui/components/cell/Url.vue
  14. 2
      packages/nc-gui/components/cell/YearPicker.vue
  15. 1
      packages/nc-gui/components/cell/attachment/Modal.vue
  16. 19
      packages/nc-gui/components/nc/Button.vue
  17. 94
      packages/nc-gui/components/shared-view/AskPassword.vue
  18. 4
      packages/nc-gui/components/shared-view/Calendar.vue
  19. 2
      packages/nc-gui/components/shared-view/Gallery.vue
  20. 2
      packages/nc-gui/components/shared-view/Grid.vue
  21. 2
      packages/nc-gui/components/shared-view/Kanban.vue
  22. 2
      packages/nc-gui/components/smartsheet/Cell.vue
  23. 6
      packages/nc-gui/components/smartsheet/Gallery.vue
  24. 8
      packages/nc-gui/components/smartsheet/Kanban.vue
  25. 2
      packages/nc-gui/components/smartsheet/Toolbar.vue
  26. 11
      packages/nc-gui/components/smartsheet/VirtualCell.vue
  27. 15
      packages/nc-gui/components/smartsheet/column/EditOrAdd.vue
  28. 45
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  29. 20
      packages/nc-gui/components/smartsheet/grid/Table.vue
  30. 2
      packages/nc-gui/components/smartsheet/header/Cell.vue
  31. 2
      packages/nc-gui/components/smartsheet/header/VirtualCell.vue
  32. 48
      packages/nc-gui/components/smartsheet/toolbar/Export.vue
  33. 1
      packages/nc-gui/components/smartsheet/toolbar/ExportSubActions.vue
  34. 2
      packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue
  35. 6
      packages/nc-gui/components/smartsheet/toolbar/SearchData.vue
  36. 6
      packages/nc-gui/components/smartsheet/toolbar/SortListMenu.vue
  37. 37
      packages/nc-gui/components/virtual-cell/components/ListItem.vue
  38. 29
      packages/nc-gui/composables/useSharedView.ts
  39. 2
      packages/nc-gui/composables/useViewGroupBy.ts
  40. 3
      packages/nc-gui/lang/en.json
  41. 77
      packages/nc-gui/layouts/shared-view.vue
  42. 2
      packages/nc-gui/lib/types.ts
  43. 3
      packages/nc-gui/pages/index/[typeOrId]/calendar/[viewId]/index.vue
  44. 74
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId].vue
  45. 2
      packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index.vue
  46. 3
      packages/nc-gui/pages/index/[typeOrId]/gallery/[viewId]/index.vue
  47. 3
      packages/nc-gui/pages/index/[typeOrId]/kanban/[viewId]/index.vue
  48. 3
      packages/nc-gui/pages/index/[typeOrId]/map/[viewId]/index.vue
  49. 4
      packages/nc-gui/pages/index/[typeOrId]/view/[viewId].vue
  50. 2
      packages/nc-gui/utils/iconUtils.ts
  51. 14
      tests/playwright/pages/Dashboard/common/Toolbar/CalendarViewMode.ts
  52. 22
      tests/playwright/pages/Dashboard/common/Toolbar/index.ts
  53. 26
      tests/playwright/pages/Dashboard/common/Topbar/index.ts
  54. 6
      tests/playwright/tests/db/views/viewGridShare.spec.ts

BIN
packages/nc-gui/assets/img/views/calendar.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 KiB

BIN
packages/nc-gui/assets/img/views/form.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
packages/nc-gui/assets/img/views/gallery.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
packages/nc-gui/assets/img/views/grid.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 KiB

BIN
packages/nc-gui/assets/img/views/kanban.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 279 KiB

5
packages/nc-gui/assets/nc-icons/key.svg

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
<path
d="M10.3333 4.99998L12.6667 2.66665M14 1.33331L12.6667 2.66665L14 1.33331ZM7.59333 7.73998C7.93756 8.07962 8.2112 8.48401 8.3985 8.92984C8.5858 9.37568 8.68306 9.85416 8.68468 10.3377C8.68631 10.8213 8.59225 11.3004 8.40794 11.7475C8.22363 12.1946 7.95271 12.6008 7.61076 12.9427C7.26882 13.2847 6.86261 13.5556 6.41554 13.7399C5.96846 13.9242 5.48933 14.0183 5.00575 14.0167C4.52218 14.015 4.0437 13.9178 3.59786 13.7305C3.15203 13.5432 2.74764 13.2695 2.408 12.9253C1.74009 12.2338 1.37051 11.3076 1.37886 10.3462C1.38722 9.38479 1.77284 8.46514 2.45267 7.78531C3.13249 7.10548 4.05214 6.71986 5.01353 6.71151C5.97492 6.70315 6.90113 7.07273 7.59267 7.74065L7.59333 7.73998ZM7.59333 7.73998L10.3333 4.99998L7.59333 7.73998ZM10.3333 4.99998L12.3333 6.99998L14.6667 4.66665L12.6667 2.66665L10.3333 4.99998Z"
stroke="currentColor" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

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

@ -321,7 +321,7 @@ function handleSelectDate(value?: dayjs.Dayjs) {
<GeneralIcon
v-if="localState && !readOnly"
icon="closeCircle"
class="nc-clear-date-icon absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
class="nc-clear-date-icon nc-action-icon absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
@click.stop="handleSelectDate()"
/>
</div>

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

@ -525,7 +525,7 @@ const cellValue = computed(
<GeneralIcon
v-if="localState && (isExpandedForm || isForm || !isGrid || isEditColumn) && !readOnly"
icon="closeCircle"
class="nc-clear-date-time-icon h-4 w-4 absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
class="nc-clear-date-time-icon nc-action-icon h-4 w-4 absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
@click.stop="handleSelectDate()"
/>
</div>

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

@ -97,7 +97,7 @@ watch(
<nuxt-link
v-else-if="validEmail"
no-ref
class="py-1 underline hover:opacity-75 inline-block nc-cell-field-link max-w-full"
class="py-1 underline inline-block nc-cell-field-link max-w-full"
:href="`mailto:${vModel}`"
target="_blank"
:tabindex="readOnly ? -1 : 0"

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

@ -181,11 +181,11 @@ watch(inputWrapperRef, () => {
>
<a-button
:type="isEditColumn && !isExpanded ? 'text' : 'primary'"
:type="!isExpanded ? 'text' : 'primary'"
size="small"
class="nc-save-json-value-btn !rounded-lg"
:class="{
'nc-edit-modal': isEditColumn && !isExpanded,
'nc-edit-modal': !isExpanded,
}"
:disabled="!!error || localValue === vModel"
@click="onSave"

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

@ -79,7 +79,7 @@ watch(
<a
v-else-if="validPhoneNumber"
class="py-1 underline hover:opacity-75 inline-block nc-cell-field-link"
class="py-1 underline inline-block nc-cell-field-link"
:href="`tel:${vModel}`"
target="_blank"
rel="noopener noreferrer"

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

@ -331,7 +331,7 @@ const cellValue = computed(() => localState.value?.format(parseProp(column.value
<GeneralIcon
v-if="localState && !readOnly"
icon="closeCircle"
class="nc-clear-time-icon absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
class="nc-clear-time-icon nc-action-icon absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
@click.stop="handleSelectTime()"
/>
</div>

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

@ -102,7 +102,7 @@ watch(
v-else-if="isValid && !cellUrlOptions?.overlay"
no-prefetch
no-rel
class="py-1 z-3 underline hover:opacity-75 nc-cell-field-link max-w-full"
class="py-1 z-3 underline nc-cell-field-link max-w-full"
:to="url"
:target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
:tabindex="readOnly ? -1 : 0"
@ -114,7 +114,7 @@ watch(
v-else-if="isValid && !disableOverlay && cellUrlOptions?.overlay"
no-prefetch
no-rel
class="py-1 z-3 w-full h-full text-center !no-underline hover:opacity-75 nc-cell-field-link max-w-full"
class="py-1 z-3 w-full h-full text-center !no-underline nc-cell-field-link max-w-full"
:to="url"
:target="cellUrlOptions?.behavior === 'replace' ? undefined : '_blank'"
:tabindex="readOnly ? -1 : 0"

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

@ -283,7 +283,7 @@ function handleSelectDate(value?: dayjs.Dayjs) {
<GeneralIcon
v-if="localState && !readOnly"
icon="closeCircle"
class="nc-clear-year-icon absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
class="nc-clear-year-icon nc-action-icon absolute right-0 top-[50%] transform -translate-y-1/2 invisible cursor-pointer"
@click.stop="handleSelectDate()"
/>
</div>

1
packages/nc-gui/components/cell/attachment/Modal.vue

@ -103,7 +103,6 @@ const handleFileDelete = (i: number) => {
</div>
<div class="flex items-center gap-2">
<div v-if="readOnly" class="text-gray-400">[{{ $t('labels.readOnly') }}]</div>
{{ $t('labels.viewingAttachmentsOf') }}
<div class="font-semibold underline">{{ column?.title }}</div>
</div>

19
packages/nc-gui/components/nc/Button.vue

@ -1,7 +1,6 @@
<script lang="ts" setup>
import type { ButtonType } from 'ant-design-vue/lib/button'
import { useSlots } from 'vue'
import type { NcButtonSize } from '~/lib/types'
/**
* @description
@ -76,11 +75,12 @@ useEventListener(NcButton, 'mousedown', () => {
<a-button
ref="NcButton"
:class="{
small: size === 'small',
medium: size === 'medium',
xsmall: size === 'xsmall',
xxsmall: size === 'xxsmall',
focused: isFocused,
'small': size === 'small',
'medium': size === 'medium',
'xsmall': size === 'xsmall',
'xxsmall': size === 'xxsmall',
'size-xs': size === 'xs',
'focused': isFocused,
}"
:disabled="props.disabled"
:loading="loading"
@ -168,6 +168,13 @@ useEventListener(NcButton, 'mousedown', () => {
@apply py-2 px-4 h-10 min-w-10 xs:(h-10.5 max-h-10.5 min-w-10.5 !px-3);
}
.nc-button.ant-btn.size-xs {
@apply px-2 py-0 h-7 min-w-7 rounded-lg text-small leading-[18px];
& > div {
@apply gap-x-2;
}
}
.nc-button.ant-btn.xsmall {
@apply p-0.25 h-6.25 min-w-6.25 rounded-md;
}

94
packages/nc-gui/components/shared-view/AskPassword.vue

@ -1,9 +1,15 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import type { InputPassword } from 'ant-design-vue'
import { ViewTypes } from 'nocodb-sdk'
import gridImage from '~/assets/img/views/grid.png'
import galleryImage from '~/assets/img/views/gallery.png'
import kanbanImage from '~/assets/img/views/kanban.png'
import calendarImage from '~/assets/img/views/calendar.png'
const props = defineProps<{
modelValue: boolean
viewType?: ViewTypes
}>()
const emit = defineEmits(['update:modelValue'])
@ -16,47 +22,103 @@ const { loadSharedView } = useSharedView()
const formState = ref({ password: undefined })
const passwordError = ref<string | null>(null)
const onFinish = async () => {
try {
await loadSharedView(route.params.viewId as string, formState.value.password)
vModel.value = false
} catch (e: any) {
console.error(e)
message.error(await extractSdkResponseErrorMsg(e))
const error = await extractSdkResponseErrorMsgv2(e)
console.error(error.message)
if (error.error === NcErrorType.INVALID_SHARED_VIEW_PASSWORD) {
passwordError.value = error.message
} else {
message.error(error.message)
}
}
}
const focus: VNodeRef = (el: typeof InputPassword) => el?.$el?.querySelector('input').focus()
const focus: VNodeRef = (el: typeof InputPassword) => {
return el && el?.focus?.()
}
watch(
() => formState.value.password,
() => {
passwordError.value = null
},
)
const bgImageName = computed(() => {
switch (props.viewType) {
case ViewTypes.GRID:
return gridImage
case ViewTypes.GALLERY:
return galleryImage
case ViewTypes.KANBAN:
return kanbanImage
case ViewTypes.CALENDAR:
return calendarImage
default:
return gridImage
}
})
</script>
<template>
<NcModal v-model:visible="vModel" c size="small" :class="{ active: vModel }" :mask-closable="false">
<template #header>
<div class="flex flex-row items-center gap-x-2">
<GeneralIcon icon="key" />
<NcModal
v-model:visible="vModel"
c
size="small"
:class="{ active: vModel }"
:mask-closable="false"
:mask-style="{
backgroundColor: 'rgba(255, 255, 255, 0.64)',
backdropFilter: 'blur(8px)',
}"
>
<div class="flex flex-col gap-5">
<div class="flex flex-row items-center gap-x-2 text-base font-weight-700 text-gray-800">
<GeneralIcon icon="ncKey" class="!text-base w-5 h-5" />
{{ $t('msg.thisSharedViewIsProtected') }}
</div>
</template>
<div class="mt-2">
<a-form ref="formRef" :model="formState" name="create-new-table-form" @finish="onFinish">
<a-form-item name="password" :rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]">
<a-form-item
name="password"
:rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]"
class="!mb-0"
>
<a-input-password
ref="focus"
:ref="focus"
v-model:value="formState.password"
class="nc-input-md"
class="!rounded-lg !text-small"
hide-details
size="large"
:placeholder="$t('msg.enterPassword')"
/>
<Transition name="layout">
<div v-if="passwordError" class="mb-2 text-sm text-red-500">{{ passwordError }}</div>
</Transition>
</a-form-item>
</a-form>
<div class="flex flex-row justify-end gap-x-2 mt-6">
<NcButton type="primary" html-type="submit" @click="onFinish"
>{{ $t('general.unlock') }}
<div class="flex flex-row justify-end gap-x-2">
<NcButton
:disabled="!formState.password"
type="primary"
size="small"
html-type="submit"
class="!px-2"
data-testid="nc-shared-view-password-submit-btn"
@click="onFinish"
>
{{ $t('objects.view') }}
<template #loading> {{ $t('msg.verifyingPassword') }}</template>
</NcButton>
</div>
</div>
</NcModal>
<img alt="view image" :src="bgImageName" class="fixed inset-0 w-full h-full" />
</template>

4
packages/nc-gui/components/shared-view/Calendar.vue

@ -25,10 +25,10 @@ useProvideCalendarViewStore(meta, sharedView, true, nestedFilters)
</script>
<template>
<div class="nc-container h-full mt-1.5 px-12">
<div class="nc-container h-full">
<div class="flex flex-col h-full flex-1 min-w-0">
<LazySmartsheetToolbar />
<div class="h-full flex-1 min-w-0 min-h-0 bg-gray-50">
<div class="h-full flex-1 min-w-0 min-h-0">
<LazySmartsheetCalendar />
</div>
</div>

2
packages/nc-gui/components/shared-view/Gallery.vue

@ -23,7 +23,7 @@ useProvideKanbanViewStore(meta, sharedView)
</script>
<template>
<div class="nc-container h-full mt-1.5 px-12">
<div class="nc-container h-full">
<div class="flex flex-col h-full flex-1 min-w-0">
<LazySmartsheetToolbar />
<div class="h-full flex-1 min-w-0 min-h-0 bg-gray-50">

2
packages/nc-gui/components/shared-view/Grid.vue

@ -45,7 +45,7 @@ watch(
</script>
<template>
<div class="nc-container flex flex-col h-full mt-1.5 px-12">
<div class="nc-container flex flex-col h-full">
<LazySmartsheetToolbar />
<LazySmartsheetGrid />
</div>

2
packages/nc-gui/components/shared-view/Kanban.vue

@ -23,7 +23,7 @@ useProvideKanbanViewStore(meta, sharedView, true)
</script>
<template>
<div class="nc-container h-full mt-1.5 px-12">
<div class="nc-container h-full">
<div class="flex flex-col h-full flex-1 min-w-0">
<LazySmartsheetToolbar />
<div class="h-full flex-1 min-w-0 min-h-0 bg-gray-50">

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

@ -186,7 +186,7 @@ const onContextmenu = (e: MouseEvent) => {
<LazyCellJson v-else-if="isJSON(column)" v-model="vModel" />
<LazyCellText v-else v-model="vModel" />
<div
v-if="((isPublic && readOnly && !isForm) || (isSystemColumn(column) && !isAttachment(column))) && !isTextArea(column)"
v-if="((isPublic && readOnly && !isForm) || isSystemColumn(column)) && !isAttachment(column) && !isTextArea(column)"
class="nc-locked-overlay"
/>
</template>

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

@ -396,4 +396,10 @@ watch(
.ant-carousel.gallery-carousel :deep(.slick-next) {
@apply right-0;
}
:deep(.ant-card) {
&:hover .nc-action-icon {
@apply invisible;
}
}
</style>

8
packages/nc-gui/components/smartsheet/Kanban.vue

@ -512,7 +512,7 @@ const getRowId = (row: RowType) => {
<LazySmartsheetRow :row="record">
<a-card
:key="`${getRowId(record)}-${index}`"
class="!rounded-lg h-full border-gray-200 border-1 group overflow-hidden break-all max-w-[450px] shadow-sm hover:shadow-md cursor-pointer"
class="!rounded-lg h-full border-gray-200 border-1 group overflow-hidden break-all max-w-[450px] shadow-sm hover:shadow-md cursor-pointer children:pointer-events-none"
:body-style="{ padding: '0px' }"
:data-stack="stack.title"
:data-testid="`nc-gallery-card-${record.row.id}`"
@ -809,4 +809,10 @@ const getRowId = (row: RowType) => {
:deep(.slick-slide) {
@apply !pointer-events-none;
}
:deep(.ant-card) {
&:hover .nc-action-icon {
@apply invisible;
}
}
</style>

2
packages/nc-gui/components/smartsheet/Toolbar.vue

@ -65,8 +65,6 @@ const { allowCSVDownload } = useSharedView()
<!-- <LazySmartsheetToolbarQrScannerButton v-if="isMobileMode && (isGrid || isKanban || isGallery)" /> -->
<LazySmartsheetToolbarExport v-if="isPublic && allowCSVDownload" />
<div class="flex-1" />
</template>

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

@ -40,10 +40,13 @@ function onNavigate(dir: NavigateDir, e: KeyboardEvent) {
<template>
<div
class="nc-virtual-cell w-full flex items-center"
:class="{
'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm,
'nc-display-value-cell': isPrimary(column) && !isForm,
}"
:class="[
`nc-virtual-cell-${(column.uidt || 'default').toLowerCase()}`,
{
'text-right justify-end': isGrid && !isForm && isRollup(column) && !isExpandedForm,
'nc-display-value-cell': isPrimary(column) && !isForm,
},
]"
@keydown.enter.exact="onNavigate(NavigateDir.NEXT, $event)"
@keydown.shift.enter.exact="onNavigate(NavigateDir.PREV, $event)"
>

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

@ -557,9 +557,13 @@ const submitBtnLabel = computed(() => {
.ant-radio-wrapper-disabled {
@apply pointer-events-none;
box-shadow: none;
&:hover {
box-shadow: none;
}
}
&:not(:hover):not(:focus-within):not(.shadow-selected),
&.ant-radio-wrapper-disabled:hover {
&:not(.ant-radio-wrapper-disabled):not(:hover):not(:focus-within):not(.shadow-selected) {
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08);
}
&:hover:not(:focus-within):not(.ant-radio-wrapper-disabled) {
@ -568,14 +572,17 @@ const submitBtnLabel = computed(() => {
}
:deep(.ant-select) {
&:not(:hover):not(.ant-select-focused) .ant-select-selector,
&:hover.ant-select-disabled .ant-select-selector {
&:not(.ant-select-disabled):not(:hover):not(.ant-select-focused) .ant-select-selector,
&:not(.ant-select-disabled):hover.ant-select-disabled .ant-select-selector {
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.08);
}
&:hover:not(.ant-select-focused):not(.ant-select-disabled) .ant-select-selector {
@apply border-gray-300;
box-shadow: 0px 0px 4px 0px rgba(0, 0, 0, 0.24);
}
&.ant-select-disabled .ant-select-selector {
box-shadow: none;
}
}
:deep(.ant-form-item-label > label) {

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

@ -547,13 +547,13 @@ export default {
:closable="false"
:footer="null"
:visible="isExpanded"
:width="commentsDrawer && isUIAllowed('commentList') ? 'min(80vw,1280px)' : 'min(80vw,1280px)'"
:width="showRightSections ? 'min(80vw,1280px)' : 'min(70vw,768px)'"
class="nc-drawer-expanded-form"
:size="isMobileMode ? 'medium' : 'small'"
v-bind="modalProps"
@update:visible="onIsExpandedUpdate"
>
<div class="h-[85vh] xs:(max-h-full h-auto) max-h-215 flex flex-col">
<div class="h-[85vh] xs:(max-h-full h-full) max-h-215 flex flex-col">
<div v-if="isMobileMode" class="flex-none h-4 flex items-center justify-center">
<div class="flex-none h-full flex items-center justify-center cursor-pointer" @click="onClose">
<div class="w-[72px] h-[2px] rounded-full bg-[#49494a]"></div>
@ -720,13 +720,13 @@ export default {
</NcButton>
</div>
</div>
<div ref="wrapper" class="flex flex-grow flex-row h-[calc(100%-4rem)] w-full border-t-1 border-gray-200">
<div ref="wrapper" class="flex flex-grow flex-row h-[calc(100%_-_4rem)] w-full border-t-1 border-gray-200">
<div
:class="{
'w-full': !showRightSections,
'flex-1': showRightSections,
}"
class="flex xs:w-full flex-col overflow-hidden"
class="h-full flex xs:w-full flex-col overflow-hidden"
>
<div
ref="expandedFormScrollWrapper"
@ -765,7 +765,8 @@ export default {
:ref="i ? null : (el: any) => (cellWrapperEl = el)"
class="bg-white flex-1 <lg:w-full px-1 min-h-[37px] flex items-center relative"
:class="{
'!bg-gray-50 !select-text nc-system-field': isReadOnlyVirtualCell(col),
' !select-text nc-system-field': isReadOnlyVirtualCell(col),
'!select-text nc-readonly-div-data-cell': readOnly,
}"
>
<LazySmartsheetVirtualCell
@ -840,7 +841,8 @@ export default {
:ref="i ? null : (el: any) => (cellWrapperEl = el)"
class="bg-white flex-1 <lg:w-full px-1 min-h-[37px] flex items-center relative"
:class="{
'!bg-gray-50 !select-text nc-system-field': isReadOnlyVirtualCell(col),
'!select-text nc-system-field': isReadOnlyVirtualCell(col),
'!bg-gray-50 !select-text nc-readonly-div-data-cell': readOnly,
}"
>
<LazySmartsheetVirtualCell
@ -933,6 +935,7 @@ export default {
</NcButton>
</div>
</div>
<div v-else class="p-2"></div>
</div>
<div
v-if="showRightSections"
@ -1004,6 +1007,9 @@ export default {
.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;
@ -1022,12 +1028,31 @@ export default {
.nc-data-cell {
@apply !rounded-lg;
transition: all 0.3s;
&:hover {
@apply !border-1 !border-brand-400;
&:not(.nc-readonly-div-data-cell):not(.nc-system-field) {
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) {
@apply !border-1;
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 {
box-shadow: 0px 0px 0px 2px rgba(51, 102, 255, 0.24) !important;
&:focus-within:not(.nc-readonly-div-data-cell):not(.nc-system-field) {
@apply !shadow-selected;
}
}
.nc-data-cell:focus-within {

20
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -2476,7 +2476,18 @@ onKeyStroke('ArrowDown', onDown)
overflow: hidden;
@apply flex h-auto;
}
&.active-cell {
:deep(.nc-cell) {
a.nc-cell-field-link {
@apply !text-brand-500;
&:hover,
.nc-cell-field {
@apply !text-brand-500;
}
}
}
}
:deep(.nc-cell),
:deep(.nc-virtual-cell) {
@apply !text-small;
@ -2506,6 +2517,13 @@ onKeyStroke('ArrowDown', onDown)
@apply !p-0 m-0;
}
a.nc-cell-field-link {
@apply !text-current;
&:hover {
@apply !text-current;
}
}
&.nc-cell-longtext {
@apply leading-[18px];

2
packages/nc-gui/components/smartsheet/header/Cell.vue

@ -102,7 +102,7 @@ const onClick = (e: Event) => {
'h-full': column,
'!text-gray-400': isKanban,
'flex-col !items-start justify-center pt-0.5': isExpandedForm && !isMobileMode && !isExpandedBulkUpdateForm,
'cursor-pointer hover:bg-gray-100':
'nc-cell-expanded-form-header cursor-pointer hover:bg-gray-100':
isExpandedForm && !isMobileMode && isUIAllowed('fieldEdit') && !isExpandedBulkUpdateForm,
'bg-gray-100': isExpandedForm && !isExpandedBulkUpdateForm ? editColumnDropdown || isDropDownOpen : false,
}"

2
packages/nc-gui/components/smartsheet/header/VirtualCell.vue

@ -183,7 +183,7 @@ const onClick = (e: Event) => {
:class="{
'flex-col !items-start justify-center pt-0.5': isExpandedForm && !isMobileMode && !isExpandedBulkUpdateForm,
'bg-gray-100': isExpandedForm && !isExpandedBulkUpdateForm ? editColumnDropdown || isDropDownOpen : false,
'cursor-pointer hover:bg-gray-100':
'nc-cell-expanded-form-header cursor-pointer hover:bg-gray-100':
isExpandedForm && !isMobileMode && isUIAllowed('fieldEdit') && !isExpandedBulkUpdateForm,
}"
@dblclick="openHeaderMenu"

48
packages/nc-gui/components/smartsheet/toolbar/Export.vue

@ -1,19 +1,47 @@
<script lang="ts" setup></script>
<script lang="ts" setup>
const { sharedView, meta, nestedFilters } = useSharedView()
const { isLocked, xWhere } = useProvideSmartsheetStore(sharedView, meta, true, ref([]), nestedFilters)
const reloadEventHook = createEventHook()
provide(ReloadViewDataHookInj, reloadEventHook)
provide(ReadonlyInj, ref(true))
provide(MetaInj, meta)
provide(ActiveViewInj, sharedView)
provide(IsPublicInj, ref(true))
provide(IsLockedInj, isLocked)
useProvideViewColumns(sharedView, meta, () => reloadEventHook?.trigger(), true)
useProvideViewGroupBy(sharedView, meta, xWhere, true)
useProvideSmartsheetLtarHelpers(meta)
useProvideKanbanViewStore(meta, sharedView)
useProvideCalendarViewStore(meta, sharedView, true, nestedFilters)
</script>
<template>
<a-dropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<a-button v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn">
<div class="flex gap-2 items-center">
<component :is="iconMap.download" class="group-hover:text-accent text-gray-500" />
<span class="text-capitalize !text-sm font-medium text-gray-500">{{ $t('general.download') }}</span>
<NcDropdown :trigger="['click']" overlay-class-name="nc-dropdown-actions-menu">
<NcButton v-e="['c:actions']" class="nc-actions-menu-btn nc-toolbar-btn" size="xs" type="secondary">
<div class="flex gap-2 items-center text-gray-700">
<component :is="iconMap.download" class="group-hover:text-accent" />
<span class="text-capitalize !text-sm font-medium xs:hidden">{{ $t('general.download') }}</span>
<component :is="iconMap.arrowDown" class="text-grey" />
</div>
</a-button>
</NcButton>
<template #overlay>
<a-menu class="ml-6 !text-sm !px-0 !py-2 !rounded">
<NcMenu class="ml-6 !text-sm !rounded-lg overflow-hidden">
<LazySmartsheetToolbarExportSubActions />
</a-menu>
</NcMenu>
</template>
</a-dropdown>
</NcDropdown>
</template>

1
packages/nc-gui/components/smartsheet/toolbar/ExportSubActions.vue

@ -75,6 +75,7 @@ const exportFile = async (exportType: ExportTypes) => {
}, 200)
}
} catch (e: any) {
isExportingType.value = undefined
message.error(await extractSdkResponseErrorMsg(e))
}
}

2
packages/nc-gui/components/smartsheet/toolbar/RowHeight.vue

@ -87,7 +87,7 @@ useMenuCloseOnEsc(open)
<NcButton
v-e="['c:row-height']"
:disabled="isLocked"
class="nc-height-menu-btn nc-toolbar-btn !border-0 !h-7"
class="nc-height-menu-btn nc-toolbar-btn !border-0 !h-7 !px-1.5 !min-w-7"
size="small"
type="secondary"
>

6
packages/nc-gui/components/smartsheet/toolbar/SearchData.vue

@ -87,7 +87,7 @@ onClickOutside(globalSearchWrapperRef, (e) => {
<div ref="globalSearchWrapperRef" class="nc-global-search-wrapper">
<a-button
v-if="!search.query && !showSearchBox"
class="nc-toolbar-btn"
class="nc-toolbar-btn !rounded-lg !h-7 !px-1.5"
data-testid="nc-global-search-show-input"
@click="handleShowSearchInput"
>
@ -110,7 +110,7 @@ onClickOutside(globalSearchWrapperRef, (e) => {
>
<GeneralIcon icon="search" class="ml-1 mr-2 h-3.5 w-3.5 text-gray-500 group-hover:text-black" />
<div v-if="!isMobileMode" class="w-16 text-xs font-medium text-gray-400 truncate">
{{ displayColumnLabel }}
{{ displayColumnLabel ?? '' }}
</div>
<div class="xs:(text-gray-600) group-hover:text-gray-700 sm:(text-gray-400)">
<component :is="iconMap.arrowDown" class="text-sm text-inherit" />
@ -136,7 +136,7 @@ onClickOutside(globalSearchWrapperRef, (e) => {
name="globalSearchQuery"
size="small"
class="text-xs w-40 h-full"
:placeholder="`${$t('general.searchIn')} ${displayColumnLabel}`"
:placeholder="`${$t('general.searchIn')} ${displayColumnLabel ?? ''}`"
:bordered="false"
data-testid="search-data-input"
@press-enter="onPressEnter"

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

@ -115,11 +115,11 @@ onMounted(() => {
<NcButton
v-e="['c:sort']"
:class="{
'!border-1 !rounded-lg !h-7': isCalendar,
'!border-0 ': !isCalendar,
'!border-1 !rounded-lg': isCalendar,
'!border-0': !isCalendar,
}"
:disabled="isLocked"
class="nc-sort-menu-btn nc-toolbar-btn"
class="nc-sort-menu-btn nc-toolbar-btn !h-7"
size="small"
type="secondary"
>

37
packages/nc-gui/components/virtual-cell/components/ListItem.vue

@ -170,25 +170,26 @@ const displayValue = computed(() => {
</button>
</NcTooltip>
</div>
<template v-if="(!isPublic && !readOnly) || isForm">
<NcTooltip class="z-10 flex">
<template #title> {{ isLinked ? 'Unlink' : 'Link' }}</template>
<NcTooltip class="z-10 flex">
<template #title> {{ isLinked ? 'Unlink' : 'Link' }}</template>
<button
tabindex="-1"
class="nc-list-item-link-unlink-btn p-1.5 flex rounded-lg transition-all"
:class="{
'bg-gray-200 text-gray-800 hover:(bg-red-100 text-red-500)': isLinked,
'bg-green-[#D4F7E0] text-[#17803D] hover:bg-green-200': !isLinked,
}"
@click="$emit('linkOrUnlink')"
>
<div v-if="isLoading" class="flex">
<MdiLoading class="flex-none w-4 h-4 !text-brand-500 animate-spin" />
</div>
<GeneralIcon v-else :icon="isLinked ? 'minus' : 'plus'" class="flex-none w-4 h-4 !font-extrabold" />
</button>
</NcTooltip>
<button
tabindex="-1"
class="nc-list-item-link-unlink-btn p-1.5 flex rounded-lg transition-all"
:class="{
'bg-gray-200 text-gray-800 hover:(bg-red-100 text-red-500)': isLinked,
'bg-green-[#D4F7E0] text-[#17803D] hover:bg-green-200': !isLinked,
}"
@click="$emit('linkOrUnlink')"
>
<div v-if="isLoading" class="flex">
<MdiLoading class="flex-none w-4 h-4 !text-brand-500 animate-spin" />
</div>
<GeneralIcon v-else :icon="isLinked ? 'minus' : 'plus'" class="flex-none w-4 h-4 !font-extrabold" />
</button>
</NcTooltip>
</template>
</div>
</a-card>
</div>

29
packages/nc-gui/composables/useSharedView.ts

@ -124,16 +124,21 @@ export function useSharedView() {
}
}
const fetchSharedViewData = async (param: {
sortsArr: SortType[]
filtersArr: FilterType[]
fields?: any[]
sort?: any[]
where?: string
/** Query params for nested data */
nested?: any
offset?: number
}) => {
const fetchSharedViewData = async (
param: {
sortsArr: SortType[]
filtersArr: FilterType[]
fields?: any[]
sort?: any[]
where?: string
/** Query params for nested data */
nested?: any
offset?: number
},
opts?: {
isGroupBy?: boolean
},
) => {
if (!sharedView.value)
return {
list: [],
@ -142,7 +147,9 @@ export function useSharedView() {
if (!param.offset) {
const page = paginationData.value.page || 1
const pageSize = paginationData.value.pageSize || appInfoDefaultLimit
const pageSize = opts?.isGroupBy
? appInfo.value.defaultGroupByLimit?.limitRecord || 10
: paginationData.value.pageSize || appInfoDefaultLimit
param.offset = (page - 1) * pageSize
param.limit = sharedView.value?.type === ViewTypes.MAP ? 1000 : pageSize
}

2
packages/nc-gui/composables/useViewGroupBy.ts

@ -382,7 +382,7 @@ const [useProvideViewGroupBy, useViewGroupBy] = useInjectionState(
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
} as any)
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value, ...query })
: await fetchSharedViewData({ sortsArr: sorts.value, filtersArr: nestedFilters.value, ...query }, { isGroupBy: true })
group.count = response.pageInfo.totalRows ?? 0
group.rows = formatData(response.list)

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

@ -773,7 +773,8 @@
"limitOptionsSubtext": "Limit options visible to users by selecting available options",
"clearSelection": "Clear selection",
"displayAsProgress": "Display as progress",
"relationType": "Relation type"
"relationType": "Relation type",
"signUpForFree": "Sign up for free"
},
"activity": {
"renameBase": "Rename Base",

77
packages/nc-gui/layouts/shared-view.vue

@ -1,7 +1,7 @@
<script lang="ts" setup>
const { isLoading, appInfo } = useGlobal()
const { sharedView } = useSharedView()
const { sharedView, allowCSVDownload } = useSharedView()
const router = useRouter()
@ -47,44 +47,65 @@ export default {
<template>
<a-layout id="nc-app">
<a-layout class="!flex-col bg-white">
<a-layout-header class="flex !bg-primary items-center text-white pl-3 pr-4 shadow-lg">
<a
class="transition-all duration-200 p-2 cursor-pointer transform hover:scale-105"
href="https://github.com/nocodb/nocodb"
target="_blank"
rel="noopener noreferrer"
>
<a-tooltip placement="bottom">
<template #title>
{{ appInfo.version }}
</template>
<img width="35" alt="NocoDB" src="~/assets/img/icons/256x256-trans.png" />
</a-tooltip>
</a>
<a-layout-header
class="nc-table-topbar flex items-center justify-between !bg-transparent !px-3 !py-2 border-b-1 border-gray-200 !h-[46px]"
>
<div class="flex items-center gap-6 h-7 max-w-[calc(100%_-_280px)] xs:max-w-[calc(100%_-_90px)]">
<a
class="transition-all duration-200 cursor-pointer transform hover:scale-105"
href="https://github.com/nocodb/nocodb"
target="_blank"
rel="noopener noreferrer"
>
<NcTooltip placement="bottom" class="flex">
<template #title>
{{ appInfo.version }}
</template>
<img width="96" alt="NocoDB" src="~/assets/img/brand/nocodb.png" class="flex-none min-w-[96px]" />
</NcTooltip>
</a>
<div>
<div class="flex justify-center items-center">
<div class="flex items-center gap-2 ml-3 text-white">
<template v-if="isLoading">
<span class="text-white" data-testid="nc-loading">{{ $t('general.loading') }}</span>
<div class="flex items-center gap-2 text-gray-900 text-sm truncate">
<template v-if="isLoading">
<span data-testid="nc-loading">{{ $t('general.loading') }}</span>
<component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin ': isLoading }" />
</template>
<component :is="iconMap.reload" :class="{ 'animate-infinite animate-spin ': isLoading }" />
</template>
<div v-else class="text-xl font-semibold truncate text-white nc-shared-view-title flex gap-2 items-center">
<GeneralViewIcon v-if="sharedView" class="!text-xl" :meta="sharedView" />
<div v-else class="text-sm font-semibold truncate nc-shared-view-title flex gap-2 items-center">
<GeneralViewIcon v-if="sharedView" class="h-4 w-4" :meta="sharedView" />
<span class="truncate">
{{ sharedView?.title }}
</div>
</span>
</div>
</div>
</div>
<div class="flex-1" />
</a-layout-header>
<div class="flex items-center gap-3">
<LazySmartsheetToolbarExport v-if="allowCSVDownload" />
<div class="w-full overflow-hidden" style="height: calc(100vh - var(--topbar-height))">
<a href="https://app.nocodb.com/#/signin" target="_blank" class="!no-underline xs:hidden" rel="noopener">
<NcButton size="xs"> {{ $t('labels.signUpForFree') }} </NcButton>
</a>
</div>
</a-layout-header>
<div class="w-full overflow-hidden" style="height: calc(100vh - (var(--topbar-height) - 3.6px))">
<slot />
</div>
</a-layout>
</a-layout>
</template>
<style lang="scss" scoped>
#nc-app {
.ant-layout-header {
@apply !h-[46px];
line-height: unset;
}
:deep(.nc-table-toolbar) {
@apply px-2;
}
}
</style>

2
packages/nc-gui/lib/types.ts

@ -197,7 +197,7 @@ interface Users {
type ViewPageType = 'view' | 'webhook' | 'api' | 'field' | 'relation'
type NcButtonSize = 'xxsmall' | 'xsmall' | 'small' | 'medium'
type NcButtonSize = 'xxsmall' | 'xsmall' | 'small' | 'medium' | 'xs'
interface SidebarTableNode extends TableType {
isMetaLoading?: boolean

3
packages/nc-gui/pages/index/[typeOrId]/calendar/[viewId]/index.vue

@ -1,5 +1,6 @@
<script lang="ts" setup>
import { message } from 'ant-design-vue'
import { ViewTypes } from 'nocodb-sdk'
definePageMeta({
public: true,
@ -27,7 +28,7 @@ try {
<template>
<div v-if="showPassword">
<LazySharedViewAskPassword v-model="showPassword" />
<LazySharedViewAskPassword v-model="showPassword" :view-type="ViewTypes.CALENDAR" />
</div>
<LazySharedViewCalendar v-else />
</template>

74
packages/nc-gui/pages/index/[typeOrId]/form/[viewId].vue

@ -1,4 +1,7 @@
<script setup lang="ts">
import type { VNodeRef } from '@vue/runtime-core'
import type { InputPassword } from 'ant-design-vue'
definePageMeta({
public: true,
pageType: 'shared-view',
@ -39,6 +42,10 @@ watch(
password.value = form.password
},
)
const focus: VNodeRef = (el: typeof InputPassword) => {
return el && el?.focus?.()
}
</script>
<template>
@ -55,25 +62,53 @@ watch(
:footer="null"
:mask-closable="false"
wrap-class-name="nc-modal-shared-form-password-dlg"
:mask-style="{
backgroundColor: 'rgba(255, 255, 255, 0.64)',
backdropFilter: 'blur(8px)',
}"
@close="passwordDlg = false"
>
<div class="w-full flex flex-col gap-4">
<h2 class="text-xl font-semibold">{{ $t('msg.thisSharedViewIsProtected') }}</h2>
<a-form layout="vertical" no-style :model="form" @finish="loadSharedView">
<a-form-item name="password" :rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]">
<a-input-password v-model:value="form.password" size="large" :placeholder="$t('msg.enterPassword')" />
<div class="flex flex-col gap-5">
<div class="flex flex-row items-center gap-x-2 text-base font-weight-700 text-gray-800">
<GeneralIcon icon="ncKey" class="!text-base w-5 h-5" />
{{ $t('msg.thisSharedViewIsProtected') }}
</div>
<a-form :model="form" @finish="loadSharedView">
<a-form-item
name="password"
:rules="[{ required: true, message: $t('msg.error.signUpRules.passwdRequired') }]"
class="!mb-0"
>
<a-input-password
:ref="focus"
v-model:value="form.password"
class="!rounded-lg !text-small"
hide-details
:placeholder="$t('msg.enterPassword')"
/>
<Transition name="layout">
<div v-if="passwordError" class="mb-2 text-sm text-red-500">{{ passwordError }}</div>
</Transition>
</a-form-item>
<Transition name="layout">
<div v-if="passwordError" class="mb-2 text-sm text-red-500">{{ passwordError }}</div>
</Transition>
<!-- Unlock -->
<button type="submit" class="mt-4 scaling-btn bg-opacity-100">{{ $t('general.unlock') }}</button>
</a-form>
<div class="flex flex-row justify-end gap-x-2">
<NcButton
:disabled="!form.password"
type="primary"
size="small"
html-type="submit"
class="!px-2"
data-testid="nc-shared-view-password-submit-btn"
@click="loadSharedView"
>{{ $t('objects.view') }}
<template #loading> {{ $t('msg.verifyingPassword') }}</template>
</NcButton>
</div>
</div>
</a-modal>
<img v-if="passwordDlg" alt="view image" src="~/assets/img/views/form.png" class="fixed inset-0 w-full h-full" />
</NuxtLayout>
</div>
</template>
@ -94,17 +129,4 @@ watch(
}
}
}
.nc-modal-shared-form-password-dlg {
.ant-input-affix-wrapper,
.ant-input {
@apply !appearance-none my-1 border-1 border-solid border-primary border-opacity-50 rounded;
}
.password {
input {
@apply !border-none !m-0;
}
}
}
</style>

2
packages/nc-gui/pages/index/[typeOrId]/form/[viewId]/index.vue

@ -26,7 +26,7 @@ router.afterEach((to) => shouldRedirect(to.name as string))
<template>
<div
class="scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-200 hover-scrollbar-thumb-gray-300 h-[100vh] overflow-y-auto overflow-x-hidden flex flex-col color-transition p-4 lg:p-10 nc-form-view min-h-[600px]"
class="scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-200 hover-scrollbar-thumb-gray-300 h-[100vh] overflow-y-auto overflow-x-hidden flex flex-col color-transition p-4 lg:p-6 nc-form-view min-h-[600px]"
:class="{
'children:(!h-auto my-auto)': sharedViewMeta?.surveyMode,
}"

3
packages/nc-gui/pages/index/[typeOrId]/gallery/[viewId]/index.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { ViewTypes } from 'nocodb-sdk'
definePageMeta({
public: true,
@ -26,7 +27,7 @@ try {
<template>
<div v-if="showPassword">
<LazySharedViewAskPassword v-model="showPassword" />
<LazySharedViewAskPassword v-model="showPassword" :view-type="ViewTypes.GALLERY" />
</div>
<LazySharedViewGallery v-else />
</template>

3
packages/nc-gui/pages/index/[typeOrId]/kanban/[viewId]/index.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { ViewTypes } from 'nocodb-sdk'
definePageMeta({
public: true,
@ -27,7 +28,7 @@ try {
<template>
<div v-if="showPassword">
<LazySharedViewAskPassword v-model="showPassword" />
<LazySharedViewAskPassword v-model="showPassword" :view-type="ViewTypes.KANBAN" />
</div>
<LazySharedViewKanban v-else />
</template>

3
packages/nc-gui/pages/index/[typeOrId]/map/[viewId]/index.vue

@ -1,5 +1,6 @@
<script setup lang="ts">
import { message } from 'ant-design-vue'
import { ViewTypes } from 'nocodb-sdk'
definePageMeta({
public: true,
@ -26,7 +27,7 @@ try {
<template>
<div v-if="showPassword">
<LazySharedViewAskPassword v-model="showPassword" />
<LazySharedViewAskPassword v-model="showPassword" :view-type="ViewTypes.MAP" />
</div>
<LazySharedViewMap v-else />
</template>

4
packages/nc-gui/pages/index/[typeOrId]/view/[viewId].vue

@ -1,4 +1,6 @@
<script setup lang="ts">
import { ViewTypes } from 'nocodb-sdk'
definePageMeta({
public: true,
requiresAuth: false,
@ -37,5 +39,5 @@ onMounted(async () => {
<LazySharedViewAskPassword v-model="showPassword" />
</div>
<LazySharedViewGrid v-else-if="meta" />
<LazySharedViewGrid v-else-if="meta" :view-type="ViewTypes.GRID" />
</template>

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

@ -190,6 +190,7 @@ import NcHelp from '~icons/nc-icons/help'
import NcAlertTriangle from '~icons/nc-icons/alert-triangle'
import NcAudit from '~icons/nc-icons/audit'
import NcMessageCircle from '~icons/nc-icons/message-circle'
import NcKey from '~icons/nc-icons/key'
// keep it for reference
// todo: remove it after all icons are migrated
@ -612,6 +613,7 @@ export const iconMap = {
alertTriangle: NcAlertTriangle,
audit: NcAudit,
messageCircle: NcMessageCircle,
ncKey: NcKey,
}
export const getMdiIcon = (type: string): any => {

14
tests/playwright/pages/Dashboard/common/Toolbar/CalendarViewMode.ts

@ -12,10 +12,18 @@ export class ToolbarCalendarViewModePage extends BasePage {
return this.rootPage.getByTestId('nc-calendar-view-mode');
}
getViewTab({ title }: { title: string }) {
return this.get().getByTestId(`nc-calendar-view-mode-${title}`);
}
async changeCalendarView({ title }: { title: string }) {
await this.get().click({ force: true });
await this.rootPage.waitForTimeout(500);
if (await this.getViewTab({ title }).isVisible()) {
await this.getViewTab({ title }).click({ force: true });
} else {
await this.get().click({ force: true });
await this.rootPage.waitForTimeout(500);
await this.rootPage.locator('.rc-virtual-list-holder-inner > div').locator(`text="${title}"`).click();
await this.rootPage.locator('.rc-virtual-list-holder-inner > div').locator(`text="${title}"`).click();
}
}
}

22
tests/playwright/pages/Dashboard/common/Toolbar/index.ts

@ -195,28 +195,6 @@ export class ToolbarPage extends BasePage {
await this.get().locator(`.nc-toolbar-btn.nc-add-new-row-btn`).click();
}
async clickDownload(type: string, verificationFile = 'expectedData.txt') {
await this.get().locator(`.nc-toolbar-btn.nc-actions-menu-btn`).click();
const [download] = await Promise.all([
// Start waiting for the download
this.rootPage.waitForEvent('download'),
// Perform the action that initiates download
this.rootPage
.locator(`.nc-dropdown-actions-menu`)
.locator(`li.ant-dropdown-menu-item:has-text("${type}")`)
.click(),
]);
// Save downloaded file somewhere
await download.saveAs('./output/at.txt');
// verify downloaded content against expected content
const expectedData = fs.readFileSync(`./fixtures/${verificationFile}`, 'utf8').replace(/\r/g, '').split('\n');
const file = fs.readFileSync('./output/at.txt', 'utf8').replace(/\r/g, '').split('\n');
expect(file).toEqual(expectedData);
}
async clickRowHeight() {
// ant-btn nc-height-menu-btn nc-toolbar-btn
await this.get().locator(`.nc-toolbar-btn.nc-height-menu-btn`).click();

26
tests/playwright/pages/Dashboard/common/Topbar/index.ts

@ -1,4 +1,6 @@
import { Locator } from '@playwright/test';
import { expect, Locator } from '@playwright/test';
import * as fs from 'fs';
import BasePage from '../../../Base';
import { GridPage } from '../../Grid';
import { GalleryPage } from '../../Gallery';
@ -98,4 +100,26 @@ export class TopbarPage extends BasePage {
await this.get().locator(`.nc-icon-reload`).click();
await this.rootPage.waitForLoadState('networkidle');
}
async clickDownload(type: string, verificationFile = 'expectedData.txt') {
await this.get().locator(`.nc-toolbar-btn.nc-actions-menu-btn`).click();
const [download] = await Promise.all([
// Start waiting for the download
this.rootPage.waitForEvent('download'),
// Perform the action that initiates download
this.rootPage
.locator(`.nc-dropdown-actions-menu`)
.locator(`li.ant-dropdown-menu-item:has-text("${type}")`)
.click(),
]);
// Save downloaded file somewhere
await download.saveAs('./output/at.txt');
// verify downloaded content against expected content
const expectedData = fs.readFileSync(`./fixtures/${verificationFile}`, 'utf8').replace(/\r/g, '').split('\n');
const file = fs.readFileSync('./output/at.txt', 'utf8').replace(/\r/g, '').split('\n');
expect(file).toEqual(expectedData);
}
}

6
tests/playwright/tests/db/views/viewGridShare.spec.ts

@ -246,7 +246,7 @@ test.describe('Shared view', () => {
**/
// verify download
await sharedPage.grid.toolbar.clickDownload(
await sharedPage.grid.topbar.clickDownload(
'Download CSV',
isSqlite(context) || isPg(context) ? 'expectedDataSqlite.txt' : 'expectedData.txt'
);
@ -284,12 +284,12 @@ test.describe('Shared view', () => {
// verify if password request modal exists
const sharedPage2 = new DashboardPage(page, context.base);
await sharedPage2.rootPage.locator('input[placeholder="Enter password"]').fill('incorrect p@ssword');
await sharedPage2.rootPage.click('button:has-text("Unlock")');
await sharedPage2.rootPage.click('button[data-testid="nc-shared-view-password-submit-btn"]');
await sharedPage2.verifyToast({ message: 'INVALID_SHARED_VIEW_PASSWORD' });
// correct password
await sharedPage2.rootPage.locator('input[placeholder="Enter password"]').fill('p@ssword');
await sharedPage2.rootPage.click('button:has-text("Unlock")');
await sharedPage2.rootPage.click('button[data-testid="nc-shared-view-password-submit-btn"]');
// verify if download button is disabled
await sharedPage2.grid.toolbar.verifyDownloadDisabled();

Loading…
Cancel
Save