feat(electron): multi tabs support (#7440)

use https://www.electronjs.org/docs/latest/api/web-contents-view to serve different tab views
added tabs view manager in electron to handle multi-view actions and events.

fix AF-1111
fix AF-999
fix PD-1459
fix AF-964
PD-1458
This commit is contained in:
pengx17
2024-07-29 11:05:22 +00:00
parent 622715d2f3
commit 1efc1d0f5b
88 changed files with 3160 additions and 945 deletions

View File

@@ -0,0 +1,56 @@
import 'setimmediate';
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import { ThemeProvider } from '@affine/component/theme-provider';
import { appConfigProxy } from '@affine/core/hooks/use-app-config-storage';
import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header';
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
import { performanceLogger } from '@affine/core/shared';
import {
configureGlobalStorageModule,
Framework,
FrameworkRoot,
} from '@toeverything/infra';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { ShellRoot } from './shell';
const framework = new Framework();
configureGlobalStorageModule(framework);
configureElectronStateStorageImpls(framework);
configureAppTabsHeaderModule(framework);
const frameworkProvider = framework.provider();
const logger = performanceLogger.namespace('shell');
function main() {
appConfigProxy
.getSync()
.catch(() => console.error('failed to load app config'));
}
function mountApp() {
const root = document.getElementById('app');
if (!root) {
throw new Error('Root element not found');
}
logger.info('render app');
createRoot(root).render(
<StrictMode>
<FrameworkRoot framework={frameworkProvider}>
<ThemeProvider>
<ShellRoot />
</ThemeProvider>
</FrameworkRoot>
</StrictMode>
);
}
try {
main();
mountApp();
} catch (err) {
console.error('Failed to bootstrap app', err);
}

View File

@@ -0,0 +1,22 @@
import { cssVar } from '@toeverything/theme';
import { globalStyle, style } from '@vanilla-extract/css';
export const root = style({
width: '100vw',
height: '100vh',
opacity: 1,
transition: 'opacity 0.1s',
background: cssVar('backgroundPrimaryColor'),
selectors: {
'&[data-active="false"]': {
opacity: 0,
},
'&[data-translucent="true"]': {
background: 'transparent',
},
},
});
globalStyle(`${root}[data-active="false"] *`, {
['WebkitAppRegion' as string]: 'no-drag !important',
});

View File

@@ -0,0 +1,86 @@
import { useAppSettingHelper } from '@affine/core/hooks/affine/use-app-setting-helper';
import { AppTabsHeader } from '@affine/core/modules/app-tabs-header';
import { apis, events } from '@affine/electron-api';
import { useEffect, useState } from 'react';
import * as styles from './shell.css';
const useIsShellActive = () => {
const [active, setActive] = useState(true);
useEffect(() => {
const unsub = events?.ui.onTabShellViewActiveChange(active => {
setActive(active);
});
return () => {
unsub?.();
};
});
return active;
};
const useTabsBoundingRect = () => {
const [rect, setRect] = useState<{
x: number;
y: number;
width: number;
height: number;
}>({
x: environment.isDesktop && environment.isMacOs ? 80 : 0,
y: 0,
width: window.innerWidth,
height: 52,
});
useEffect(() => {
let unsub: (() => void) | undefined;
apis?.ui
.getTabsBoundingRect()
.then(rect => {
if (rect) {
setRect(rect);
}
unsub = events?.ui.onTabsBoundingRectChanged(rect => {
if (rect) {
setRect(rect);
}
});
})
.catch(err => {
console.error(err);
});
return () => {
unsub?.();
};
}, []);
return rect;
};
export function ShellRoot() {
const active = useIsShellActive();
const { appSettings } = useAppSettingHelper();
const rect = useTabsBoundingRect();
const translucent =
environment.isDesktop &&
environment.isMacOs &&
appSettings.enableBlurBackground;
return (
<div
className={styles.root}
data-translucent={translucent}
data-active={active}
>
<AppTabsHeader
style={{
position: 'fixed',
top: rect.y,
left: rect.x,
width: rect.width,
height: rect.height,
}}
/>
</div>
);
}