From 07409b8a916804edc20d69ad3a9b808cacd445e6 Mon Sep 17 00:00:00 2001 From: pengx17 Date: Thu, 1 Aug 2024 16:43:18 +0000 Subject: [PATCH] refactor(electron): tab title/icon update logic (#7675) fix AF-1122 fix AF-1136 --- .../app-tabs-header/views/app-tabs-header.tsx | 35 +--- .../app-tabs-header/views/styles.css.ts | 2 +- .../core/src/modules/workbench/constants.tsx | 23 +++ .../src/modules/workbench/entities/view.ts | 15 ++ .../core/src/modules/workbench/index.ts | 9 +- .../services/desktop-state-synchronizer.ts | 170 +++--------------- .../modules/workbench/view/desktop-adapter.ts | 2 + .../src/modules/workbench/view/view-meta.tsx | 29 +++ .../pages/workspace/all-collection/index.tsx | 6 + .../src/pages/workspace/all-page/all-page.tsx | 7 + .../src/pages/workspace/all-tag/index.tsx | 12 +- .../src/pages/workspace/collection/index.tsx | 4 + .../detail-page/detail-page-header.tsx | 19 +- .../core/src/pages/workspace/tag/index.tsx | 5 + .../core/src/pages/workspace/trash-page.tsx | 11 +- packages/frontend/electron/renderer/app.tsx | 13 -- .../src/main/application-menu/create.ts | 10 +- .../frontend/electron/src/main/ui/handlers.ts | 7 + .../windows-manager/tab-views-meta-schema.ts | 8 +- .../src/main/windows-manager/tab-views.ts | 91 ++++++++-- 20 files changed, 261 insertions(+), 217 deletions(-) create mode 100644 packages/frontend/core/src/modules/workbench/constants.tsx create mode 100644 packages/frontend/core/src/modules/workbench/view/view-meta.tsx diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx index f4a27e9913..2fe119a52d 100644 --- a/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx +++ b/packages/frontend/core/src/modules/app-tabs-header/views/app-tabs-header.tsx @@ -7,21 +7,8 @@ import { import { appSidebarWidthAtom } from '@affine/core/components/app-sidebar/index.jotai'; import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls'; import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks'; -import { DesktopStateSynchronizer } from '@affine/core/modules/workbench/services/desktop-state-synchronizer'; -import type { WorkbenchMeta } from '@affine/electron-api'; import { apis, events } from '@affine/electron-api'; -import { - AllDocsIcon, - CloseIcon, - DeleteIcon, - EdgelessIcon, - PageIcon, - PlusIcon, - RightSidebarIcon, - TagIcon, - TodayIcon, - ViewLayersIcon, -} from '@blocksuite/icons/rc'; +import { CloseIcon, PlusIcon, RightSidebarIcon } from '@blocksuite/icons/rc'; import { useLiveData, useService, @@ -38,25 +25,14 @@ import { useState, } from 'react'; +import { iconNameToIcon } from '../../workbench/constants'; +import { DesktopStateSynchronizer } from '../../workbench/services/desktop-state-synchronizer'; import { AppTabsHeaderService, type TabStatus, } from '../services/app-tabs-header-service'; import * as styles from './styles.css'; -type ModuleName = NonNullable; - -const moduleNameToIcon = { - all: , - collection: , - doc: , - page: , - edgeless: , - journal: , - tag: , - trash: , -} satisfies Record; - const WorkbenchTab = ({ workbench, active: tabActive, @@ -66,6 +42,7 @@ const WorkbenchTab = ({ active: boolean; tabsLength: number; }) => { + useServiceOptional(DesktopStateSynchronizer); const tabsHeaderService = useService(AppTabsHeaderService); const activeViewIndex = workbench.activeViewIndex ?? 0; const onContextMenu = useAsyncCallback( @@ -115,7 +92,7 @@ const WorkbenchTab = ({ >
{workbench.ready || !workbench.loaded ? ( - moduleNameToIcon[view.moduleName ?? 'all'] + iconNameToIcon[view.iconName ?? 'allDocs'] ) : ( )} @@ -194,8 +171,6 @@ export const AppTabsHeader = ({ await tabsHeaderService.onToggleRightSidebar(); }, [tabsHeaderService]); - useServiceOptional(DesktopStateSynchronizer); - useEffect(() => { if (mode === 'app') { apis?.ui.pingAppLayoutReady().catch(console.error); diff --git a/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts b/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts index eb9f0a1bed..aed2ee786a 100644 --- a/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts +++ b/packages/frontend/core/src/modules/app-tabs-header/views/styles.css.ts @@ -80,7 +80,7 @@ export const tab = style({ boxShadow: cssVar('shadow1'), }, '&[data-pinned="false"]': { - paddingRight: 8, + paddingRight: 20, }, '&[data-pinned="true"]': { flexShrink: 0, diff --git a/packages/frontend/core/src/modules/workbench/constants.tsx b/packages/frontend/core/src/modules/workbench/constants.tsx new file mode 100644 index 0000000000..719d816fd4 --- /dev/null +++ b/packages/frontend/core/src/modules/workbench/constants.tsx @@ -0,0 +1,23 @@ +import { + AllDocsIcon, + DeleteIcon, + EdgelessIcon, + PageIcon, + TagIcon, + TodayIcon, + ViewLayersIcon, +} from '@blocksuite/icons/rc'; +import type { ReactNode } from 'react'; + +export const iconNameToIcon = { + allDocs: , + collection: , + doc: , + page: , + edgeless: , + journal: , + tag: , + trash: , +} satisfies Record; + +export type ViewIconName = keyof typeof iconNameToIcon; diff --git a/packages/frontend/core/src/modules/workbench/entities/view.ts b/packages/frontend/core/src/modules/workbench/entities/view.ts index c91fbf18f3..f037f67693 100644 --- a/packages/frontend/core/src/modules/workbench/entities/view.ts +++ b/packages/frontend/core/src/modules/workbench/entities/view.ts @@ -3,12 +3,15 @@ import type { Location, To } from 'history'; import { Observable } from 'rxjs'; import { createNavigableHistory } from '../../../utils/navigable-history'; +import type { ViewIconName } from '../constants'; import { ViewScope } from '../scopes/view'; import { SidebarTab } from './sidebar-tab'; export class View extends Entity<{ id: string; defaultLocation?: To | undefined; + title?: string; + icon?: ViewIconName; }> { scope = this.framework.createScope(ViewScope, { view: this as View, @@ -70,6 +73,10 @@ export class View extends Entity<{ size$ = new LiveData(100); + title$ = new LiveData(this.props.title ?? ''); + + icon$ = new LiveData(this.props.icon ?? 'allDocs'); + push(path: To) { this.history.push(path); } @@ -105,4 +112,12 @@ export class View extends Entity<{ activeSidebarTab(id: string | null) { this._activeSidebarTabId$.next(id); } + + setTitle(title: string) { + this.title$.next(title); + } + + setIcon(icon: ViewIconName) { + this.icon$.next(icon); + } } diff --git a/packages/frontend/core/src/modules/workbench/index.ts b/packages/frontend/core/src/modules/workbench/index.ts index 5d404cf94d..5b13b0d997 100644 --- a/packages/frontend/core/src/modules/workbench/index.ts +++ b/packages/frontend/core/src/modules/workbench/index.ts @@ -3,17 +3,16 @@ export { ViewScope } from './scopes/view'; export { WorkbenchService } from './services/workbench'; export { useIsActiveView } from './view/use-is-active-view'; export { ViewBody, ViewHeader, ViewSidebarTab } from './view/view-islands'; +export { ViewIcon, ViewTitle } from './view/view-meta'; export { WorkbenchLink } from './view/workbench-link'; export { WorkbenchRoot } from './view/workbench-root'; import { - DocsService, type Framework, GlobalStateService, WorkspaceScope, } from '@toeverything/infra'; -import { WorkspacePropertiesAdapter } from '../properties'; import { SidebarTab } from './entities/sidebar-tab'; import { View } from './entities/view'; import { Workbench } from './entities/workbench'; @@ -52,9 +51,5 @@ export function configureDesktopWorkbenchModule(services: Framework) { .impl(WorkbenchDefaultState, DesktopWorkbenchDefaultState, [ GlobalStateService, ]) - .service(DesktopStateSynchronizer, [ - WorkbenchService, - WorkspacePropertiesAdapter, - DocsService, - ]); + .service(DesktopStateSynchronizer, [WorkbenchService]); } diff --git a/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts b/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts index 5dd3e8d869..032dbb0c69 100644 --- a/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts +++ b/packages/frontend/core/src/modules/workbench/services/desktop-state-synchronizer.ts @@ -1,35 +1,13 @@ -import { - apis, - appInfo, - events, - type WorkbenchViewMeta, -} from '@affine/electron-api'; -import { I18n, type I18nKeys, i18nTime } from '@affine/i18n'; -import type { DocsService } from '@toeverything/infra'; -import { Service } from '@toeverything/infra'; -import { combineLatest, filter, map, of, switchMap } from 'rxjs'; +import { apis, appInfo, events } from '@affine/electron-api'; +import { LiveData, Service } from '@toeverything/infra'; -import { resolveRouteLinkMeta } from '../../navigation'; -import type { RouteModulePath } from '../../navigation/utils'; -import type { WorkspacePropertiesAdapter } from '../../properties'; import type { WorkbenchService } from '../../workbench'; -const routeModuleToI18n = { - all: 'All pages', - collection: 'Collections', - tag: 'Tags', - trash: 'Trash', -} satisfies Record; - /** * Synchronize workbench state with state stored in main process */ export class DesktopStateSynchronizer extends Service { - constructor( - private readonly workbenchService: WorkbenchService, - private readonly workspaceProperties: WorkspacePropertiesAdapter, - private readonly docsService: DocsService - ) { + constructor(private readonly workbenchService: WorkbenchService) { super(); this.startSync(); } @@ -80,70 +58,31 @@ export class DesktopStateSynchronizer extends Service { // sync workbench state with main process // also fill tab view meta with title & moduleName - this.workspaceProperties.workspace.engine.rootDocState$ - .pipe( - filter(v => v.ready), - switchMap(() => workbench.views$), - switchMap(views => { - return combineLatest( - views.map(view => - view.location$.map(location => { - return { - view, - location, - }; - }) - ) - ); - }), - map(viewLocations => { - if (!apis || !appInfo?.viewId) { - return; - } - - const viewMetas = viewLocations.map(({ view, location }) => { - return { - id: view.id, - path: location, - }; - }); - - return viewMetas.map(viewMeta => this.fillTabViewMeta(viewMeta)); - }), - filter(v => !!v), - switchMap(viewMetas => { - return this.docsService.list.docs$.pipe( - switchMap(docs => { - return combineLatest( - viewMetas.map(vm => { - return ( - docs - .find(doc => doc.id === vm.docId) - ?.mode$.asObservable() ?? of('page') - ).pipe( - map(mode => ({ - ...vm, - moduleName: - vm.moduleName === 'page' ? mode : vm.moduleName, - })) - ); - }) - ); - }) - ); - }) - ) - .subscribe(viewMetas => { - if (!apis || !appInfo?.viewId) { - return; - } - - apis.ui - .updateWorkbenchMeta(appInfo.viewId, { - views: viewMetas, - }) - .catch(console.error); + LiveData.computed(get => { + return get(workbench.views$).map(view => { + const location = get(view.location$); + return { + id: view.id, + title: get(view.title$), + iconName: get(view.icon$), + path: { + pathname: location.pathname, + search: location.search, + hash: location.hash, + }, + }; }); + }).subscribe(views => { + if (!apis || !appInfo?.viewId) { + return; + } + + apis.ui + .updateWorkbenchMeta(appInfo.viewId, { + views, + }) + .catch(console.error); + }); workbench.activeViewIndex$.subscribe(activeViewIndex => { if (!apis || !appInfo?.viewId) { @@ -169,59 +108,4 @@ export class DesktopStateSynchronizer extends Service { .catch(console.error); }); }; - - private toFullUrl( - basename: string, - location: { hash?: string; pathname?: string; search?: string } - ) { - return basename + location.pathname + location.search + location.hash; - } - - // fill tab view meta with title & moduleName - private fillTabViewMeta( - view: WorkbenchViewMeta - ): WorkbenchViewMeta & { docId?: string } { - if (!view.path) { - return view; - } - - const url = this.toFullUrl( - this.workbenchService.workbench.basename$.value, - view.path - ); - const linkMeta = resolveRouteLinkMeta(url); - - if (!linkMeta) { - return view; - } - - const journalString = - linkMeta.moduleName === 'doc' - ? this.workspaceProperties.getJournalPageDateString(linkMeta.docId) - : undefined; - const isJournal = !!journalString; - - const title = (() => { - // todo: resolve more module types like collections? - if (linkMeta?.moduleName === 'doc') { - if (journalString) { - return i18nTime(journalString, { absolute: { accuracy: 'day' } }); - } - return ( - this.workspaceProperties.workspace.docCollection.meta.getDocMeta( - linkMeta.docId - )?.title || I18n['Untitled']() - ); - } else { - return I18n[routeModuleToI18n[linkMeta.moduleName]](); - } - })(); - - return { - ...view, - title: title, - docId: linkMeta.docId, - moduleName: isJournal ? 'journal' : linkMeta.moduleName, - }; - } } diff --git a/packages/frontend/core/src/modules/workbench/view/desktop-adapter.ts b/packages/frontend/core/src/modules/workbench/view/desktop-adapter.ts index 702176ff13..110334712a 100644 --- a/packages/frontend/core/src/modules/workbench/view/desktop-adapter.ts +++ b/packages/frontend/core/src/modules/workbench/view/desktop-adapter.ts @@ -21,6 +21,7 @@ export function useBindWorkbenchToDesktopRouter( basename: string ) { const browserLocation = useLocation(); + useEffect(() => { const newLocation = browserLocationToViewLocation( browserLocation, @@ -36,6 +37,7 @@ export function useBindWorkbenchToDesktopRouter( ) { return; } + workbench.open(newLocation); }, [basename, browserLocation, workbench]); } diff --git a/packages/frontend/core/src/modules/workbench/view/view-meta.tsx b/packages/frontend/core/src/modules/workbench/view/view-meta.tsx new file mode 100644 index 0000000000..80762c3483 --- /dev/null +++ b/packages/frontend/core/src/modules/workbench/view/view-meta.tsx @@ -0,0 +1,29 @@ +import { useServiceOptional } from '@toeverything/infra'; +import { useEffect } from 'react'; + +import type { ViewIconName } from '../constants'; +import { ViewService } from '../services/view'; + +export const ViewTitle = ({ title }: { title: string }) => { + const view = useServiceOptional(ViewService)?.view; + + useEffect(() => { + if (view) { + view.setTitle(title); + } + }, [title, view]); + + return null; +}; + +export const ViewIcon = ({ icon }: { icon: ViewIconName }) => { + const view = useServiceOptional(ViewService)?.view; + + useEffect(() => { + if (view) { + view.setIcon(icon); + } + }, [icon, view]); + + return null; +}; diff --git a/packages/frontend/core/src/pages/workspace/all-collection/index.tsx b/packages/frontend/core/src/pages/workspace/all-collection/index.tsx index c7ee17fedf..0d55a794c1 100644 --- a/packages/frontend/core/src/pages/workspace/all-collection/index.tsx +++ b/packages/frontend/core/src/pages/workspace/all-collection/index.tsx @@ -6,6 +6,10 @@ import { VirtualizedCollectionList, } from '@affine/core/components/page-list'; import { useNavigateHelper } from '@affine/core/hooks/use-navigate-helper'; +import { + ViewIcon, + ViewTitle, +} from '@affine/core/modules/workbench/view/view-meta'; import { useI18n } from '@affine/i18n'; import { useLiveData, useService, WorkspaceService } from '@toeverything/infra'; import { nanoid } from 'nanoid'; @@ -55,6 +59,8 @@ export const AllCollection = () => { return ( <> + + { return; }, [globalContext, isActiveView]); + const t = useI18n(); + return ( <> + + { [setOpen, setSelectedTagIds] ); + const t = useI18n(); + return ( <> + + diff --git a/packages/frontend/core/src/pages/workspace/collection/index.tsx b/packages/frontend/core/src/pages/workspace/collection/index.tsx index f4705f2ae1..822bc71f81 100644 --- a/packages/frontend/core/src/pages/workspace/collection/index.tsx +++ b/packages/frontend/core/src/pages/workspace/collection/index.tsx @@ -29,6 +29,8 @@ import { useIsActiveView, ViewBody, ViewHeader, + ViewIcon, + ViewTitle, } from '../../../modules/workbench'; import { WorkspaceSubPath } from '../../../shared'; import * as styles from './collection.css'; @@ -155,6 +157,8 @@ const Placeholder = ({ collection }: { collection: Collection }) => { return ( <> + +
+ + { setTimeout(() => titleInputHandleRef.current?.triggerEdit()); }, []); + + const title = useDocCollectionPageTitle(workspace.docCollection, page?.id); + const doc = useService(DocService).doc; + const currentMode = useLiveData(doc.mode$); + return (
+ + { }, [pageIds, pageMetas]); const isActiveView = useIsActiveView(); + const tagName = useLiveData(currentTag?.value$); useEffect(() => { if (isActiveView && currentTag) { @@ -59,6 +62,8 @@ export const TagDetail = ({ tagId }: { tagId?: string }) => { return ( <> + + diff --git a/packages/frontend/core/src/pages/workspace/trash-page.tsx b/packages/frontend/core/src/pages/workspace/trash-page.tsx index 1dd0a9a8da..4bb123b43a 100644 --- a/packages/frontend/core/src/pages/workspace/trash-page.tsx +++ b/packages/frontend/core/src/pages/workspace/trash-page.tsx @@ -14,7 +14,13 @@ import { } from '@toeverything/infra'; import { useEffect } from 'react'; -import { useIsActiveView, ViewBody, ViewHeader } from '../../modules/workbench'; +import { + useIsActiveView, + ViewBody, + ViewHeader, + ViewIcon, + ViewTitle, +} from '../../modules/workbench'; import { EmptyPageList } from './page-list-empty'; import * as styles from './trash-page.css'; @@ -56,8 +62,11 @@ export const TrashPage = () => { return; }, [globalContextService.globalContext.isTrash, isActiveView]); + const t = useI18n(); return ( <> + + diff --git a/packages/frontend/electron/renderer/app.tsx b/packages/frontend/electron/renderer/app.tsx index f4a419f01b..8168b2db93 100644 --- a/packages/frontend/electron/renderer/app.tsx +++ b/packages/frontend/electron/renderer/app.tsx @@ -4,7 +4,6 @@ import '@affine/component/theme/theme.css'; import { NotificationCenter } from '@affine/component'; import { AffineContext } from '@affine/component/context'; import { GlobalLoading } from '@affine/component/global-loading'; -import { registerAffineCommand } from '@affine/core/commands'; import { AppFallback } from '@affine/core/components/affine/app-container'; import { configureCommonModules } from '@affine/core/modules'; import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header'; @@ -22,7 +21,6 @@ import { import { Telemetry } from '@affine/core/telemetry'; import createEmotionCache from '@affine/core/utils/create-emotion-cache'; import { createI18n, setUpLanguage } from '@affine/i18n'; -import { SettingsIcon } from '@blocksuite/icons/rc'; import { CacheProvider } from '@emotion/react'; import { Framework, @@ -127,14 +125,3 @@ export function App() { ); } - -registerAffineCommand({ - id: 'affine:reload', - category: 'affine:general', - label: 'Reload current tab', - icon: , - keyBinding: '$mod+R', - run() { - location.reload(); - }, -}); diff --git a/packages/frontend/electron/src/main/application-menu/create.ts b/packages/frontend/electron/src/main/application-menu/create.ts index 08c9055cdf..ab78209160 100644 --- a/packages/frontend/electron/src/main/application-menu/create.ts +++ b/packages/frontend/electron/src/main/application-menu/create.ts @@ -7,6 +7,7 @@ import { addTab, closeTab, initAndShowMainWindow, + reloadView, showDevTools, showMainWindow, undoCloseTab, @@ -92,8 +93,13 @@ export function createApplicationMenu() { { label: 'View', submenu: [ - { role: 'reload' }, - { role: 'forceReload' }, + { + label: 'Reload', + accelerator: 'CommandOrControl+R', + click() { + reloadView().catch(console.error); + }, + }, { label: 'Open devtools', accelerator: isMac ? 'Cmd+Option+I' : 'Ctrl+Shift+I', diff --git a/packages/frontend/electron/src/main/ui/handlers.ts b/packages/frontend/electron/src/main/ui/handlers.ts index 996ce0939f..aa8ec78cd3 100644 --- a/packages/frontend/electron/src/main/ui/handlers.ts +++ b/packages/frontend/electron/src/main/ui/handlers.ts @@ -23,6 +23,7 @@ import { showTab, showTabContextMenu, updateWorkbenchMeta, + updateWorkbenchViewMeta, } from '../windows-manager'; import { getChallengeResponse } from './challenge'; import { uiSubjects } from './subject'; @@ -168,6 +169,12 @@ export const uiHandlers = { ) => { return updateWorkbenchMeta(...args); }, + updateWorkbenchViewMeta: async ( + _, + ...args: Parameters + ) => { + return updateWorkbenchViewMeta(...args); + }, getTabViewsMeta: async () => { return getTabViewsMeta(); }, diff --git a/packages/frontend/electron/src/main/windows-manager/tab-views-meta-schema.ts b/packages/frontend/electron/src/main/windows-manager/tab-views-meta-schema.ts index 0b0abbc0d2..18a77dba12 100644 --- a/packages/frontend/electron/src/main/windows-manager/tab-views-meta-schema.ts +++ b/packages/frontend/electron/src/main/windows-manager/tab-views-meta-schema.ts @@ -1,8 +1,8 @@ import { z } from 'zod'; -export const workbenchViewModuleSchema = z.enum([ +export const workbenchViewIconNameSchema = z.enum([ 'trash', - 'all', + 'allDocs', 'collection', 'tag', 'doc', // refers to a doc whose mode is not yet being resolved @@ -22,7 +22,7 @@ export const workbenchViewMetaSchema = z.object({ .optional(), // todo: move title/module to cached stated title: z.string().optional(), - moduleName: workbenchViewModuleSchema.optional(), + iconName: workbenchViewIconNameSchema.optional(), }); export const workbenchMetaSchema = z.object({ @@ -42,4 +42,4 @@ export const TabViewsMetaKey = 'tabViewsMetaSchema'; export type TabViewsMetaSchema = z.infer; export type WorkbenchMeta = z.infer; export type WorkbenchViewMeta = z.infer; -export type WorkbenchViewModule = z.infer; +export type WorkbenchViewModule = z.infer; diff --git a/packages/frontend/electron/src/main/windows-manager/tab-views.ts b/packages/frontend/electron/src/main/windows-manager/tab-views.ts index 01711e415b..72e379f402 100644 --- a/packages/frontend/electron/src/main/windows-manager/tab-views.ts +++ b/packages/frontend/electron/src/main/windows-manager/tab-views.ts @@ -258,15 +258,50 @@ export class WebContentViewsManager { if (index === -1) { return; } + const workbench = workbenches[index]; const newWorkbenches = workbenches.toSpliced(index, 1, { - ...workbenches[index], + ...workbench, ...patch, + views: patch.views + ? patch.views.map(v => { + const existing = workbench.views.find(e => e.id === v.id); + return { + ...existing, + ...v, + }; + }) + : workbench.views, }); this.patchTabViewsMeta({ workbenches: newWorkbenches, }); }; + updateWorkbenchViewMeta = ( + workbenchId: string, + viewId: string, + patch: Partial + ) => { + const workbench = this.tabViewsMeta.workbenches.find( + w => w.id === workbenchId + ); + if (!workbench) { + return; + } + const views = workbench.views; + const viewIndex = views.findIndex(v => v.id === viewId); + if (viewIndex === -1) { + return; + } + const newViews = views.toSpliced(viewIndex, 1, { + ...views[viewIndex], + ...patch, + }); + this.updateWorkbenchMeta(workbenchId, { + views: newViews, + }); + }; + isActiveTab = (id: string) => { return this.activeWorkbenchId === id; }; @@ -345,10 +380,18 @@ export class WebContentViewsManager { addTab = async (option?: AddTabOption) => { if (!option) { + const activeWorkbench = this.activeWorkbenchMeta; + const basename = (activeWorkbench?.basename ?? '') + '/'; + option = { - basename: '/', + basename, view: { title: 'New Tab', + path: basename.startsWith('/workspace') + ? { + pathname: 'all', + } + : undefined, }, }; } @@ -393,18 +436,18 @@ export class WebContentViewsManager { let view = this.tabViewsMap.get(id); if (!view) { view = await this.createAndAddView('app', id); - const workbench = this.tabViewsMeta.workbenches.find(w => w.id === id); - const viewMeta = workbench?.views[workbench.activeViewIndex]; - if (workbench && viewMeta) { - const url = new URL( - workbench.basename + (viewMeta.path?.pathname ?? ''), - mainWindowOrigin - ); - url.hash = viewMeta.path?.hash ?? ''; - url.search = viewMeta.path?.search ?? ''; - logger.info(`loading tab ${id} at ${url.href}`); - view.webContents.loadURL(url.href).catch(logger.error); - } + } + const workbench = this.tabViewsMeta.workbenches.find(w => w.id === id); + const viewMeta = workbench?.views[workbench.activeViewIndex]; + if (workbench && viewMeta) { + const url = new URL( + workbench.basename + (viewMeta.path?.pathname ?? ''), + mainWindowOrigin + ); + url.hash = viewMeta.path?.hash ?? ''; + url.search = viewMeta.path?.search ?? ''; + logger.info(`loading tab ${id} at ${url.href}`); + view.webContents.loadURL(url.href).catch(logger.error); } return view; }; @@ -835,6 +878,19 @@ export const updateWorkbenchMeta = ( ) => { WebContentViewsManager.instance.updateWorkbenchMeta(id, meta); }; + +export const updateWorkbenchViewMeta = ( + workbenchId: string, + viewId: string, + meta: Partial +) => { + WebContentViewsManager.instance.updateWorkbenchViewMeta( + workbenchId, + viewId, + meta + ); +}; + export const getWorkbenchMeta = (id: string) => { return TabViewsMetaState.value.workbenches.find(w => w.id === id); }; @@ -851,6 +907,13 @@ export const closeTab = WebContentViewsManager.instance.closeTab; export const undoCloseTab = WebContentViewsManager.instance.undoCloseTab; export const activateView = WebContentViewsManager.instance.activateView; +export const reloadView = async () => { + const id = WebContentViewsManager.instance.activeWorkbenchId; + if (id) { + await WebContentViewsManager.instance.loadTab(id); + } +}; + export const onTabAction = (fn: (event: TabAction) => void) => { const { unsubscribe } = WebContentViewsManager.instance.tabAction$.subscribe(fn);