Browse Source

Merge pull request #3830 from nocodb/fix/mem-leak-avoid-rerender

Fix : Avoid re-rendering smartsheet
pull/3904/head
navi 2 years ago committed by GitHub
parent
commit
0637c811d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      packages/nc-gui/components/smartsheet/Form.vue
  2. 9
      packages/nc-gui/components/smartsheet/Gallery.vue
  3. 35
      packages/nc-gui/components/smartsheet/Grid.vue
  4. 13
      packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue
  5. 8
      packages/nc-gui/components/smartsheet/sidebar/index.vue
  6. 7
      packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue
  7. 2
      packages/nc-gui/composables/useViews.ts
  8. 4
      packages/nc-gui/pages/[projectType]/[projectId]/index.vue
  9. 2
      packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue
  10. 24
      packages/nc-gui/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue
  11. 1
      packages/nc-gui/pages/[projectType]/form/[viewId]/index.vue
  12. 1
      scripts/cypress/integration/common/00_pre_configurations.js
  13. 21
      scripts/cypress/integration/common/4c_form_view_detailed.js
  14. 2
      scripts/cypress/integration/common/5a_user_role.js
  15. 2
      scripts/cypress/integration/common/9a_QuickTest.js
  16. 4
      scripts/cypress/support/commands.js
  17. 1
      scripts/cypress/support/page_objects/navigation.js

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

@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import Draggable from 'vuedraggable' import Draggable from 'vuedraggable'
import { RelationTypes, UITypes, getSystemColumns, isVirtualCol } from 'nocodb-sdk' import { RelationTypes, UITypes, ViewTypes, getSystemColumns, isVirtualCol } from 'nocodb-sdk'
import { import {
ActiveViewInj, ActiveViewInj,
IsFormInj, IsFormInj,
@ -375,6 +375,12 @@ onMounted(async () => {
await loadFormView() await loadFormView()
setFormData() setFormData()
}) })
watch(view, (nextView) => {
if (nextView?.type === ViewTypes.FORM) {
reloadEventHook.trigger()
}
})
</script> </script>
<template> <template>

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

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { isVirtualCol } from 'nocodb-sdk' import { ViewTypes, isVirtualCol } from 'nocodb-sdk'
import { import {
ActiveViewInj, ActiveViewInj,
ChangePageInj, ChangePageInj,
@ -159,6 +159,13 @@ onMounted(async () => {
// provide view data reload hook as fallback to row data reload // provide view data reload hook as fallback to row data reload
provide(ReloadRowDataHookInj, reloadViewDataHook) provide(ReloadRowDataHookInj, reloadViewDataHook)
watch(view, async (nextView) => {
if (nextView?.type === ViewTypes.GALLERY) {
await loadData()
await loadGalleryData()
}
})
</script> </script>
<template> <template>

35
packages/nc-gui/components/smartsheet/Grid.vue

@ -161,16 +161,6 @@ openNewRecordFormHook?.on(async () => {
expandForm(newRow, undefined, true) expandForm(newRow, undefined, true)
}) })
watch(
() => view.value?.id,
async (next, old) => {
if (next && old && next !== old) {
await loadData()
}
},
{ immediate: true },
)
const onresize = (colID: string, event: any) => { const onresize = (colID: string, event: any) => {
updateWidth(colID, event.detail) updateWidth(colID, event.detail)
} }
@ -239,7 +229,6 @@ useEventListener(document, 'keyup', async (e: KeyboardEvent) => {
/** On clicking outside of table reset active cell */ /** On clicking outside of table reset active cell */
const smartTable = ref(null) const smartTable = ref(null)
onClickOutside(smartTable, () => { onClickOutside(smartTable, () => {
clearRangeRows() clearRangeRows()
if (selected.col === null) return if (selected.col === null) return
@ -247,6 +236,7 @@ onClickOutside(smartTable, () => {
const activeCol = fields.value[selected.col] const activeCol = fields.value[selected.col]
if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return if (editEnabled && (isVirtualCol(activeCol) || activeCol.uidt === UITypes.JSON)) return
selected.row = null selected.row = null
selected.col = null selected.col = null
}) })
@ -326,7 +316,17 @@ const expandedFormOnRowIdDlg = computed({
provide(ReloadRowDataHookInj, reloadViewDataHook) provide(ReloadRowDataHookInj, reloadViewDataHook)
// trigger initial data load in grid // trigger initial data load in grid
reloadViewDataHook.trigger() // reloadViewDataHook.trigger()
watch(
() => view.value?.id,
async (next, old) => {
if (next && next !== old) {
await loadData()
}
},
{ immediate: true },
)
</script> </script>
<template> <template>
@ -489,12 +489,7 @@ reloadViewDataHook.trigger()
v-else v-else
v-model="row.row[columnObj.title]" v-model="row.row[columnObj.title]"
:column="columnObj" :column="columnObj"
:edit-enabled=" :edit-enabled="hasEditPermission && editEnabled && selected.col === colIndex && selected.row === rowIndex"
isUIAllowed('xcDatatableEditable') &&
editEnabled &&
selected.col === colIndex &&
selected.row === rowIndex
"
:row-index="rowIndex" :row-index="rowIndex"
:active="selected.col === colIndex && selected.row === rowIndex" :active="selected.col === colIndex && selected.row === rowIndex"
@update:edit-enabled="editEnabled = false" @update:edit-enabled="editEnabled = false"
@ -508,7 +503,7 @@ reloadViewDataHook.trigger()
</template> </template>
</LazySmartsheetRow> </LazySmartsheetRow>
<tr v-if="!isView && !isLocked && isUIAllowed('xcDatatableEditable') && !isSqlView"> <tr v-if="!isView && !isLocked && hasEditPermission && !isSqlView">
<td <td
v-e="['c:row:add:grid-bottom']" v-e="['c:row:add:grid-bottom']"
:colspan="visibleColLength + 1" :colspan="visibleColLength + 1"
@ -527,7 +522,7 @@ reloadViewDataHook.trigger()
</tbody> </tbody>
</table> </table>
<template v-if="!isLocked && isUIAllowed('xcDatatableEditable')" #overlay> <template v-if="!isLocked && hasEditPermission" #overlay>
<a-menu class="shadow !rounded !py-0" @click="contextMenu = false"> <a-menu class="shadow !rounded !py-0" @click="contextMenu = false">
<a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)"> <a-menu-item v-if="contextMenuTarget" @click="deleteRow(contextMenuTarget.row)">
<div v-e="['a:row:delete']" class="nc-project-menu-item"> <div v-e="['a:row:delete']" class="nc-project-menu-item">

13
packages/nc-gui/components/smartsheet/sidebar/MenuTop.vue

@ -43,8 +43,6 @@ const { api } = useApi()
const router = useRouter() const router = useRouter()
const route = useRoute()
/** Selected view(s) for menu */ /** Selected view(s) for menu */
const selected = ref<string[]>([]) const selected = ref<string[]>([])
@ -214,12 +212,18 @@ function openDeleteDialog(view: Record<string, any>) {
close(1000) close(1000)
} }
} }
watch(views, (nextViews) => {
if (nextViews?.length && (!activeView.value || !nextViews.includes(activeView.value))) {
activeView.value = nextViews[0]
}
})
</script> </script>
<template> <template>
<a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu flex-1" :selected-keys="selected"> <a-menu ref="menuRef" :class="{ dragging }" class="nc-views-menu flex-1" :selected-keys="selected">
<LazySmartsheetSidebarRenameableMenuItem <LazySmartsheetSidebarRenameableMenuItem
v-for="(view, index) of views" v-for="view of views"
:id="view.id" :id="view.id"
:key="view.id" :key="view.id"
:view="view" :view="view"
@ -227,8 +231,7 @@ function openDeleteDialog(view: Record<string, any>) {
class="transition-all ease-in duration-300" class="transition-all ease-in duration-300"
:class="{ :class="{
'bg-gray-100': isMarked === view.id, 'bg-gray-100': isMarked === view.id,
'active': 'active': activeView.id === view.id,
(route.params.viewTitle && route.params.viewTitle === view.title) || (route.params.viewTitle === '' && index === 0),
[`nc-view-item nc-${viewTypeAlias[view.type] || view.type}-view-item`]: true, [`nc-view-item nc-${viewTypeAlias[view.type] || view.type}-view-item`]: true,
}" }"
@change-view="changeView" @change-view="changeView"

8
packages/nc-gui/components/smartsheet/sidebar/index.vue

@ -72,6 +72,10 @@ watch(
}) })
} }
} }
} else {
if (nextViews?.length && activeView.value !== nextViews[0]) {
activeView.value = nextViews[0]
}
} }
/** if active view is not found, set it to first view */ /** if active view is not found, set it to first view */
if (!activeView.value && nextViews.length) { if (!activeView.value && nextViews.length) {
@ -90,8 +94,8 @@ function openModal({ type, title = '', copyViewId }: { type: ViewTypes; title: s
} }
/** Handle view creation */ /** Handle view creation */
function onCreate(view: ViewType) { async function onCreate(view: ViewType) {
views.value.push(view) await loadViews()
router.push({ params: { viewTitle: view.title || '' } }) router.push({ params: { viewTitle: view.title || '' } })
modalOpen = false modalOpen = false
$e('a:view:create', { view: view.type }) $e('a:view:create', { view: view.type })

7
packages/nc-gui/components/smartsheet/toolbar/FieldsMenu.vue

@ -86,14 +86,16 @@ const onMove = (_event: { moved: { newIndex: number } }) => {
const coverImageColumnId = computed({ const coverImageColumnId = computed({
get: () => get: () =>
activeView.value?.type === ViewTypes.GALLERY ? (activeView.value?.view as GalleryType).fk_cover_image_col_id : undefined, activeView.value?.type === ViewTypes.GALLERY && activeView.value?.view
? (activeView.value?.view as GalleryType).fk_cover_image_col_id
: undefined,
set: async (val) => { set: async (val) => {
if (val && activeView.value?.type === ViewTypes.GALLERY && activeView.value?.id && activeView.value?.view) { if (val && activeView.value?.type === ViewTypes.GALLERY && activeView.value?.id && activeView.value?.view) {
await $api.dbView.galleryUpdate(activeView.value?.id, { await $api.dbView.galleryUpdate(activeView.value?.id, {
...activeView.value?.view, ...activeView.value?.view,
fk_cover_image_col_id: val, fk_cover_image_col_id: val,
}) })
;(activeView.value?.view as GalleryType).fk_cover_image_col_id = val ;(activeView.value.view as GalleryType).fk_cover_image_col_id = val
reloadViewMetaHook.trigger() reloadViewMetaHook.trigger()
} }
}, },
@ -203,6 +205,7 @@ const getIcon = (c: ColumnType) =>
:deep(.ant-checkbox-inner) { :deep(.ant-checkbox-inner) {
@apply transform scale-60; @apply transform scale-60;
} }
:deep(.ant-checkbox) { :deep(.ant-checkbox) {
@apply top-auto; @apply top-auto;
} }

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

@ -18,7 +18,7 @@ export function useViews(meta: MaybeRef<TableType | undefined>) {
} }
} }
watch(() => meta, loadViews, { immediate: true }) watch(meta, loadViews, { immediate: true })
return { views: $$(views), loadViews } return { views: $$(views), loadViews }
} }

4
packages/nc-gui/pages/[projectType]/[projectId]/index.vue

@ -491,10 +491,10 @@ onBeforeUnmount(reset)
</a-layout-sider> </a-layout-sider>
</template> </template>
<div :key="$route.fullPath.split('?')[0]"> <div>
<LazyDashboardSettingsModal v-model="dialogOpen" :open-key="openDialogKey" /> <LazyDashboardSettingsModal v-model="dialogOpen" :open-key="openDialogKey" />
<NuxtPage /> <NuxtPage :page-key="$route.params.projectId" />
<LazyGeneralPreviewAs float /> <LazyGeneralPreviewAs float />
</div> </div>

2
packages/nc-gui/pages/[projectType]/[projectId]/index/index.vue

@ -79,7 +79,7 @@ function onEdit(targetKey: number, action: 'add' | 'remove' | string) {
</div> </div>
<div class="w-full min-h-[300px] flex-auto"> <div class="w-full min-h-[300px] flex-auto">
<NuxtPage /> <NuxtPage :page-key="$route.name" />
</div> </div>
</div> </div>
</div> </div>

24
packages/nc-gui/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue

@ -15,20 +15,22 @@ const activeTab = inject(
computed(() => ({} as TabItem)), computed(() => ({} as TabItem)),
) )
/** wait until table list loads since meta load requires table list **/ watch(
until(tables) () => route.params.title,
.toMatch((tables) => tables.length > 0) (tableTitle) => {
.then(() => { /** wait until table list loads since meta load requires table list **/
getMeta(route.params.title as string, true).finally(() => (loading.value = false)) until(tables)
}) .toMatch((tables) => tables.length > 0)
.then(() => {
getMeta(tableTitle as string, true).finally(() => (loading.value = false))
})
},
{ immediate: true },
)
</script> </script>
<template> <template>
<div class="w-full h-full"> <div class="w-full h-full">
<div v-if="loading" class="flex items-center justify-center h-full w-full"> <LazyTabsSmartsheet :active-tab="activeTab" />
<a-spin size="large" />
</div>
<LazyTabsSmartsheet v-else :key="route.params.title" :active-tab="activeTab" />
</div> </div>
</template> </template>

1
packages/nc-gui/pages/[projectType]/form/[viewId]/index.vue

@ -14,7 +14,6 @@ const {
passwordDlg, passwordDlg,
password, password,
loadSharedView, loadSharedView,
isLoading,
} = useSharedFormStoreOrThrow() } = useSharedFormStoreOrThrow()
function isRequired(_columnObj: Record<string, any>, required = false) { function isRequired(_columnObj: Record<string, any>, required = false) {

1
scripts/cypress/integration/common/00_pre_configurations.js

@ -248,6 +248,7 @@ export const genTest = (apiType, dbType) => {
.should("be.visible") .should("be.visible")
.click(); .click();
cy.get("button.ant-tabs-tab-remove").should("not.exist"); cy.get("button.ant-tabs-tab-remove").should("not.exist");
cy.wait(2000);
// first instance of updating local storage information // first instance of updating local storage information
cy.saveLocalStorage(); cy.saveLocalStorage();

21
scripts/cypress/integration/common/4c_form_view_detailed.js

@ -214,9 +214,9 @@ export const genTest = (apiType, dbType) => {
// submit button & validate // submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click(); cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(".ant-alert-message") cy.get(
.contains("Successfully submitted form data") ".ant-alert-message :contains('Successfully submitted form data')"
.should("exist"); ).should("exist");
// end of test removes newly added rows from table. that step validates if row was successfully added. // end of test removes newly added rows from table. that step validates if row was successfully added.
}); });
@ -238,9 +238,9 @@ export const genTest = (apiType, dbType) => {
// submit button & validate // submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click(); cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(".ant-alert-message") cy.get(".ant-alert-message :contains('Congratulations!')").should(
.contains("Congratulations!") "exist"
.should("exist"); );
// end of test removes newly added rows from table. that step validates if row was successfully added. // end of test removes newly added rows from table. that step validates if row was successfully added.
}); });
@ -262,9 +262,9 @@ export const genTest = (apiType, dbType) => {
// submit button & validate // submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click(); cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(".ant-alert-message") cy.get(".ant-alert-message :contains('Congratulations!')").should(
.contains("Congratulations") "exist"
.should("exist"); );
cy.get("button") cy.get("button")
.contains("Submit Another Form") .contains("Submit Another Form")
.should("exist") .should("exist")
@ -288,8 +288,7 @@ export const genTest = (apiType, dbType) => {
// submit button & validate // submit button & validate
cy.get(".nc-form").find("button").contains("Submit").click(); cy.get(".nc-form").find("button").contains("Submit").click();
cy.get(".ant-alert-message") cy.get(".ant-alert-message :contains('Congratulations!')")
.contains("Congratulations")
.should("exist") .should("exist")
.then(() => { .then(() => {
// validate if form has appeared again // validate if form has appeared again

2
scripts/cypress/integration/common/5a_user_role.js

@ -152,7 +152,7 @@ export const genTest = (apiType, dbType) => {
cy.getSettled("button.ant-tabs-tab-remove") cy.getSettled("button.ant-tabs-tab-remove")
.should("be.visible") .should("be.visible")
.click(); .click();
cy.wait(500); cy.wait(2000);
} }
cy.saveLocalStorage(); cy.saveLocalStorage();

2
scripts/cypress/integration/common/9a_QuickTest.js

@ -115,7 +115,7 @@ export const genTest = (apiType, dbType, testMode) => {
cy.wait(2000); cy.wait(2000);
// close team & auth tab // close team & auth tab
cy.get("button.ant-tabs-tab-remove").should("exist").click(); cy.get("button.ant-tabs-tab-remove").should("exist").click();
cy.wait(1000); cy.wait(2000);
} else { } else {
cy.restoreLocalStorage(); cy.restoreLocalStorage();
} }

4
scripts/cypress/support/commands.js

@ -179,7 +179,7 @@ Cypress.Commands.add("openTableTab", (tn, rc) => {
// for some tables, linked records are not available immediately // for some tables, linked records are not available immediately
cy.wait(1000); cy.wait(1000);
cy.get(".xc-row-table.nc-grid").should("exist"); cy.get(".xc-row-table.nc-grid", { timeout: 30000 }).should("exist");
// wait for page rendering to complete // wait for page rendering to complete
if (rc != 0) { if (rc != 0) {
@ -199,7 +199,7 @@ Cypress.Commands.add("closeTableTab", (tn) => {
.click(); .click();
// subsequent tab open commands will fail if tab is not closed completely // subsequent tab open commands will fail if tab is not closed completely
cy.wait(100); cy.wait(2000)
}); });
Cypress.Commands.add("openOrCreateGqlProject", (_args) => { Cypress.Commands.add("openOrCreateGqlProject", (_args) => {

1
scripts/cypress/support/page_objects/navigation.js

@ -110,6 +110,7 @@ export class _projectsPage {
// close team & auth tab // close team & auth tab
cy.getSettled("button.ant-tabs-tab-remove").should("be.visible").click(); cy.getSettled("button.ant-tabs-tab-remove").should("be.visible").click();
cy.get("button.ant-tabs-tab-remove").should("not.exist"); cy.get("button.ant-tabs-tab-remove").should("not.exist");
cy.wait(2000);
} }
// Open existing project // Open existing project

Loading…
Cancel
Save