feat(component): improve fallback skeleton (#2323)

This commit is contained in:
Himself65 2023-05-11 13:35:42 +08:00 committed by GitHub
parent 47848cb5da
commit 73dbb39009
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 130 additions and 60 deletions

View File

@ -1,6 +1,5 @@
import { styled } from '@affine/component';
import { AffineLoading } from '@affine/component/affine-loading';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { memo, Suspense } from 'react';
export const Loading = memo(function Loading() {
@ -35,12 +34,15 @@ const StyledLoadingContainer = styled('div')(() => {
};
});
export const PageLoading = ({ text }: { text?: string }) => {
const t = useAFFiNEI18N();
export const PageLoading = () => {
// We disable the loading on desktop, because want it looks faster.
// This is a design requirement.
if (environment.isDesktop) {
return null;
}
return (
<StyledLoadingContainer>
<Loading />
<h1>{text ? text : t['Loading']()}</h1>
</StyledLoadingContainer>
);
};

View File

@ -2,12 +2,12 @@ import {
AppContainer,
MainContainer,
ToolContainer,
WorkspaceFallback,
} from '@affine/component/workspace';
import { DebugLogger } from '@affine/debug';
import { DEFAULT_HELLO_WORLD_PAGE_ID } from '@affine/env';
import { initPage } from '@affine/env/blocksuite';
import { setUpLanguage, useI18N } from '@affine/i18n';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import { createAffineGlobalChannel } from '@affine/workspace/affine/sync';
import {
rootCurrentPageIdAtom,
@ -31,7 +31,6 @@ import {
publicWorkspaceIdAtom,
} from '../atoms/public-workspace';
import { HelpIsland } from '../components/pure/help-island';
import { PageLoading } from '../components/pure/loading';
import { RootAppSidebar } from '../components/root-app-sidebar';
import { useCurrentWorkspace } from '../hooks/current/use-current-workspace';
import { useRouterHelper } from '../hooks/use-router-helper';
@ -141,15 +140,14 @@ export const CurrentWorkspaceContext = ({
useRouterWithWorkspaceIdDefense(router);
const metadata = useAtomValue(rootWorkspacesMetadataAtom);
const exist = metadata.find(m => m.id === workspaceId);
const t = useAFFiNEI18N();
if (!router.isReady) {
return <PageLoading text={t['Router is Loading']()} />;
return <WorkspaceFallback key="router-is-loading" />;
}
if (!workspaceId) {
return <PageLoading text={t['Finding Workspace ID']()} />;
return <WorkspaceFallback key="finding-workspace-id" />;
}
if (!exist) {
return <PageLoading text={t['Workspace Not Found']()} />;
return <WorkspaceFallback key="workspace-not-found" />;
}
return <>{children}</>;
};
@ -157,7 +155,6 @@ export const CurrentWorkspaceContext = ({
export const WorkspaceLayout: FC<PropsWithChildren> =
function WorkspacesSuspense({ children }) {
const i18n = useI18N();
const t = useAFFiNEI18N();
useEffect(() => {
document.documentElement.lang = i18n.language;
// todo(himself65): this is a hack, we should use a better way to set the language
@ -233,9 +230,7 @@ export const WorkspaceLayout: FC<PropsWithChildren> =
</CurrentWorkspaceContext>
</AllWorkspaceContext>
<CurrentWorkspaceContext>
<Suspense
fallback={<PageLoading text={t['Finding Current Workspace']()} />}
>
<Suspense fallback={<WorkspaceFallback />}>
<Provider>
<WorkspaceLayoutInner>{children}</WorkspaceLayoutInner>
</Provider>
@ -251,7 +246,6 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
const currentPageId = useAtomValue(rootCurrentPageIdAtom);
const router = useRouter();
const { jumpToPage } = useRouterHelper(router);
const t = useAFFiNEI18N();
//#region init workspace
if (currentWorkspace.blockSuiteWorkspace.isEmpty) {
@ -360,9 +354,7 @@ export const WorkspaceLayoutInner: FC<PropsWithChildren> = ({ children }) => {
paths={isPublicWorkspace ? publicPathGenerator : pathGenerator}
/>
<MainContainer>
<Suspense fallback={<PageLoading text={t['Page is Loading']()} />}>
{children}
</Suspense>
<Suspense fallback={<WorkspaceFallback />}>{children}</Suspense>
<ToolContainer>
{/* fixme(himself65): remove this */}
<div id="toolWrapper" style={{ marginBottom: '12px' }}>

View File

@ -1,6 +1,7 @@
import '@affine/component/theme/global.css';
import '@affine/component/theme/theme.css';
import { WorkspaceFallback } from '@affine/component/workspace';
import { config, setupGlobal } from '@affine/env';
import { createI18n, I18nextProvider } from '@affine/i18n';
import { rootStore } from '@affine/workspace/atom';
@ -15,7 +16,6 @@ import React, { lazy, Suspense, useEffect, useMemo } from 'react';
import { AffineErrorBoundary } from '../components/affine/affine-error-eoundary';
import { ProviderComposer } from '../components/provider-composer';
import { PageLoading } from '../components/pure/loading';
import { MessageCenter } from '../components/pure/message-center';
import { ThemeProvider } from '../providers/theme-provider';
import type { NextPageWithLayout } from '../shared';
@ -66,7 +66,7 @@ const App = function App({
<I18nextProvider i18n={i18n}>
<MessageCenter />
<AffineErrorBoundary router={useRouter()}>
<Suspense fallback={<PageLoading key="RootPageLoading" />}>
<Suspense fallback={<WorkspaceFallback key="RootPageLoading" />}>
<ProviderComposer
contexts={useMemo(
() =>

View File

@ -1,5 +1,4 @@
import { DebugLogger } from '@affine/debug';
import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { NextPage } from 'next';
import { useRouter } from 'next/router';
import { Suspense, useEffect } from 'react';
@ -63,9 +62,8 @@ const IndexPageInner = () => {
};
const IndexPage: NextPage = () => {
const t = useAFFiNEI18N();
return (
<Suspense fallback={<PageLoading text={t['Loading All Workspaces']()} />}>
<Suspense fallback={<PageLoading />}>
<IndexPageInner />
</Suspense>
);

View File

@ -84,9 +84,7 @@ function AuthContext({ children }: PropsWithChildren): ReactElement {
}
}, [login]);
if (!login) {
return (
<PageLoading text="No login, redirecting to local workspace page..." />
);
return <PageLoading />;
}
return <>{children}</>;
}
@ -278,7 +276,7 @@ export const AffinePlugin: WorkspacePlugin<WorkspaceFlavour.AFFINE> = {
UI: {
Provider: ({ children }) => {
return (
<Suspense fallback={<PageLoading text="Checking login status..." />}>
<Suspense fallback={<PageLoading />}>
<AuthContext>{children}</AuthContext>
</Suspense>
);

View File

@ -0,0 +1,15 @@
import { style } from '@vanilla-extract/css';
export const fallbackStyle = style({
margin: '0 2px',
height: '100%',
});
export const fallbackHeaderStyle = style({
padding: '0 6px',
height: '58px',
width: '100%',
display: 'flex',
flexDirection: 'row',
gap: '8px',
});

View File

@ -0,0 +1,18 @@
import { Skeleton } from '@mui/material';
import type { ReactElement } from 'react';
import { fallbackHeaderStyle, fallbackStyle } from './fallback.css';
import { AppSidebar } from './index';
export const AppSidebarFallback = (): ReactElement | null => {
return (
<AppSidebar>
<div className={fallbackStyle}>
<div className={fallbackHeaderStyle}>
<Skeleton variant="circular" width={40} height={40} />
<Skeleton variant="rectangular" width={150} height={40} />
</div>
</div>
</AppSidebar>
);
};

View File

@ -2,9 +2,15 @@ import { IconButton } from '@affine/component';
import { SidebarIcon } from '@blocksuite/icons';
import type { Meta, StoryFn } from '@storybook/react';
import { useAtom } from 'jotai';
import type { PropsWithChildren } from 'react';
import { useState } from 'react';
import { AppSidebar, appSidebarOpenAtom, ResizeIndicator } from '.';
import {
AppSidebar,
AppSidebarFallback,
appSidebarOpenAtom,
ResizeIndicator,
} from '.';
import { navHeaderStyle, sidebarButtonStyle } from './index.css';
export default {
@ -12,42 +18,61 @@ export default {
component: AppSidebar,
} satisfies Meta;
const Container = ({ children }: PropsWithChildren) => (
<main
style={{
position: 'relative',
width: '100vw',
height: '600px',
overflow: 'hidden',
display: 'flex',
flexDirection: 'row',
}}
>
{children}
</main>
);
const Main = () => {
const [open, setOpen] = useAtom(appSidebarOpenAtom);
return (
<div>
<div className={navHeaderStyle}>
{!open && (
<IconButton
className={sidebarButtonStyle}
onClick={() => {
setOpen(true);
}}
>
<SidebarIcon width={24} height={24} />
</IconButton>
)}
</div>
</div>
);
};
const Footer = () => <div>Add Page</div>;
export const Default: StoryFn = () => {
const [open, setOpen] = useAtom(appSidebarOpenAtom);
const [ref, setRef] = useState<HTMLElement | null>(null);
return (
<>
<main
style={{
position: 'relative',
width: '100vw',
height: '600px',
overflow: 'hidden',
display: 'flex',
flexDirection: 'row',
}}
>
<Container>
<AppSidebar footer={<Footer />} ref={setRef}>
Test
</AppSidebar>
<ResizeIndicator targetElement={ref} />
<div>
<div className={navHeaderStyle}>
{!open && (
<IconButton
className={sidebarButtonStyle}
onClick={() => {
setOpen(true);
}}
>
<SidebarIcon width={24} height={24} />
</IconButton>
)}
</div>
</div>
</main>
<Main />
</Container>
</>
);
};
export const Fallback = () => {
return (
<Container>
<AppSidebarFallback />
<Main />
</Container>
);
};

View File

@ -151,5 +151,6 @@ export const AppSidebar = forwardRef<HTMLElement, AppSidebarProps>(
}
);
export { AppSidebarFallback } from './fallback';
export type { ResizeIndicatorProps } from './resize-indicator';
export { ResizeIndicator } from './resize-indicator';

View File

@ -1,5 +1,12 @@
import { style } from '@vanilla-extract/css';
export const blockSuiteEditorStyle = style({
padding: '0 1rem',
maxWidth: 'var(--affine-editor-width)',
margin: '0 2rem',
padding: '0 24px',
});
export const blockSuiteEditorHeaderStyle = style({
marginTop: '40px',
marginBottom: '40px',
});

View File

@ -12,7 +12,10 @@ import { createPortal } from 'react-dom';
import type { FallbackProps } from 'react-error-boundary';
import { ErrorBoundary } from 'react-error-boundary';
import { blockSuiteEditorStyle } from './index.css';
import {
blockSuiteEditorHeaderStyle,
blockSuiteEditorStyle,
} from './index.css';
export type EditorProps = {
page: Page;
@ -136,10 +139,11 @@ const BlockSuiteErrorFallback = (
export const BlockSuiteFallback = memo(function BlockSuiteFallback() {
return (
<div className={blockSuiteEditorStyle}>
<Skeleton animation="wave" height={60} />
{Array.from({ length: 10 }).map((_, index) => (
<Skeleton animation="wave" height={30} key={index} />
))}
<Skeleton
className={blockSuiteEditorHeaderStyle}
animation="wave"
height={50}
/>
<Skeleton animation="wave" height={30} width="40%" />
</div>
);

View File

@ -1,6 +1,7 @@
import { clsx } from 'clsx';
import type { PropsWithChildren, ReactElement } from 'react';
import { AppSidebarFallback } from '../app-sidebar';
import { appStyle, mainContainerStyle, toolStyle } from './index.css';
export type WorkspaceRootProps = PropsWithChildren<{
@ -32,3 +33,12 @@ export const MainContainer = (props: MainContainerProps): ReactElement => {
export const ToolContainer = (props: PropsWithChildren): ReactElement => {
return <div className={toolStyle}>{props.children}</div>;
};
export const WorkspaceFallback = (): ReactElement => {
return (
<AppContainer>
<AppSidebarFallback />
<MainContainer></MainContainer>
</AppContainer>
);
};