Browse Source

Merge pull request #6498 from nocodb/nc-fix/ws-dropdown-fix

Mobile UI fixes
pull/6431/head
Muhammed Mustafa 1 year ago committed by GitHub
parent
commit
7971c22491
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      packages/nc-gui/assets/nc-icons/download.svg
  2. 6
      packages/nc-gui/components/dashboard/Sidebar.vue
  3. 2
      packages/nc-gui/components/dashboard/Sidebar/TopSection.vue
  4. 5
      packages/nc-gui/components/dashboard/TreeView/TableNode.vue
  5. 18
      packages/nc-gui/components/dashboard/TreeView/ViewsList.vue
  6. 9
      packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue
  7. 9
      packages/nc-gui/components/dashboard/View.vue
  8. 2
      packages/nc-gui/components/nc/MenuItem.vue
  9. 2
      packages/nc-gui/components/nc/Pagination.vue
  10. 2
      packages/nc-gui/components/project/AllTables.vue
  11. 1
      packages/nc-gui/components/smartsheet/Toolbar.vue
  12. 14
      packages/nc-gui/components/smartsheet/expanded-form/Comments.vue
  13. 32
      packages/nc-gui/components/smartsheet/expanded-form/index.vue
  14. 32
      packages/nc-gui/components/smartsheet/grid/index.vue
  15. 6
      packages/nc-gui/components/smartsheet/header/Cell.vue
  16. 53
      packages/nc-gui/components/smartsheet/toolbar/OpenViewSidebarBtn.vue
  17. 3
      packages/nc-gui/lang/en.json
  18. 22
      packages/nc-gui/store/sidebar.ts
  19. 9
      tests/playwright/pages/Dashboard/Form/index.ts
  20. 4
      tests/playwright/pages/Dashboard/Kanban/index.ts
  21. 19
      tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts
  22. 10
      tests/playwright/pages/Dashboard/common/WorkspaceMenu/index.ts
  23. 3
      tests/playwright/pages/Dashboard/index.ts
  24. 8
      tests/playwright/tests/db/columns/columnAttachments.spec.ts
  25. 16
      tests/playwright/tests/db/features/baseShare.spec.ts
  26. 2
      tests/playwright/tests/db/features/verticalFillHandle.spec.ts

2
packages/nc-gui/assets/nc-icons/download.svg

@ -1,5 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 10V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V10" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M14 10V12.6667C14 13.0203 13.8595 13.3594 13.6095 13.6095C13.3594 13.8595 13.0203 14 12.6667 14H3.33333C2.97971 14 2.64057 13.8595 2.39052 13.6095C2.14048 13.3594 2 13.0203 2 12.6667V10" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.66675 6.66663L8.00008 9.99996L11.3334 6.66663" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M4.66675 6.66663L8.00008 9.99996L11.3334 6.66663" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M8 10V2" stroke="#4A5268" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/> <path d="M8 10V2" stroke="#1F293A" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 643 B

After

Width:  |  Height:  |  Size: 643 B

6
packages/nc-gui/components/dashboard/Sidebar.vue

@ -5,11 +5,15 @@ const { isWorkspaceLoading } = storeToRefs(workspaceStore)
const { isSharedBase } = storeToRefs(useProject()) const { isSharedBase } = storeToRefs(useProject())
const { isMobileMode } = useGlobal()
const treeViewDom = ref<HTMLElement>() const treeViewDom = ref<HTMLElement>()
const isTreeViewOnScrollTop = ref(false) const isTreeViewOnScrollTop = ref(false)
const checkScrollTopMoreThanZero = () => { const checkScrollTopMoreThanZero = () => {
if (isMobileMode.value) return
if (treeViewDom.value) { if (treeViewDom.value) {
if (treeViewDom.value.scrollTop > 0) { if (treeViewDom.value.scrollTop > 0) {
isTreeViewOnScrollTop.value = true isTreeViewOnScrollTop.value = true
@ -43,7 +47,7 @@ onUnmounted(() => {
</div> </div>
<div <div
ref="treeViewDom" ref="treeViewDom"
class="flex flex-col nc-scrollbar-dark-md flex-grow" class="flex flex-col nc-scrollbar-dark-md flex-grow xs:(border-transparent pt-2)"
:class="{ :class="{
'border-t-1': !isSharedBase, 'border-t-1': !isSharedBase,
'border-transparent': !isTreeViewOnScrollTop, 'border-transparent': !isTreeViewOnScrollTop,

2
packages/nc-gui/components/dashboard/Sidebar/TopSection.vue

@ -43,7 +43,7 @@ const navigateToSettings = () => {
</div> </div>
</template> </template>
<template v-else-if="!isSharedBase"> <template v-else-if="!isSharedBase">
<div class="flex flex-col p-1 gap-y-0.5 mt-0.25 mb-0.5 truncate"> <div class="xs:hidden flex flex-col p-1 gap-y-0.5 mt-0.25 mb-0.5 truncate">
<DashboardSidebarTopSectionHeader /> <DashboardSidebarTopSectionHeader />
<NcButton <NcButton

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

@ -46,6 +46,7 @@ const { setMenuContext, openRenameTableDialog, duplicateTable } = inject(TreeVie
const { loadViews: _loadViews } = useViewsStore() const { loadViews: _loadViews } = useViewsStore()
const { activeView } = storeToRefs(useViewsStore()) const { activeView } = storeToRefs(useViewsStore())
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
// todo: temp // todo: temp
const { projectTables } = storeToRefs(useTablesStore()) const { projectTables } = storeToRefs(useTablesStore())
@ -108,6 +109,10 @@ const onOpenTable = async () => {
isLoading.value = true isLoading.value = true
try { try {
await _openTable(table.value) await _openTable(table.value)
if (isMobileMode.value) {
isLeftSidebarOpen.value = false
}
} catch (e) { } catch (e) {
message.error(await extractSdkResponseErrorMsg(e)) message.error(await extractSdkResponseErrorMsg(e))
} finally { } finally {

18
packages/nc-gui/components/dashboard/TreeView/ViewsList.vue

@ -31,6 +31,8 @@ const emits = defineEmits<Emits>()
const project = inject(ProjectInj)! const project = inject(ProjectInj)!
const table = inject(SidebarTableInj)! const table = inject(SidebarTableInj)!
const { isLeftSidebarOpen } = storeToRefs(useSidebarStore())
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()
const { $e } = useNuxtApp() const { $e } = useNuxtApp()
@ -190,13 +192,17 @@ const initSortable = (el: HTMLElement) => {
onMounted(() => menuRef.value && initSortable(menuRef.value.$el)) onMounted(() => menuRef.value && initSortable(menuRef.value.$el))
/** Navigate to view by changing url param */ /** Navigate to view by changing url param */
function changeView(view: ViewType) { async function changeView(view: ViewType) {
navigateToView({ await navigateToView({
view, view,
tableId: table.value.id!, tableId: table.value.id!,
projectId: project.value.id!, projectId: project.value.id!,
hardReload: view.type === ViewTypes.FORM && selected.value[0] === view.id, hardReload: view.type === ViewTypes.FORM && selected.value[0] === view.id,
}) })
if (isMobileMode.value) {
isLeftSidebarOpen.value = false
}
} }
/** Rename a view */ /** Rename a view */
@ -351,13 +357,13 @@ function onOpenModal({
<template> <template>
<div <div
v-if="!views.length" v-if="!views.length"
class="text-gray-500 my-1.75" class="text-gray-500 my-1.75 xs:(my-2.5 text-base)"
:class="{ :class="{
'ml-19.25': isDefaultBase, 'ml-19.25 xs:ml-22.25': isDefaultBase,
'ml-24.75': !isDefaultBase, 'ml-24.75 xs:ml-30': !isDefaultBase,
}" }"
> >
No Views {{ $t('labels.noViews') }}
</div> </div>
<a-menu <a-menu

9
packages/nc-gui/components/dashboard/TreeView/ViewsNode.vue

@ -53,8 +53,6 @@ const activeView = inject(ActiveViewInj, ref())
const isLocked = inject(IsLockedInj, ref(false)) const isLocked = inject(IsLockedInj, ref(false))
const { rightSidebarState } = storeToRefs(useSidebarStore())
const isDefaultBase = computed(() => { const isDefaultBase = computed(() => {
const base = project.value?.bases?.find((b) => b.id === vModel.value.base_id) const base = project.value?.bases?.find((b) => b.id === vModel.value.base_id)
if (!base) return false if (!base) return false
@ -82,6 +80,7 @@ const onClick = useDebounceFn(() => {
/** Enable editing view name on dbl click */ /** Enable editing view name on dbl click */
function onDblClick() { function onDblClick() {
if (isMobileMode.value) return
if (!isUIAllowed('viewCreateOrEdit')) return if (!isUIAllowed('viewCreateOrEdit')) return
if (!isEditing.value) { if (!isEditing.value) {
@ -193,12 +192,6 @@ function onStopEdit() {
}, 250) }, 250)
} }
watch(rightSidebarState, () => {
if (rightSidebarState.value === 'peekCloseEnd') {
isDropdownOpen.value = false
}
})
function onRef(el: HTMLElement) { function onRef(el: HTMLElement) {
if (activeViewTitleOrId.value === vModel.value.id) { if (activeViewTitleOrId.value === vModel.value.id) {
nextTick(() => { nextTick(() => {

9
packages/nc-gui/components/dashboard/View.vue

@ -12,6 +12,7 @@ const {
leftSidebarWidthPercent, leftSidebarWidthPercent,
leftSideBarSize: sideBarSize, leftSideBarSize: sideBarSize,
leftSidebarState: sidebarState, leftSidebarState: sidebarState,
mobileNormalizedSidebarSize,
} = storeToRefs(useSidebarStore()) } = storeToRefs(useSidebarStore())
const wrapperRef = ref<HTMLDivElement>() const wrapperRef = ref<HTMLDivElement>()
@ -31,14 +32,6 @@ const { handleSidebarOpenOnMobileForNonViews } = useConfigStore()
const contentSize = computed(() => 100 - sideBarSize.value.current) const contentSize = computed(() => 100 - sideBarSize.value.current)
const mobileNormalizedSidebarSize = computed(() => {
if (isMobileMode.value) {
return isLeftSidebarOpen.value ? 100 : 0
}
return currentSidebarSize.value
})
const mobileNormalizedContentSize = computed(() => { const mobileNormalizedContentSize = computed(() => {
if (isMobileMode.value) { if (isMobileMode.value) {
return isLeftSidebarOpen.value ? 0 : 100 return isLeftSidebarOpen.value ? 0 : 100

2
packages/nc-gui/components/nc/MenuItem.vue

@ -18,7 +18,7 @@
.nc-menu-item > .ant-dropdown-menu-title-content { .nc-menu-item > .ant-dropdown-menu-title-content {
// Not Icon // Not Icon
:not(.nc-icon):not(.material-symbols) { :not(.nc-icon):not(.material-symbols) {
line-height: 0.95; line-height: 1.5;
} }
@apply flex flex-row items-center; @apply flex flex-row items-center;

2
packages/nc-gui/components/nc/Pagination.vue

@ -13,7 +13,7 @@ const current = useVModel(props, 'current', emits)
const pageSize = useVModel(props, 'pageSize', emits) const pageSize = useVModel(props, 'pageSize', emits)
const totalPages = computed(() => Math.ceil(total.value / pageSize.value)) const totalPages = computed(() => Math.max(Math.ceil(total.value / pageSize.value), 1))
const { isMobileMode } = useGlobal() const { isMobileMode } = useGlobal()

2
packages/nc-gui/components/project/AllTables.vue

@ -119,7 +119,7 @@ function openTableCreateDialog(baseIndex?: number | undefined) {
<style lang="scss" scoped> <style lang="scss" scoped>
.nc-project-view-all-table-btn { .nc-project-view-all-table-btn {
@apply flex flex-col gap-y-6 p-4 bg-gray-100 rounded-xl w-56 cursor-pointer text-gray-600 hover:(bg-gray-100 !text-black); @apply flex flex-col gap-y-6 p-4 bg-gray-100 rounded-xl w-56 cursor-pointer text-gray-600 hover:(bg-gray-200 !text-black);
.nc-icon { .nc-icon {
@apply h-10 w-10; @apply h-10 w-10;

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

@ -58,7 +58,6 @@ const { allowCSVDownload } = useSharedView()
:show-system-fields="false" :show-system-fields="false"
/> />
</template> </template>
<LazySmartsheetToolbarOpenViewSidebarBtn v-if="isViewSidebarAvailable" />
</template> </template>
</div> </div>
</template> </template>

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

@ -153,7 +153,7 @@ const processedAudit = (log: string) => {
</div> </div>
<div class="font-medium text-center my-6 text-gray-500">{{ $t('activity.startCommenting') }}</div> <div class="font-medium text-center my-6 text-gray-500">{{ $t('activity.startCommenting') }}</div>
</div> </div>
<div v-else class="flex flex-col h-full p-2 space-y-2 nc-scrollbar-md"> <div v-else class="flex flex-col h-full py-2 pl-2 pr-1 space-y-2 nc-scrollbar-md">
<div v-for="log of comments" :key="log.id"> <div v-for="log of comments" :key="log.id">
<div class="bg-white rounded-xl group border-1 gap-2 border-gray-200"> <div class="bg-white rounded-xl group border-1 gap-2 border-gray-200">
<div class="flex flex-col p-4 gap-3"> <div class="flex flex-col p-4 gap-3">
@ -198,14 +198,12 @@ const processedAudit = (log: string) => {
</div> </div>
</div> </div>
</div> </div>
<div v-if="hasEditPermission" class="h-16.5 p-2 rounded-b-xl bg-gray-50 gap-2 flex"> <div v-if="hasEditPermission" class="p-2 bg-gray-50 gap-2 flex">
<div class="flex flex-row items-end"> <div class="h-14 flex flex-row w-full bg-white py-2.75 px-1.5 items-center rounded-xl border-1 border-gray-200">
<GeneralUserIcon size="base" /> <GeneralUserIcon size="base" class="!w-10" />
</div>
<div class="flex flex-row bg-white py-2.75 px-1.5 items-center rounded-lg border-1 border-gray-200">
<a-input <a-input
v-model:value="comment" v-model:value="comment"
class="!rounded-lg border-1 bg-white !px-2 !py-2 !border-gray-200 nc-comment-box !outline-none" class="!rounded-lg border-1 bg-white !px-2.5 !py-2 !border-gray-200 nc-comment-box !outline-none"
placeholder="Start typing..." placeholder="Start typing..."
:bordered="false" :bordered="false"
@keyup.enter.prevent="saveComment" @keyup.enter.prevent="saveComment"
@ -217,7 +215,7 @@ const processedAudit = (log: string) => {
</div> </div>
</div> </div>
</div> </div>
<div v-else ref="commentsWrapperEl" class="flex flex-col h-full pl-1.5 pr-1 pt-1 nc-scrollbar-md space-y-2"> <div v-else ref="commentsWrapperEl" class="flex flex-col h-full pl-2 pr-1 pt-2 nc-scrollbar-md space-y-2">
<template v-if="audits.length === 0"> <template v-if="audits.length === 0">
<div class="flex flex-col text-center justify-center h-full"> <div class="flex flex-col text-center justify-center h-full">
<div class="text-center text-3xl text-gray-600"> <div class="text-center text-3xl text-gray-600">

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

@ -367,20 +367,32 @@ export default {
:width="commentsDrawer && isUIAllowed('commentList') ? 'min(80vw,1280px)' : 'min(80vw,1280px)'" :width="commentsDrawer && isUIAllowed('commentList') ? 'min(80vw,1280px)' : 'min(80vw,1280px)'"
:body-style="{ padding: 0 }" :body-style="{ padding: 0 }"
:closable="false" :closable="false"
size="large" size="small"
class="nc-drawer-expanded-form" class="nc-drawer-expanded-form"
:class="{ active: isExpanded }" :class="{ active: isExpanded }"
> >
<div class="h-[75vh] xs:(h-[95vh] max-h-full) max-h-215 flex flex-col p-6"> <div class="h-[85vh] xs:(max-h-full) max-h-215 flex flex-col p-6">
<div class="flex h-8 flex-shrink-0 w-full items-center nc-expanded-form-header relative mb-4 justify-between"> <div class="flex h-8 flex-shrink-0 w-full items-center nc-expanded-form-header relative mb-4 justify-between">
<template v-if="!isMobileMode"> <template v-if="!isMobileMode">
<div class="flex gap-3"> <div class="flex gap-3">
<div class="flex gap-2"> <div class="flex gap-2">
<NcButton v-if="props.showNextPrevIcons" type="secondary" class="nc-prev-arrow !w-10" @click="$emit('prev')"> <NcButton
<MdiChevronUp class="text-md text-gray-700" /> v-if="props.showNextPrevIcons"
:disabled="props.firstRow"
type="secondary"
class="nc-prev-arrow !w-10"
@click="$emit('prev')"
>
<MdiChevronUp class="text-md" />
</NcButton> </NcButton>
<NcButton v-if="!props.lastRow" type="secondary" class="nc-next-arrow !w-10" @click="onNext"> <NcButton
<MdiChevronDown class="text-md text-gray-700" /> v-if="props.showNextPrevIcons"
:disabled="props.lastRow"
type="secondary"
class="nc-next-arrow !w-10"
@click="onNext"
>
<MdiChevronDown class="text-md" />
</NcButton> </NcButton>
</div> </div>
<div v-if="displayValue" class="flex items-center truncate max-w-32 font-bold text-gray-800 text-xl"> <div v-if="displayValue" class="flex items-center truncate max-w-32 font-bold text-gray-800 text-xl">
@ -455,8 +467,8 @@ export default {
</div> </div>
</template> </template>
</div> </div>
<div ref="wrapper" class="flex flex-grow flex-row h-[calc(100%-3rem)] w-full gap-4"> <div ref="wrapper" class="flex flex-grow flex-row h-[calc(100%-4rem)] w-full gap-4">
<div class="flex w-full flex-col border-1 rounded-xl overflow-hidden border-gray-200 xs:(border-0 rounded-none)"> <div class="flex w-2/3 xs:w-full flex-col border-1 rounded-xl overflow-hidden border-gray-200 xs:(border-0 rounded-none)">
<div <div
class="flex flex-col flex-grow mt-2 h-full max-h-full nc-scrollbar-md !pb-2 items-center w-full bg-white p-4 xs:p-0" class="flex flex-col flex-grow mt-2 h-full max-h-full nc-scrollbar-md !pb-2 items-center w-full bg-white p-4 xs:p-0"
> >
@ -543,7 +555,7 @@ export default {
</div> </div>
<div <div
v-if="isUIAllowed('dataEdit')" v-if="isUIAllowed('dataEdit')"
class="w-full h-14 border-t-1 border-gray-200 bg-white flex items-center justify-end p-2 xs:(p-0 mt-4 border-t-0 gap-x-4 justify-between)" class="w-full h-16 border-t-1 border-gray-200 bg-white flex items-center justify-end p-3 xs:(p-0 mt-4 border-t-0 gap-x-4 justify-between)"
> >
<NcDropdown v-if="!isNew && isMobileMode"> <NcDropdown v-if="!isNew && isMobileMode">
<NcButton type="secondary" class="nc-expand-form-more-actions w-10"> <NcButton type="secondary" class="nc-expand-form-more-actions w-10">
@ -598,7 +610,7 @@ export default {
</div> </div>
<div <div
v-if="!isNew && commentsDrawer && isUIAllowed('commentList')" v-if="!isNew && commentsDrawer && isUIAllowed('commentList')"
class="nc-comments-drawer border-1 relative border-gray-200 w-[380px] bg-gray-50 rounded-xl min-w-0 overflow-hidden h-full xs:hidden" class="nc-comments-drawer border-1 relative border-gray-200 w-1/3 max-w-125 bg-gray-50 rounded-xl min-w-0 overflow-hidden h-full xs:hidden"
:class="{ active: commentsDrawer && isUIAllowed('commentList') }" :class="{ active: commentsDrawer && isUIAllowed('commentList') }"
> >
<LazySmartsheetExpandedFormComments /> <LazySmartsheetExpandedFormComments />

32
packages/nc-gui/components/smartsheet/grid/index.vue

@ -237,23 +237,21 @@ onMounted(() => {
/> />
</Suspense> </Suspense>
<Suspense> <SmartsheetExpandedForm
<LazySmartsheetExpandedForm v-if="expandedFormOnRowIdDlg"
v-if="expandedFormOnRowIdDlg" :key="routeQuery.rowId"
:key="routeQuery.rowId" v-model="expandedFormOnRowIdDlg"
v-model="expandedFormOnRowIdDlg" :row="{ row: {}, oldRow: {}, rowMeta: {} }"
:row="{ row: {}, oldRow: {}, rowMeta: {} }" :meta="meta"
:meta="meta" :state="expandedFormRowState"
:state="expandedFormRowState" :row-id="routeQuery.rowId"
:row-id="routeQuery.rowId" :view="view"
:view="view" show-next-prev-icons
show-next-prev-icons :first-row="getExpandedRowIndex() === 0"
:first-row="getExpandedRowIndex() === 0" :last-row="getExpandedRowIndex() === data.length - 1"
:last-row="getExpandedRowIndex() === data.length - 1" @next="navigateToSiblingRow(NavigateDir.NEXT)"
@next="navigateToSiblingRow(NavigateDir.NEXT)" @prev="navigateToSiblingRow(NavigateDir.PREV)"
@prev="navigateToSiblingRow(NavigateDir.PREV)" />
/>
</Suspense>
<Suspense> <Suspense>
<LazyDlgBulkUpdate <LazyDlgBulkUpdate

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

@ -11,6 +11,8 @@ interface Props {
const props = defineProps<Props>() const props = defineProps<Props>()
const { isMobileMode } = useGlobal()
const hideMenu = toRef(props, 'hideMenu') const hideMenu = toRef(props, 'hideMenu')
const isForm = inject(IsFormInj, ref(false)) const isForm = inject(IsFormInj, ref(false))
@ -42,13 +44,13 @@ const closeAddColumnDropdown = () => {
} }
const openHeaderMenu = () => { const openHeaderMenu = () => {
if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit')) { if (!isForm.value && !isExpandedForm.value && isUIAllowed('fieldEdit') && !isMobileMode.value) {
editColumnDropdown.value = true editColumnDropdown.value = true
} }
} }
const openDropDown = () => { const openDropDown = () => {
if (isForm.value || isExpandedForm.value || !isUIAllowed('fieldEdit')) return if (isForm.value || isExpandedForm.value || (!isUIAllowed('fieldEdit') && !isMobileMode.value)) return
isDropDownOpen.value = !isDropDownOpen.value isDropDownOpen.value = !isDropDownOpen.value
} }
</script> </script>

53
packages/nc-gui/components/smartsheet/toolbar/OpenViewSidebarBtn.vue

@ -1,53 +0,0 @@
<script lang="ts" setup>
const { isRightSidebarOpen: _isRightSidebarOpen } = storeToRefs(useSidebarStore())
const isRightSidebarOpen = ref(_isRightSidebarOpen.value)
watch(_isRightSidebarOpen, (val) => {
if (val) {
isRightSidebarOpen.value = true
} else {
setTimeout(() => {
isRightSidebarOpen.value = false
}, 300)
}
})
const onClick = () => {
if (_isRightSidebarOpen.value) return
_isRightSidebarOpen.value = !_isRightSidebarOpen.value
}
</script>
<template>
<NcTooltip
placement="bottomLeft"
hide-on-click
class="transition-all duration-100"
:class="{
'!w-0 !opacity-0': isRightSidebarOpen,
'!w-8 !opacity-100 mr-2': !isRightSidebarOpen,
}"
>
<template #title>
{{
isRightSidebarOpen
? `${$t('general.hide')} ${$t('objects.sidebar').toLowerCase()}`
: `${$t('general.show')} ${$t('objects.sidebar').toLowerCase()}`
}}
</template>
<NcButton
type="text"
size="small"
class="nc-sidebar-right-toggle-icon !text-gray-600 !hover:text-gray-800"
:class="{
'invisible !w-0': isRightSidebarOpen,
}"
@click="onClick"
>
<div class="flex items-center text-inherit">
<GeneralIcon icon="doubleLeftArrow" class="duration-150 transition-all !text-lg -mt-0.25" />
</div>
</NcButton>
</NcTooltip>
</template>

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

@ -408,7 +408,8 @@
"addRowGrid": "Manually add data in grid view", "addRowGrid": "Manually add data in grid view",
"addRowForm": "Enter record data through a form", "addRowForm": "Enter record data through a form",
"noAccess": "No access", "noAccess": "No access",
"restApis": "Rest APIs" "restApis": "Rest APIs",
"noViews": "No Views"
}, },
"activity": { "activity": {
"moveProject": "Move Project", "moveProject": "Move Project",

22
packages/nc-gui/store/sidebar.ts

@ -4,6 +4,7 @@ import { MAX_WIDTH_FOR_MOBILE_MODE } from '~/lib'
export const useSidebarStore = defineStore('sidebarStore', () => { export const useSidebarStore = defineStore('sidebarStore', () => {
const { width } = useWindowSize() const { width } = useWindowSize()
const isViewPortMobile = () => width.value < MAX_WIDTH_FOR_MOBILE_MODE const isViewPortMobile = () => width.value < MAX_WIDTH_FOR_MOBILE_MODE
const { isMobileMode } = useGlobal()
const isLeftSidebarOpen = ref(!isViewPortMobile()) const isLeftSidebarOpen = ref(!isViewPortMobile())
const isRightSidebarOpen = ref(true) const isRightSidebarOpen = ref(true)
@ -15,27 +16,28 @@ export const useSidebarStore = defineStore('sidebarStore', () => {
current: leftSidebarWidthPercent.value, current: leftSidebarWidthPercent.value,
}) })
const rightSidebarSize = ref({
old: 17.5,
current: 17.5,
})
const leftSidebarState = ref< const leftSidebarState = ref<
'openStart' | 'openEnd' | 'hiddenStart' | 'hiddenEnd' | 'peekOpenStart' | 'peekOpenEnd' | 'peekCloseOpen' | 'peekCloseEnd' 'openStart' | 'openEnd' | 'hiddenStart' | 'hiddenEnd' | 'peekOpenStart' | 'peekOpenEnd' | 'peekCloseOpen' | 'peekCloseEnd'
>(isLeftSidebarOpen.value ? 'openEnd' : 'hiddenEnd') >(isLeftSidebarOpen.value ? 'openEnd' : 'hiddenEnd')
const rightSidebarState = ref< const mobileNormalizedSidebarSize = computed(() => {
'openStart' | 'openEnd' | 'hiddenStart' | 'hiddenEnd' | 'peekOpenStart' | 'peekOpenEnd' | 'peekCloseOpen' | 'peekCloseEnd' if (isMobileMode.value) {
>(isRightSidebarOpen.value ? 'openEnd' : 'hiddenEnd') return isLeftSidebarOpen.value ? 100 : 0
}
return leftSideBarSize.value.current
})
const leftSidebarWidth = computed(() => (width.value * mobileNormalizedSidebarSize.value) / 100)
return { return {
isLeftSidebarOpen, isLeftSidebarOpen,
isRightSidebarOpen, isRightSidebarOpen,
rightSidebarSize,
leftSidebarWidthPercent, leftSidebarWidthPercent,
leftSideBarSize, leftSideBarSize,
leftSidebarState, leftSidebarState,
rightSidebarState, leftSidebarWidth,
mobileNormalizedSidebarSize,
} }
}) })

9
tests/playwright/pages/Dashboard/Form/index.ts

@ -244,15 +244,6 @@ export class FormPage extends BasePage {
async verifyStatePostSubmit(param: { message?: string; submitAnotherForm?: boolean; showBlankForm?: boolean }) { async verifyStatePostSubmit(param: { message?: string; submitAnotherForm?: boolean; showBlankForm?: boolean }) {
if (undefined !== param.message) { if (undefined !== param.message) {
let retryCounter = 0;
while (retryCounter <= 5) {
const msg = await this.getFormAfterSubmit().innerText();
if (msg.includes('Form submitted successfully')) {
break;
}
await this.rootPage.waitForTimeout(100 * retryCounter);
retryCounter++;
}
await expect(this.getFormAfterSubmit()).toContainText(param.message); await expect(this.getFormAfterSubmit()).toContainText(param.message);
} }
if (true === param.submitAnotherForm) { if (true === param.submitAnotherForm) {

4
tests/playwright/pages/Dashboard/Kanban/index.ts

@ -100,9 +100,13 @@ export class KanbanPage extends BasePage {
async verifyCardOrder(param: { order: string[]; stackIndex: number }) { async verifyCardOrder(param: { order: string[]; stackIndex: number }) {
const { order, stackIndex } = param; const { order, stackIndex } = param;
const stack = this.get().locator(`.nc-kanban-stack`).nth(stackIndex); const stack = this.get().locator(`.nc-kanban-stack`).nth(stackIndex);
for (let i = 0; i < order.length; i++) { for (let i = 0; i < order.length; i++) {
const card = stack.locator(`.nc-kanban-item`).nth(i); const card = stack.locator(`.nc-kanban-item`).nth(i);
await (await card.elementHandle())?.waitForElementState('stable');
await card.scrollIntoViewIfNeeded(); await card.scrollIntoViewIfNeeded();
const cardTitle = card.locator(`.nc-cell`); const cardTitle = card.locator(`.nc-cell`);
await expect(cardTitle).toHaveText(order[i]); await expect(cardTitle).toHaveText(order[i]);

19
tests/playwright/pages/Dashboard/common/LeftSidebar/index.ts

@ -1,4 +1,4 @@
import { Locator } from '@playwright/test'; import { expect, Locator } from '@playwright/test';
import { DashboardPage } from '../../index'; import { DashboardPage } from '../../index';
import BasePage from '../../../Base'; import BasePage from '../../../Base';
import { getTextExcludeIconText } from '../../../../tests/utils/general'; import { getTextExcludeIconText } from '../../../../tests/utils/general';
@ -20,7 +20,7 @@ export class LeftSidebarPage extends BasePage {
super(dashboard.rootPage); super(dashboard.rootPage);
this.dashboard = dashboard; this.dashboard = dashboard;
this.btn_workspace = this.get().locator('.nc-sidebar-header'); this.btn_workspace = this.get().locator('.nc-workspace-menu');
this.btn_newProject = this.get().locator('[data-testid="nc-sidebar-create-project-btn"]'); this.btn_newProject = this.get().locator('[data-testid="nc-sidebar-create-project-btn"]');
this.btn_cmdK = this.get().locator('[data-testid="nc-sidebar-search-btn"]'); this.btn_cmdK = this.get().locator('[data-testid="nc-sidebar-search-btn"]');
this.btn_teamAndSettings = this.get().locator('[data-testid="nc-sidebar-team-settings-btn"]'); this.btn_teamAndSettings = this.get().locator('[data-testid="nc-sidebar-team-settings-btn"]');
@ -57,6 +57,10 @@ export class LeftSidebarPage extends BasePage {
return await this.btn_workspace.getAttribute('data-workspace-title'); return await this.btn_workspace.getAttribute('data-workspace-title');
} }
async verifyWorkspaceName({ title }: { title: string }) {
await expect(this.btn_workspace.locator('.nc-workspace-title')).toHaveText(title);
}
async createWorkspace({ title }: { title: string }) { async createWorkspace({ title }: { title: string }) {
await this.clickWorkspace(); await this.clickWorkspace();
await this.modal_workspace.locator('.ant-dropdown-menu-item:has-text("Create New Workspace")').waitFor(); await this.modal_workspace.locator('.ant-dropdown-menu-item:has-text("Create New Workspace")').waitFor();
@ -69,6 +73,13 @@ export class LeftSidebarPage extends BasePage {
await inputModal.locator('button.ant-btn-primary').click(); await inputModal.locator('button.ant-btn-primary').click();
} }
async verifyWorkspaceCount({ count }: { count: number }) {
await this.clickWorkspace();
// TODO: THere is one extra html attribute
await expect(this.rootPage.getByTestId('nc-workspace-list')).toHaveCount(count + 1);
}
async getWorkspaceList() { async getWorkspaceList() {
const ws = []; const ws = [];
await this.clickWorkspace(); await this.clickWorkspace();
@ -88,6 +99,8 @@ export class LeftSidebarPage extends BasePage {
await this.clickWorkspace(); await this.clickWorkspace();
const nodes = this.modal_workspace.locator('[data-testid="nc-workspace-list"]'); const nodes = this.modal_workspace.locator('[data-testid="nc-workspace-list"]');
await this.rootPage.waitForTimeout(2000);
for (let i = 0; i < (await nodes.count()); i++) { for (let i = 0; i < (await nodes.count()); i++) {
const text = await getTextExcludeIconText(nodes.nth(i)); const text = await getTextExcludeIconText(nodes.nth(i));
if (text.toLowerCase() === param.title.toLowerCase()) { if (text.toLowerCase() === param.title.toLowerCase()) {
@ -96,5 +109,7 @@ export class LeftSidebarPage extends BasePage {
} }
} }
await this.rootPage.keyboard.press('Escape'); await this.rootPage.keyboard.press('Escape');
await this.rootPage.waitForTimeout(3500);
} }
} }

10
tests/playwright/pages/Dashboard/common/WorkspaceMenu/index.ts

@ -17,6 +17,16 @@ export class WorkspaceMenuObject extends BasePage {
await this.rootPage.locator('[data-testid="nc-workspace-menu"]').click(); await this.rootPage.locator('[data-testid="nc-workspace-menu"]').click();
} }
async switchWorkspace({ workspaceTitle }: { workspaceTitle: string }) {
await this.toggle();
await this.rootPage.waitForTimeout(2500);
await this.rootPage.getByTestId('nc-workspace-list').getByText(workspaceTitle).click({
force: true,
});
await this.rootPage.keyboard.press('Escape');
await this.rootPage.waitForTimeout(4000);
}
async click({ menu, subMenu }: { menu: string; subMenu: string }) { async click({ menu, subMenu }: { menu: string; subMenu: string }) {
const pMenu = this.rootPage.locator(`.nc-dropdown-workspace-menu:visible`); const pMenu = this.rootPage.locator(`.nc-dropdown-workspace-menu:visible`);
await pMenu.locator(`div.nc-workspace-menu-item:has-text("${menu}"):visible`).click(); await pMenu.locator(`div.nc-workspace-menu-item:has-text("${menu}"):visible`).click();

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

@ -189,8 +189,11 @@ export class DashboardPage extends BasePage {
async signOut() { async signOut() {
await this.sidebar.userMenu.click(); await this.sidebar.userMenu.click();
await this.rootPage.waitForTimeout(1000);
await this.rootPage.getByTestId('nc-sidebar-user-logout').waitFor({ state: 'visible' }); await this.rootPage.getByTestId('nc-sidebar-user-logout').waitFor({ state: 'visible' });
await this.sidebar.userMenu.clickLogout(); await this.sidebar.userMenu.clickLogout();
await this.rootPage.waitForTimeout(1000);
await this.rootPage.locator('[data-testid="nc-form-signin"]:visible').waitFor(); await this.rootPage.locator('[data-testid="nc-form-signin"]:visible').waitFor();
await new Promise(resolve => setTimeout(resolve, 150)); await new Promise(resolve => setTimeout(resolve, 150));

8
tests/playwright/tests/db/columns/columnAttachments.spec.ts

@ -31,6 +31,9 @@ test.describe('Attachment column', () => {
columnHeader: 'testAttach', columnHeader: 'testAttach',
filePath: filepath, filePath: filepath,
}); });
await dashboard.rootPage.waitForTimeout(500);
await dashboard.grid.cell.attachment.verifyFile({ await dashboard.grid.cell.attachment.verifyFile({
index: i, index: i,
columnHeader: 'testAttach', columnHeader: 'testAttach',
@ -41,6 +44,9 @@ test.describe('Attachment column', () => {
columnHeader: 'testAttach', columnHeader: 'testAttach',
filePath: [`${process.cwd()}/fixtures/sampleFiles/sampleImage.jpeg`], filePath: [`${process.cwd()}/fixtures/sampleFiles/sampleImage.jpeg`],
}); });
await dashboard.rootPage.waitForTimeout(1000);
await dashboard.grid.cell.attachment.verifyFile({ await dashboard.grid.cell.attachment.verifyFile({
index: 4, index: 4,
columnHeader: 'testAttach', columnHeader: 'testAttach',
@ -74,7 +80,7 @@ test.describe('Attachment column', () => {
filePath: [`${process.cwd()}/fixtures/sampleFiles/1.json`], filePath: [`${process.cwd()}/fixtures/sampleFiles/1.json`],
}); });
await sharedForm.rootPage.waitForTimeout(500); await sharedForm.rootPage.waitForTimeout(1000);
await sharedForm.submit(); await sharedForm.submit();
await sharedForm.verifySuccessMessage(); await sharedForm.verifySuccessMessage();
await newPage.close(); await newPage.close();

16
tests/playwright/tests/db/features/baseShare.spec.ts

@ -1,15 +1,16 @@
import { test } from '@playwright/test'; import { test } from '@playwright/test';
import { DashboardPage } from '../../../pages/Dashboard'; import { DashboardPage } from '../../../pages/Dashboard';
import setup, { unsetup } from '../../../setup'; import setup, { NcContext, unsetup } from '../../../setup';
import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar'; import { ToolbarPage } from '../../../pages/Dashboard/common/Toolbar';
import { LoginPage } from '../../../pages/LoginPage'; import { LoginPage } from '../../../pages/LoginPage';
import { getDefaultPwd } from '../../../tests/utils/general'; import { getDefaultPwd } from '../../../tests/utils/general';
import { isEE } from '../../../setup/db';
// To be enabled after shared base is implemented // To be enabled after shared base is implemented
test.describe('Shared base', () => { test.describe('Shared base', () => {
let dashboard: DashboardPage; let dashboard: DashboardPage;
let toolbar: ToolbarPage; let toolbar: ToolbarPage;
let context: any; let context: NcContext;
let loginPage: LoginPage; let loginPage: LoginPage;
async function roleTest(role: string) { async function roleTest(role: string) {
@ -83,8 +84,17 @@ test.describe('Shared base', () => {
withoutPrefix: true, withoutPrefix: true,
}); });
// await dashboard.treeView.openProject({ title: context.project.title }); await dashboard.rootPage.waitForTimeout(1000);
if (isEE()) {
await dashboard.grid.workspaceMenu.switchWorkspace({
workspaceTitle: context.workspace.title,
});
}
await dashboard.treeView.openProject({ title: context.project.title, context });
await dashboard.treeView.openTable({ title: 'Country' }); await dashboard.treeView.openTable({ title: 'Country' });
url = await dashboard.grid.topbar.getSharedBaseUrl({ role: 'viewer', enableSharedBase: false }); url = await dashboard.grid.topbar.getSharedBaseUrl({ role: 'viewer', enableSharedBase: false });
await dashboard.rootPage.waitForTimeout(2000); await dashboard.rootPage.waitForTimeout(2000);

2
tests/playwright/tests/db/features/verticalFillHandle.spec.ts

@ -212,7 +212,7 @@ test.describe('Fill Handle', () => {
await unsetup(p.context); await unsetup(p.context);
}); });
test('Miscellaneous (Checkbox, attachment) @flaky', async () => { test('Miscellaneous (Checkbox, attachment)', async () => {
const fields = [ const fields = [
{ title: 'Checkbox', value: 'true', type: 'checkbox' }, { title: 'Checkbox', value: 'true', type: 'checkbox' },
{ title: 'Attachment', value: `${process.cwd()}/fixtures/sampleFiles/1.json`, type: 'attachment' }, { title: 'Attachment', value: `${process.cwd()}/fixtures/sampleFiles/1.json`, type: 'attachment' },

Loading…
Cancel
Save