refactor(core): workbench (#7355)

Merge the right sidebar logic into the workbench. this can simplify our logic.

Previously we had 3 modules

* workbench
* right-sidebar (Control sidebar open&close)
* multi-tab-sidebar (Control tabs)

Now everything is managed in Workbench.

# Behavioral changes

The sidebar button is always visible and can be opened at any time.
If there is no content to display,  will be `No Selection`

![CleanShot 2024-06-28 at 14.00.41.png](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/g3jz87HxbjOJpXV3FPT7/d74b3a60-2299-452e-877e-188186fe5ee5.png)

Elements in the sidebar can now be defined as`unmountOnInactive=false`. Inactive sidebars are marked with `display: none` but not unmount, so the `ChatPanel` can always remain in the DOM and user input will be retained even if the sidebar is closed.
This commit is contained in:
EYHN
2024-07-12 04:11:05 +00:00
parent 5f16cb400d
commit 5dd7382693
55 changed files with 831 additions and 704 deletions
@@ -52,16 +52,18 @@ type PageMenuProps = {
rename?: () => void;
page: Doc;
isJournal?: boolean;
containerWidth: number;
};
// fixme: refactor this file
export const PageHeaderMenuButton = ({
rename,
page,
isJournal,
containerWidth,
}: PageMenuProps) => {
const pageId = page?.id;
const t = useI18n();
const { hideShare } = useDetailPageHeaderResponsive();
const { hideShare } = useDetailPageHeaderResponsive(containerWidth);
const confirmEnableCloud = useEnableCloud();
const workspace = useService(WorkspaceService).workspace;
@@ -1,5 +1,10 @@
import { RightSidebarService } from '@affine/core/modules/right-sidebar';
import { useLiveData, useService } from '@toeverything/infra';
import { WorkbenchService } from '@affine/core/modules/workbench';
import {
GlobalStateService,
LiveData,
useLiveData,
useService,
} from '@toeverything/infra';
import { useEffect, useState } from 'react';
import { ToolContainer } from '../../workspace';
@@ -11,18 +16,37 @@ import {
gradient,
} from './styles.css';
const RIGHT_SIDEBAR_AI_HAS_EVER_OPENED_KEY =
'app:settings:rightsidebar:ai:has-ever-opened';
export const AIIsland = () => {
// to make sure ai island is hidden first and animate in
const [hide, setHide] = useState(true);
const rightSidebar = useService(RightSidebarService).rightSidebar;
const activeTabName = useLiveData(rightSidebar.activeTabName$);
const rightSidebarOpen = useLiveData(rightSidebar.isOpen$);
const aiChatHasEverOpened = useLiveData(rightSidebar.aiChatHasEverOpened$);
const workbench = useService(WorkbenchService).workbench;
const activeView = useLiveData(workbench.activeView$);
const haveChatTab = useLiveData(
activeView.sidebarTabs$.map(tabs => tabs.some(t => t.id === 'chat'))
);
const activeTab = useLiveData(activeView.activeSidebarTab$);
const sidebarOpen = useLiveData(workbench.sidebarOpen$);
const globalState = useService(GlobalStateService).globalState;
const aiChatHasEverOpened = useLiveData(
LiveData.from(
globalState.watch<boolean>(RIGHT_SIDEBAR_AI_HAS_EVER_OPENED_KEY),
false
)
);
useEffect(() => {
setHide(rightSidebarOpen && activeTabName === 'chat');
}, [activeTabName, rightSidebarOpen]);
if (sidebarOpen && activeTab?.id === 'chat') {
globalState.set(RIGHT_SIDEBAR_AI_HAS_EVER_OPENED_KEY, true);
}
}, [activeTab, globalState, sidebarOpen]);
useEffect(() => {
setHide((sidebarOpen && activeTab?.id === 'chat') || !haveChatTab);
}, [activeTab, haveChatTab, sidebarOpen]);
return (
<ToolContainer>
@@ -43,8 +67,8 @@ export const AIIsland = () => {
data-testid="ai-island"
onClick={() => {
if (hide) return;
rightSidebar.open();
rightSidebar.setActiveTabName('chat');
workbench.openSidebar();
activeView.activeSidebarTab('chat');
}}
>
<AIIcon />