feat(core): add global loading state (#4840)
This commit is contained in:
@@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -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,
|
||||||
|
]);
|
||||||
|
});
|
||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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" />}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user