mirror of
https://github.com/lobehub/lobehub.git
synced 2026-03-27 13:29:15 +07:00
✨ feat: improve desktop onboarding window management and footer actions (#11619)
* ✨ feat: improve desktop onboarding window management and footer actions - Add APP_WINDOW_MIN_SIZE constant for consistent window constraints - Extract reusable OnboardingFooterActions component for step navigation - Implement setWindowMinimumSize API in electron system service - Apply dedicated minimum size (1200x900) during onboarding flow - Restore app-level defaults (860x500) when onboarding completes - Add windowMinimumSize parameter support in BrowserManager Resolves: LOBE-3643, LOBE-3225, LOBE-2588 * chore: update .gitignore to include pnpm-lock.yaml and remove pnpm-lock.yaml file Signed-off-by: Innei <tukon479@gmail.com> --------- Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -117,3 +117,5 @@ e2e/reports
|
||||
out
|
||||
i18n-unused-keys-report.json
|
||||
.vitest-reports
|
||||
|
||||
pnpm-lock.yaml
|
||||
@@ -1,3 +1,5 @@
|
||||
import { APP_WINDOW_MIN_SIZE } from '@lobechat/desktop-bridge';
|
||||
|
||||
import type { BrowserWindowOpts } from './core/browser/Browser';
|
||||
|
||||
export const BrowsersIdentifiers = {
|
||||
@@ -11,7 +13,8 @@ export const appBrowsers = {
|
||||
height: 800,
|
||||
identifier: 'app',
|
||||
keepAlive: true,
|
||||
minWidth: 400,
|
||||
minHeight: APP_WINDOW_MIN_SIZE.height,
|
||||
minWidth: APP_WINDOW_MIN_SIZE.width,
|
||||
path: '/',
|
||||
showOnInit: true,
|
||||
titleBarStyle: 'hidden',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type {
|
||||
InterceptRouteParams,
|
||||
OpenSettingsWindowOptions,
|
||||
WindowResizableParams,
|
||||
WindowMinimumSizeParams,
|
||||
WindowSizeParams,
|
||||
} from '@lobechat/electron-client-ipc';
|
||||
import { findMatchingRoute } from '~common/routes';
|
||||
@@ -81,9 +81,21 @@ export default class BrowserWindowsCtr extends ControllerModule {
|
||||
}
|
||||
|
||||
@IpcMethod()
|
||||
setWindowResizable(params: WindowResizableParams) {
|
||||
setWindowMinimumSize(params: WindowMinimumSizeParams) {
|
||||
this.withSenderIdentifier((identifier) => {
|
||||
this.app.browserManager.setWindowResizable(identifier, params.resizable);
|
||||
const currentSize = this.app.browserManager.getWindowSize(identifier);
|
||||
const nextWindowSize = {
|
||||
...currentSize,
|
||||
};
|
||||
if (params.height) {
|
||||
nextWindowSize.height = Math.max(currentSize.height, params.height);
|
||||
}
|
||||
if (params.width) {
|
||||
nextWindowSize.width = Math.max(currentSize.width, params.width);
|
||||
}
|
||||
|
||||
this.app.browserManager.setWindowSize(identifier, nextWindowSize);
|
||||
this.app.browserManager.setWindowMinimumSize(identifier, params);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
||||
import { APP_WINDOW_MIN_SIZE, TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
||||
import { MainBroadcastEventKey, MainBroadcastParams } from '@lobechat/electron-client-ipc';
|
||||
import {
|
||||
BrowserWindow,
|
||||
@@ -291,9 +291,19 @@ export default class Browser {
|
||||
});
|
||||
}
|
||||
|
||||
setWindowResizable(resizable: boolean): void {
|
||||
logger.debug(`[${this.identifier}] Setting window resizable: ${resizable}`);
|
||||
this._browserWindow?.setResizable(resizable);
|
||||
setWindowMinimumSize(size: { height?: number; width?: number }): void {
|
||||
logger.debug(`[${this.identifier}] Setting window minimum size: ${JSON.stringify(size)}`);
|
||||
|
||||
const currentMinimumSize = this._browserWindow?.getMinimumSize?.() ?? [0, 0];
|
||||
const rawWidth = size.width ?? currentMinimumSize[0];
|
||||
const rawHeight = size.height ?? currentMinimumSize[1];
|
||||
|
||||
// Electron doesn't "reset" minimum size with 0x0 reliably.
|
||||
// Treat 0 / negative as fallback to app-level default preset.
|
||||
const width = rawWidth > 0 ? rawWidth : APP_WINDOW_MIN_SIZE.width;
|
||||
const height = rawHeight > 0 ? rawHeight : APP_WINDOW_MIN_SIZE.height;
|
||||
|
||||
this._browserWindow?.setMinimumSize?.(width, height);
|
||||
}
|
||||
|
||||
// ==================== Window Position ====================
|
||||
|
||||
@@ -250,9 +250,14 @@ export class BrowserManager {
|
||||
browser?.setWindowSize(size);
|
||||
}
|
||||
|
||||
setWindowResizable(identifier: string, resizable: boolean) {
|
||||
getWindowSize(identifier: string) {
|
||||
const browser = this.browsers.get(identifier);
|
||||
browser?.setWindowResizable(resizable);
|
||||
return browser?.browserWindow.getBounds();
|
||||
}
|
||||
|
||||
setWindowMinimumSize(identifier: string, size: { height?: number; width?: number }) {
|
||||
const browser = this.browsers.get(identifier);
|
||||
browser?.setWindowMinimumSize(size);
|
||||
}
|
||||
|
||||
getIdentifierByWebContents(webContents: WebContents): string | null {
|
||||
|
||||
@@ -10,3 +10,8 @@ export {
|
||||
|
||||
// Desktop window constants
|
||||
export const TITLE_BAR_HEIGHT = 38;
|
||||
|
||||
export const APP_WINDOW_MIN_SIZE = {
|
||||
height: 600,
|
||||
width: 1000,
|
||||
} as const;
|
||||
|
||||
@@ -3,6 +3,7 @@ export interface WindowSizeParams {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export interface WindowResizableParams {
|
||||
resizable: boolean;
|
||||
export interface WindowMinimumSizeParams {
|
||||
height?: number;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { TITLE_BAR_HEIGHT } from '@lobechat/desktop-bridge';
|
||||
import { Center, Flexbox, Text } from '@lobehub/ui';
|
||||
import { Divider } from 'antd';
|
||||
import { cx } from 'antd-style';
|
||||
import { css, cx } from 'antd-style';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
|
||||
import SimpleTitleBar from '@/features/Electron/titlebar/SimpleTitleBar';
|
||||
@@ -13,6 +13,9 @@ import { useIsDark } from '@/hooks/useIsDark';
|
||||
|
||||
import { styles } from './style';
|
||||
|
||||
const contentContainer = css`
|
||||
overflow: auto;
|
||||
`;
|
||||
const OnboardingContainer: FC<PropsWithChildren> = ({ children }) => {
|
||||
const isDarkMode = useIsDark();
|
||||
return (
|
||||
@@ -44,9 +47,9 @@ const OnboardingContainer: FC<PropsWithChildren> = ({ children }) => {
|
||||
<ThemeButton placement={'bottomRight'} size={18} />
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
<Center height={'100%'} padding={16} width={'100%'}>
|
||||
<Flexbox align={'center'} className={cx(contentContainer)} height={'100%'} width={'100%'}>
|
||||
{children}
|
||||
</Center>
|
||||
</Flexbox>
|
||||
<Center padding={24}>
|
||||
<Text align={'center'} type={'secondary'}>
|
||||
© 2025 LobeHub. All rights reserved.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Flexbox, type FlexboxProps } from '@lobehub/ui';
|
||||
import { cssVar } from 'antd-style';
|
||||
import { type ReactNode, memo } from 'react';
|
||||
|
||||
interface OnboardingFooterActionsProps extends Omit<FlexboxProps, 'children'> {
|
||||
left?: ReactNode;
|
||||
right?: ReactNode;
|
||||
}
|
||||
|
||||
const OnboardingFooterActions = memo<OnboardingFooterActionsProps>(
|
||||
({ left, right, style, ...rest }) => {
|
||||
return (
|
||||
<Flexbox
|
||||
align={'center'}
|
||||
horizontal
|
||||
justify={'space-between'}
|
||||
style={{
|
||||
background: cssVar.colorBgContainer,
|
||||
bottom: 0,
|
||||
marginTop: 'auto',
|
||||
paddingTop: 16,
|
||||
position: 'sticky',
|
||||
width: '100%',
|
||||
zIndex: 10,
|
||||
...style,
|
||||
}}
|
||||
{...rest}
|
||||
>
|
||||
<div>{left}</div>
|
||||
<div>{right}</div>
|
||||
</Flexbox>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
OnboardingFooterActions.displayName = 'OnboardingFooterActions';
|
||||
|
||||
export default OnboardingFooterActions;
|
||||
@@ -10,6 +10,7 @@ import { useUserStore } from '@/store/user';
|
||||
import { userGeneralSettingsSelectors } from '@/store/user/selectors';
|
||||
|
||||
import LobeMessage from '../components/LobeMessage';
|
||||
import OnboardingFooterActions from '../components/OnboardingFooterActions';
|
||||
|
||||
type DataMode = 'share' | 'privacy';
|
||||
|
||||
@@ -48,7 +49,7 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Flexbox gap={16} style={{ height: '100%', minHeight: '100%' }}>
|
||||
<Flexbox>
|
||||
<LobeMessage sentences={[t('screen4.title'), t('screen4.title2'), t('screen4.title3')]} />
|
||||
<Text as={'p'}>{t('screen4.description')}</Text>
|
||||
@@ -113,19 +114,23 @@ const DataModeStep = memo<DataModeStepProps>(({ onBack, onNext }) => {
|
||||
<Text color={cssVar.colorTextSecondary} fontSize={12} style={{ marginTop: 16 }}>
|
||||
{t('screen4.footerNote')}
|
||||
</Text>
|
||||
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 32 }}>
|
||||
<Button
|
||||
icon={Undo2Icon}
|
||||
onClick={onBack}
|
||||
style={{ color: cssVar.colorTextDescription }}
|
||||
type={'text'}
|
||||
>
|
||||
{t('back')}
|
||||
</Button>
|
||||
<Button onClick={onNext} type={'primary'}>
|
||||
{t('next')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
<OnboardingFooterActions
|
||||
left={
|
||||
<Button
|
||||
icon={Undo2Icon}
|
||||
onClick={onBack}
|
||||
style={{ color: cssVar.colorTextDescription }}
|
||||
type={'text'}
|
||||
>
|
||||
{t('back')}
|
||||
</Button>
|
||||
}
|
||||
right={
|
||||
<Button onClick={onNext} type={'primary'}>
|
||||
{t('next')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
'use client';
|
||||
|
||||
import { AuthorizationProgress, useWatchBroadcast } from '@lobechat/electron-client-ipc';
|
||||
import {
|
||||
AuthorizationPhase,
|
||||
AuthorizationProgress,
|
||||
useWatchBroadcast,
|
||||
} from '@lobechat/electron-client-ipc';
|
||||
import { Alert, Button, Center, Flexbox, Icon, Input, Text } from '@lobehub/ui';
|
||||
import { Divider } from 'antd';
|
||||
import { cssVar } from 'antd-style';
|
||||
@@ -9,6 +13,7 @@ import { memo, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { isDesktop } from '@/const/version';
|
||||
import UserInfo from '@/features/User/UserInfo';
|
||||
import { remoteServerService } from '@/services/electron/remoteServer';
|
||||
import { useElectronStore } from '@/store/electron';
|
||||
import { setDesktopAutoOidcFirstOpenHandled } from '@/utils/electron/autoOidc';
|
||||
@@ -21,6 +26,13 @@ type LoginMethod = 'cloud' | 'selfhost';
|
||||
// 登录状态类型
|
||||
type LoginStatus = 'idle' | 'loading' | 'success' | 'error';
|
||||
|
||||
const authorizationPhaseI18nKeyMap: Record<AuthorizationPhase, string> = {
|
||||
browser_opened: 'screen5.auth.phase.browserOpened',
|
||||
cancelled: 'screen5.actions.cancel',
|
||||
verifying: 'screen5.auth.phase.verifying',
|
||||
waiting_for_auth: 'screen5.auth.phase.waitingForAuth',
|
||||
};
|
||||
|
||||
const loginMethodMetas = {
|
||||
cloud: {
|
||||
descriptionKey: 'screen5.methods.cloud.description',
|
||||
@@ -181,6 +193,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
setAuthProgress(progress);
|
||||
if (progress.phase === 'cancelled') {
|
||||
setCloudLoginStatus('idle');
|
||||
setSelfhostLoginStatus('idle');
|
||||
setAuthProgress(null);
|
||||
}
|
||||
});
|
||||
@@ -188,6 +201,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
const handleCancelAuth = async () => {
|
||||
await remoteServerService.cancelAuthorization();
|
||||
setCloudLoginStatus('idle');
|
||||
setSelfhostLoginStatus('idle');
|
||||
setAuthProgress(null);
|
||||
};
|
||||
|
||||
@@ -195,13 +209,19 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
const renderCloudContent = () => {
|
||||
if (cloudLoginStatus === 'success') {
|
||||
return (
|
||||
<Flexbox gap={12} style={{ width: '100%' }}>
|
||||
<Flexbox gap={16} style={{ width: '100%' }}>
|
||||
<Alert
|
||||
description={t('authResult.success.desc')}
|
||||
style={{ width: '100%' }}
|
||||
title={t('authResult.success.title')}
|
||||
type={'success'}
|
||||
/>
|
||||
<UserInfo
|
||||
style={{
|
||||
background: cssVar.colorFillSecondary,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
disabled={isSigningOut || isConnectingServer}
|
||||
@@ -239,27 +259,35 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
}
|
||||
|
||||
if (cloudLoginStatus === 'loading') {
|
||||
const phaseText = t(authorizationPhaseI18nKeyMap[authProgress?.phase ?? 'browser_opened'], {
|
||||
defaultValue: t('screen5.actions.signingIn'),
|
||||
});
|
||||
const remainingSeconds = authProgress
|
||||
? Math.max(0, Math.ceil((authProgress.maxPollTime - authProgress.elapsed) / 1000))
|
||||
: null;
|
||||
|
||||
return (
|
||||
<Flexbox gap={8} style={{ width: '100%' }}>
|
||||
<Button block disabled={true} icon={Cloud} loading={true} size={'large'} type={'primary'}>
|
||||
{authProgress
|
||||
? t(`screen5.auth.phase.${authProgress.phase}`, {
|
||||
defaultValue: t('screen5.actions.signingIn'),
|
||||
})
|
||||
: t('screen5.actions.signingIn')}
|
||||
{t('screen5.actions.signingIn')}
|
||||
</Button>
|
||||
{authProgress && (
|
||||
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
||||
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
|
||||
{phaseText}
|
||||
</Text>
|
||||
<Flexbox align={'center'} horizontal justify={'space-between'}>
|
||||
{remainingSeconds !== null ? (
|
||||
<Text style={{ color: cssVar.colorTextDescription }} type={'secondary'}>
|
||||
{t('screen5.auth.remaining', {
|
||||
time: Math.round((authProgress.maxPollTime - authProgress.elapsed) / 1000),
|
||||
time: remainingSeconds,
|
||||
})}
|
||||
</Text>
|
||||
<Button onClick={handleCancelAuth} size={'small'} type={'text'}>
|
||||
{t('screen5.actions.cancel')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
)}
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
<Button onClick={handleCancelAuth} size={'small'} type={'text'}>
|
||||
{t('screen5.actions.cancel')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
);
|
||||
}
|
||||
@@ -283,13 +311,19 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
const renderSelfhostContent = () => {
|
||||
if (selfhostLoginStatus === 'success') {
|
||||
return (
|
||||
<Flexbox gap={12} style={{ width: '100%' }}>
|
||||
<Flexbox gap={16} style={{ width: '100%' }}>
|
||||
<Alert
|
||||
description={t('authResult.success.desc')}
|
||||
style={{ width: '100%' }}
|
||||
title={t('authResult.success.title')}
|
||||
type={'success'}
|
||||
/>
|
||||
<UserInfo
|
||||
style={{
|
||||
background: cssVar.colorFillSecondary,
|
||||
borderRadius: 8,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
block
|
||||
disabled={isSigningOut || isConnectingServer}
|
||||
@@ -354,8 +388,8 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flexbox gap={32}>
|
||||
<Flexbox>
|
||||
<Center gap={32} style={{ height: '100%', minHeight: '100%' }}>
|
||||
<Flexbox align={'flex-start'} justify={'flex-start'} style={{ width: '100%' }}>
|
||||
<LobeMessage sentences={[t('screen5.title'), t('screen5.title2'), t('screen5.title3')]} />
|
||||
<Text as={'p'}>{t('screen5.description')}</Text>
|
||||
</Flexbox>
|
||||
@@ -401,7 +435,7 @@ const LoginStep = memo<LoginStepProps>(({ onBack, onNext }) => {
|
||||
</Button>
|
||||
</Flexbox>
|
||||
)}
|
||||
</Flexbox>
|
||||
</Center>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { ensureElectronIpc } from '@/utils/electron/ipc';
|
||||
|
||||
import LobeMessage from '../components/LobeMessage';
|
||||
import OnboardingFooterActions from '../components/OnboardingFooterActions';
|
||||
|
||||
type PermissionMeta = {
|
||||
descriptionKey: string;
|
||||
@@ -154,7 +155,7 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Flexbox gap={16}>
|
||||
<Flexbox gap={16} style={{ height: '100%', minHeight: '100%' }}>
|
||||
<Flexbox>
|
||||
<LobeMessage sentences={[t('screen3.title'), t('screen3.title2'), t('screen3.title3')]} />
|
||||
<Text as={'p'}>{t('screen3.description')}</Text>
|
||||
@@ -207,19 +208,23 @@ const PermissionsStep = memo<PermissionsStepProps>(({ onBack, onNext }) => {
|
||||
</Block>
|
||||
))}
|
||||
</Block>
|
||||
<Flexbox horizontal justify={'space-between'} style={{ marginTop: 32 }}>
|
||||
<Button
|
||||
icon={Undo2Icon}
|
||||
onClick={onBack}
|
||||
style={{ color: cssVar.colorTextDescription }}
|
||||
type={'text'}
|
||||
>
|
||||
{t('back')}
|
||||
</Button>
|
||||
<Button onClick={onNext} type={'primary'}>
|
||||
{t('next')}
|
||||
</Button>
|
||||
</Flexbox>
|
||||
<OnboardingFooterActions
|
||||
left={
|
||||
<Button
|
||||
icon={Undo2Icon}
|
||||
onClick={onBack}
|
||||
style={{ color: cssVar.colorTextDescription }}
|
||||
type={'text'}
|
||||
>
|
||||
{t('back')}
|
||||
</Button>
|
||||
}
|
||||
right={
|
||||
<Button onClick={onNext} type={'primary'}>
|
||||
{t('next')}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Flexbox>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { APP_WINDOW_MIN_SIZE } from '@lobechat/desktop-bridge';
|
||||
import { Flexbox, Skeleton } from '@lobehub/ui';
|
||||
import { Suspense, memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
@@ -50,12 +51,11 @@ const DesktopOnboardingPage = memo(() => {
|
||||
|
||||
// 设置窗口大小和可调整性
|
||||
useEffect(() => {
|
||||
const fixedSize = { height: 900, width: 1400 };
|
||||
const minimumSize = { height: 900, width: 1200 };
|
||||
|
||||
const applyWindowSettings = async () => {
|
||||
try {
|
||||
await electronSystemService.setWindowSize(fixedSize);
|
||||
await electronSystemService.setWindowResizable({ resizable: false });
|
||||
await electronSystemService.setWindowMinimumSize(minimumSize);
|
||||
} catch (error) {
|
||||
console.error('[DesktopOnboarding] Failed to apply window settings:', error);
|
||||
}
|
||||
@@ -64,7 +64,8 @@ const DesktopOnboardingPage = memo(() => {
|
||||
applyWindowSettings();
|
||||
|
||||
return () => {
|
||||
electronSystemService.setWindowResizable({ resizable: true }).catch((error) => {
|
||||
// Restore to app-level default minimum size preset
|
||||
electronSystemService.setWindowMinimumSize(APP_WINDOW_MIN_SIZE).catch((error) => {
|
||||
console.error('[DesktopOnboarding] Failed to restore window settings:', error);
|
||||
});
|
||||
};
|
||||
@@ -127,9 +128,9 @@ const DesktopOnboardingPage = memo(() => {
|
||||
// 如果是第4步(LoginStep),完成 onboarding
|
||||
setDesktopOnboardingCompleted();
|
||||
clearDesktopOnboardingStep(); // Clear persisted step since onboarding is complete
|
||||
// Restore window resizable before hard reload (cleanup won't run due to hard navigation)
|
||||
// Restore window minimum size before hard reload (cleanup won't run due to hard navigation)
|
||||
electronSystemService
|
||||
.setWindowResizable({ resizable: true })
|
||||
.setWindowMinimumSize(APP_WINDOW_MIN_SIZE)
|
||||
.catch(console.error)
|
||||
.finally(() => {
|
||||
// Use hard reload instead of SPA navigation to ensure the app boots with the new desktop state.
|
||||
@@ -196,7 +197,7 @@ const DesktopOnboardingPage = memo(() => {
|
||||
|
||||
return (
|
||||
<OnboardingContainer>
|
||||
<Flexbox gap={24} style={{ maxWidth: 560, width: '100%' }}>
|
||||
<Flexbox gap={24} style={{ maxWidth: 560, minHeight: '100%', width: '100%' }}>
|
||||
<Suspense
|
||||
fallback={
|
||||
<Flexbox gap={8}>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type {
|
||||
ElectronAppState,
|
||||
WindowResizableParams,
|
||||
WindowMinimumSizeParams,
|
||||
WindowSizeParams,
|
||||
} from '@lobechat/electron-client-ipc';
|
||||
|
||||
@@ -36,14 +36,14 @@ class ElectronSystemService {
|
||||
return this.ipc.windows.minimizeWindow();
|
||||
}
|
||||
|
||||
async setWindowResizable(params: WindowResizableParams): Promise<void> {
|
||||
return this.ipc.windows.setWindowResizable(params);
|
||||
}
|
||||
|
||||
async setWindowSize(params: WindowSizeParams): Promise<void> {
|
||||
return this.ipc.windows.setWindowSize(params);
|
||||
}
|
||||
|
||||
async setWindowMinimumSize(params: WindowMinimumSizeParams): Promise<void> {
|
||||
return this.ipc.windows.setWindowMinimumSize(params);
|
||||
}
|
||||
|
||||
async openExternalLink(url: string): Promise<void> {
|
||||
return this.ipc.system.openExternalLink(url);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user