feat(core): add global loading state (#4840)

This commit is contained in:
fourdim
2023-11-09 22:25:05 -05:00
committed by GitHub
parent 1fe5a0fffa
commit af7d331610
5 changed files with 120 additions and 1 deletions

View File

@@ -0,0 +1,29 @@
import { style } from '@vanilla-extract/css';
export const globalLoadingWrapperStyle = style({
position: 'fixed',
top: 0,
left: 0,
bottom: 0,
right: '100%',
zIndex: 5,
backgroundColor: 'var(--affine-background-modal-color)',
opacity: 0,
transition: 'opacity .3s',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
color: 'var(--affine-processing-color)',
'@media': {
print: {
display: 'none',
zIndex: -1,
},
},
selectors: {
'&[data-loading="true"]': {
right: 0,
opacity: 1,
},
},
});

View File

@@ -0,0 +1,35 @@
import { atom } from 'jotai';
import { nanoid } from 'nanoid';
export type GlobalLoadingEvent = {
key?: string;
};
const globalLoadingEventsBaseAtom = atom<GlobalLoadingEvent[]>([]);
export const globalLoadingEventsAtom = atom<GlobalLoadingEvent[]>(get =>
get(globalLoadingEventsBaseAtom)
);
export const resolveGlobalLoadingEventAtom = atom(
null,
(_, set, key: string) => {
set(globalLoadingEventsBaseAtom, globalLoadingEvent =>
globalLoadingEvent.filter(notification => notification.key !== key)
);
}
);
export const pushGlobalLoadingEventAtom = atom<
null,
[GlobalLoadingEvent],
void
>(null, (_, set, newGlobalLoadingEvent) => {
newGlobalLoadingEvent.key = newGlobalLoadingEvent.key || nanoid();
set(globalLoadingEventsBaseAtom, globalLoadingEvents => [
// push to the top
{ ...newGlobalLoadingEvent },
...globalLoadingEvents,
]);
});

View File

@@ -0,0 +1,34 @@
import { useAtomValue } from 'jotai';
import { type ReactNode, useEffect, useState } from 'react';
import { Loading } from '../../ui/loading';
import * as styles from './index.css';
import { globalLoadingEventsAtom } from './index.jotai';
export {
type GlobalLoadingEvent,
pushGlobalLoadingEventAtom,
resolveGlobalLoadingEventAtom,
} from './index.jotai';
export function GlobalLoading(): ReactNode {
const globalLoadingEvents = useAtomValue(globalLoadingEventsAtom);
const [loading, setLoading] = useState(false);
useEffect(() => {
if (globalLoadingEvents.length) {
setLoading(true);
} else {
setLoading(false);
}
}, [globalLoadingEvents]);
if (!globalLoadingEvents.length) {
return null;
}
return (
<div className={styles.globalLoadingWrapperStyle} data-loading={loading}>
<Loading size={20} />
</div>
);
}

View File

@@ -3,6 +3,7 @@ import '@affine/component/theme/theme.css';
import '@toeverything/components/style.css'; import '@toeverything/components/style.css';
import { AffineContext } from '@affine/component/context'; import { AffineContext } from '@affine/component/context';
import { GlobalLoading } from '@affine/component/global-loading';
import { NotificationCenter } from '@affine/component/notification-center'; import { NotificationCenter } from '@affine/component/notification-center';
import { WorkspaceFallback } from '@affine/component/workspace'; import { WorkspaceFallback } from '@affine/component/workspace';
import { CacheProvider } from '@emotion/react'; import { CacheProvider } from '@emotion/react';
@@ -62,6 +63,7 @@ export const App = memo(function App() {
<AffineContext store={getCurrentStore()}> <AffineContext store={getCurrentStore()}>
<CloudSessionProvider> <CloudSessionProvider>
<DebugProvider> <DebugProvider>
<GlobalLoading />
{runtimeConfig.enableNotificationCenter && <NotificationCenter />} {runtimeConfig.enableNotificationCenter && <NotificationCenter />}
<RouterProvider <RouterProvider
fallbackElement={<WorkspaceFallback key="RouterFallback" />} fallbackElement={<WorkspaceFallback key="RouterFallback" />}

View File

@@ -1,9 +1,14 @@
import {
pushGlobalLoadingEventAtom,
resolveGlobalLoadingEventAtom,
} from '@affine/component/global-loading';
import { pushNotificationAtom } from '@affine/component/notification-center'; import { pushNotificationAtom } from '@affine/component/notification-center';
import { useAFFiNEI18N } from '@affine/i18n/hooks'; import { useAFFiNEI18N } from '@affine/i18n/hooks';
import type { PageBlockModel } from '@blocksuite/blocks'; import type { PageBlockModel } from '@blocksuite/blocks';
import { ContentParser } from '@blocksuite/blocks/content-parser'; import { ContentParser } from '@blocksuite/blocks/content-parser';
import type { Page } from '@blocksuite/store'; import type { Page } from '@blocksuite/store';
import { useSetAtom } from 'jotai'; import { useSetAtom } from 'jotai';
import { nanoid } from 'nanoid';
import { useCallback } from 'react'; import { useCallback } from 'react';
type ExportType = 'pdf' | 'html' | 'png' | 'markdown'; type ExportType = 'pdf' | 'html' | 'png' | 'markdown';
@@ -49,10 +54,16 @@ async function exportHandler({ page, type }: ExportHandlerOptions) {
export const useExportPage = (page: Page) => { export const useExportPage = (page: Page) => {
const pushNotification = useSetAtom(pushNotificationAtom); const pushNotification = useSetAtom(pushNotificationAtom);
const pushGlobalLoadingEvent = useSetAtom(pushGlobalLoadingEventAtom);
const resolveGlobalLoadingEvent = useSetAtom(resolveGlobalLoadingEventAtom);
const t = useAFFiNEI18N(); const t = useAFFiNEI18N();
const onClickHandler = useCallback( const onClickHandler = useCallback(
async (type: ExportType) => { async (type: ExportType) => {
const globalLoadingID = nanoid();
pushGlobalLoadingEvent({
key: globalLoadingID,
});
try { try {
await exportHandler({ await exportHandler({
page, page,
@@ -70,9 +81,17 @@ export const useExportPage = (page: Page) => {
message: t['com.affine.export.error.message'](), message: t['com.affine.export.error.message'](),
type: 'error', type: 'error',
}); });
} finally {
resolveGlobalLoadingEvent(globalLoadingID);
} }
}, },
[page, pushNotification, t] [
page,
pushGlobalLoadingEvent,
pushNotification,
resolveGlobalLoadingEvent,
t,
]
); );
return onClickHandler; return onClickHandler;