diff --git a/packages/nc-gui/assets/nc-icons/project.svg b/packages/nc-gui/assets/nc-icons/project.svg new file mode 100644 index 0000000000..842336ef6e --- /dev/null +++ b/packages/nc-gui/assets/nc-icons/project.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue b/packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue index 4ff81a7336..8a878878b5 100644 --- a/packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue +++ b/packages/nc-gui/components/dashboard/Sidebar/UserInfo.vue @@ -26,7 +26,8 @@ const logout = async () => { try { await signOut(false) - await clearWorkspaces() + // No need as all stores are cleared on signout + // await clearWorkspaces() await navigateTo('/signin') } catch (e) { diff --git a/packages/nc-gui/components/dashboard/TreeView/ViewsList.vue b/packages/nc-gui/components/dashboard/TreeView/ViewsList.vue index 222a0e6c99..c5133a76a7 100644 --- a/packages/nc-gui/components/dashboard/TreeView/ViewsList.vue +++ b/packages/nc-gui/components/dashboard/TreeView/ViewsList.vue @@ -45,7 +45,7 @@ const isDefaultBase = computed(() => { return _isDefaultBase(base) }) -const { viewsByTable, activeView } = storeToRefs(useViewsStore()) +const { viewsByTable, activeView, recentViews } = storeToRefs(useViewsStore()) const { navigateToTable } = useTablesStore() @@ -57,7 +57,7 @@ const { refreshCommandPalette } = useCommandPalette() const { addUndo, defineModelScope } = useUndoRedo() -const { navigateToView, loadViews } = useViewsStore() +const { navigateToView, loadViews, removeFromRecentViews } = useViewsStore() /** Selected view(s) for menu */ const selected = ref([]) @@ -244,6 +244,13 @@ async function onRename(view: ViewType, originalTitle?: string, undo = false) { scope: defineModelScope({ view: activeView.value }), }) } + // update view name in recent views + recentViews.value = recentViews.value.map((rv) => { + if (rv.viewId === view.id && rv.tableID === view.fk_model_id) { + rv.viewName = view.title + } + return rv + }) // View renamed successfully // message.success(t('msg.success.viewRenamed')) @@ -265,6 +272,7 @@ function openDeleteDialog(view: ViewType) { emits('deleted') + removeFromRecentViews({ viewId: view.id, tableId: view.fk_model_id, projectId: project.value.id }) refreshCommandPalette() if (activeView.value?.id === view.id) { navigateToTable({ diff --git a/packages/nc-gui/components/dlg/ProjectDelete.vue b/packages/nc-gui/components/dlg/ProjectDelete.vue index fc65218da4..57165d9843 100644 --- a/packages/nc-gui/components/dlg/ProjectDelete.vue +++ b/packages/nc-gui/components/dlg/ProjectDelete.vue @@ -14,6 +14,8 @@ const projectsStore = useProjects() const { deleteProject, navigateToFirstProjectOrHome } = projectsStore const { projects } = storeToRefs(projectsStore) +const { removeFromRecentViews } = useViewsStore() + const { refreshCommandPalette } = useCommandPalette() const project = computed(() => projects.value.get(props.projectId)) @@ -41,6 +43,7 @@ const onDelete = async () => { message.error(await extractSdkResponseErrorMsg(e)) } finally { isLoading.value = false + removeFromRecentViews({ projectId: toBeDeletedProject.id! }) } } diff --git a/packages/nc-gui/components/dlg/TableDelete.vue b/packages/nc-gui/components/dlg/TableDelete.vue index 401aa85387..d20824f7ae 100644 --- a/packages/nc-gui/components/dlg/TableDelete.vue +++ b/packages/nc-gui/components/dlg/TableDelete.vue @@ -20,6 +20,7 @@ const { getMeta, removeMeta } = useMetas() const { loadTables, projectUrl, isXcdbBase } = useProject() const { refreshCommandPalette } = useCommandPalette() +const { removeFromRecentViews } = useViewsStore() const { projectTables, activeTable } = storeToRefs(useTablesStore()) const { openTable } = useTablesStore() @@ -69,6 +70,9 @@ const onDelete = async () => { await loadTables() + // Remove from recent views + removeFromRecentViews({ projectId: props.projectId, tableId: toBeDeletedTable.id as string }) + removeMeta(toBeDeletedTable.id as string) refreshCommandPalette() // Deleted table successfully diff --git a/packages/nc-gui/store/views.ts b/packages/nc-gui/store/views.ts index 320e595208..bad2368e7c 100644 --- a/packages/nc-gui/store/views.ts +++ b/packages/nc-gui/store/views.ts @@ -1,4 +1,4 @@ -import { type ViewType } from 'nocodb-sdk' +import type { ViewType } from 'nocodb-sdk' import { acceptHMRUpdate, defineStore } from 'pinia' import type { ViewPageType } from '~/lib' @@ -6,6 +6,7 @@ export const useViewsStore = defineStore('viewsStore', () => { const { $api } = useNuxtApp() const router = useRouter() + const recentViews = ref([]) const route = router.currentRoute const tablesStore = useTablesStore() @@ -20,6 +21,7 @@ export const useViewsStore = defineStore('viewsStore', () => { viewsByTable.value.set(tablesStore.activeTableId, value) }, }) + const isViewsLoading = ref(true) const isViewDataLoading = ref(true) const isPublic = computed(() => route.value.meta?.public) @@ -118,6 +120,10 @@ export const useViewsStore = defineStore('viewsStore', () => { }) } + const changeView = async (..._args: any) => {} + + const removeFromRecentViews = (..._args: any) => {} + watch( () => tablesStore.activeTableId, async (newId, oldId) => { @@ -203,6 +209,7 @@ export const useViewsStore = defineStore('viewsStore', () => { isViewDataLoading, isPaginationLoading, loadViews, + recentViews, views, activeView, openedViewsTab, @@ -211,6 +218,8 @@ export const useViewsStore = defineStore('viewsStore', () => { viewsByTable, activeViewTitleOrId, navigateToView, + changeView, + removeFromRecentViews, } }) diff --git a/tests/playwright/pages/Dashboard/Command/CmdJPage.ts b/tests/playwright/pages/Dashboard/Command/CmdJPage.ts new file mode 100644 index 0000000000..e7a4fcb15e --- /dev/null +++ b/tests/playwright/pages/Dashboard/Command/CmdJPage.ts @@ -0,0 +1,39 @@ +import BasePage from '../../Base'; +import { DashboardPage } from '..'; + +export class CmdJ extends BasePage { + readonly dashboardPage: DashboardPage; + + constructor(dashboard: DashboardPage) { + super(dashboard.rootPage); + this.dashboardPage = dashboard; + } + + get() { + return this.dashboardPage.get().locator('.DocSearch'); + } + + async openCmdJ() { + await this.dashboardPage.rootPage.keyboard.press(this.isMacOs() ? 'Meta+J' : 'Control+J'); + // await this.dashboardPage.rootPage.waitForSelector('.DocSearch-Input'); + } + + async searchText(text: string) { + await this.dashboardPage.rootPage.fill('.DocSearch-Input', text); + } + + async isCmdJVisible() { + const isVisible = this.get(); + return await isVisible.count(); + } + + async isCmdJNotVisible() { + const isNotVisible = this.get(); + return await isNotVisible.count(); + } + + async getPlaceholderText() { + const placeholderText = this.get().locator('.DocSearch-Input'); + return await placeholderText.innerText(); + } +} diff --git a/tests/playwright/pages/Dashboard/Command/CmdKPage.ts b/tests/playwright/pages/Dashboard/Command/CmdKPage.ts new file mode 100644 index 0000000000..5ac62b25cd --- /dev/null +++ b/tests/playwright/pages/Dashboard/Command/CmdKPage.ts @@ -0,0 +1,35 @@ +import BasePage from '../../Base'; +import { DashboardPage } from '..'; + +export class CmdK extends BasePage { + readonly dashboardPage: DashboardPage; + + constructor(dashboard: DashboardPage) { + super(dashboard.rootPage); + this.dashboardPage = dashboard; + } + + get() { + return this.dashboardPage.get().locator('.cmdk-modal.cmdk-modal-active'); + } + + async openCmdK() { + await this.dashboardPage.rootPage.keyboard.press(this.isMacOs() ? 'Meta+K' : 'Control+K'); + // await this.dashboardPage.rootPage.waitForSelector('.DocSearch-Input'); + } + + async searchText(text: string) { + await this.dashboardPage.rootPage.fill('.cmdk-input', text); + await this.rootPage.keyboard.press('Enter'); + } + + async isCmdKVisible() { + const isVisible = this.get(); + return await isVisible.count(); + } + + async isCmdKNotVisible() { + const isNotVisible = this.get(); + return await isNotVisible.count(); + } +} diff --git a/tests/playwright/pages/Dashboard/Command/CmdLPage.ts b/tests/playwright/pages/Dashboard/Command/CmdLPage.ts new file mode 100644 index 0000000000..3c9768323c --- /dev/null +++ b/tests/playwright/pages/Dashboard/Command/CmdLPage.ts @@ -0,0 +1,49 @@ +import BasePage from '../../Base'; +import { DashboardPage } from '..'; + +export class CmdL extends BasePage { + readonly dashboardPage: DashboardPage; + + constructor(dashboard: DashboardPage) { + super(dashboard.rootPage); + this.dashboardPage = dashboard; + } + + get() { + return this.dashboardPage.get().locator('.cmdl-modal.cmdl-modal-active'); + } + + async openCmdL() { + await this.dashboardPage.rootPage.keyboard.press(this.isMacOs() ? 'Meta+L' : 'Control+L'); + } + + async isCmdLVisible() { + const isVisible = this.get(); + return await isVisible.count(); + } + + async isCmdLNotVisible() { + const isNotVisible = this.get(); + return await isNotVisible.count(); + } + + async moveDown() { + await this.dashboardPage.rootPage.keyboard.press('ArrowDown'); + } + + async moveUp() { + await this.dashboardPage.rootPage.keyboard.press('ArrowUp'); + } + + async openRecent() { + await this.dashboardPage.rootPage.keyboard.press('Enter'); + } + + async getActiveViewTitle() { + return await this.dashboardPage.get().locator('.nc-active-view-title').innerText(); + } + + async getActiveTableTitle() { + return await this.dashboardPage.get().locator('.nc-active-table-title').innerText(); + } +} diff --git a/tests/playwright/pages/Dashboard/index.ts b/tests/playwright/pages/Dashboard/index.ts index 86675ea008..e89f1006ea 100644 --- a/tests/playwright/pages/Dashboard/index.ts +++ b/tests/playwright/pages/Dashboard/index.ts @@ -25,6 +25,9 @@ import { ProjectTypes } from 'nocodb-sdk'; import { WorkspacePage } from '../WorkspacePage'; import { DetailsPage } from './Details'; import { WorkspaceSettingsObject } from './WorkspaceSettings'; +import { CmdJ } from './Command/CmdJPage'; +import { CmdK } from './Command/CmdKPage'; +import { CmdL } from './Command/CmdLPage'; export class DashboardPage extends BasePage { readonly project: any; @@ -55,6 +58,9 @@ export class DashboardPage extends BasePage { readonly shareProjectButton: ShareProjectButtonPage; readonly details: DetailsPage; readonly workspaceSettings: WorkspaceSettingsObject; + readonly cmdJ: CmdJ; + readonly cmdK: CmdK; + readonly cmdL: CmdL; constructor(rootPage: Page, project: any) { super(rootPage); @@ -88,6 +94,9 @@ export class DashboardPage extends BasePage { this.shareProjectButton = new ShareProjectButtonPage(this); this.details = new DetailsPage(this); this.workspaceSettings = new WorkspaceSettingsObject(this); + this.cmdJ = new CmdJ(this); + this.cmdK = new CmdK(this); + this.cmdL = new CmdL(this); } get() {