diff --git a/apps/web/src/components/pure/loading/Loading.tsx b/apps/web/src/components/pure/loading/Loading.tsx
deleted file mode 100644
index 099d50beb1..0000000000
--- a/apps/web/src/components/pure/loading/Loading.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import {
- StyledLoading,
- StyledLoadingItem,
- StyledLoadingWrapper,
-} from './styled';
-
-export const Loading = ({ size = 40 }: { size?: number }) => {
- return (
-
-
-
-
-
-
-
-
- );
-};
-
-export default Loading;
diff --git a/apps/web/src/components/pure/loading/PageLoading.tsx b/apps/web/src/components/pure/loading/PageLoading.tsx
index d01c9ba71c..9a99585071 100644
--- a/apps/web/src/components/pure/loading/PageLoading.tsx
+++ b/apps/web/src/components/pure/loading/PageLoading.tsx
@@ -1,7 +1,22 @@
import { styled } from '@affine/component';
+import { AffineLoading } from '@affine/component/affine-loading';
import { useTranslation } from '@affine/i18n';
+import { memo, Suspense } from 'react';
-import Loading from './Loading';
+export const Loading = memo(function Loading() {
+ return (
+
+ );
+});
// Used for the full page loading
const StyledLoadingContainer = styled('div')(() => {
diff --git a/apps/web/src/components/pure/loading/index.tsx b/apps/web/src/components/pure/loading/index.tsx
index 49aaacec80..df9e6fac61 100644
--- a/apps/web/src/components/pure/loading/index.tsx
+++ b/apps/web/src/components/pure/loading/index.tsx
@@ -1,3 +1,3 @@
-import Loading from './Loading';
+import { Loading } from './PageLoading';
export * from './PageLoading';
export default Loading;
diff --git a/apps/web/src/components/pure/loading/styled.ts b/apps/web/src/components/pure/loading/styled.ts
deleted file mode 100644
index 7ed7cce7a2..0000000000
--- a/apps/web/src/components/pure/loading/styled.ts
+++ /dev/null
@@ -1,94 +0,0 @@
-import { styled } from '@affine/component';
-
-// Inspired by https://codepen.io/graphilla/pen/rNvBMYY
-export const StyledLoadingWrapper = styled('div', {
- shouldForwardProp: prop => {
- return !['size'].includes(prop as string);
- },
-})<{ size?: number }>(({ size = 40 }) => {
- return {
- width: size * 4,
- height: size * 4,
- position: 'relative',
- };
-});
-export const StyledLoading = styled('div')`
- position: absolute;
- left: 25%;
- top: 50%;
- transform: rotateX(55deg) rotateZ(-45deg);
- @keyframes slide {
- 0% {
- transform: translate(var(--sx), var(--sy));
- }
- 65% {
- transform: translate(var(--ex), var(--sy));
- }
- 95%,
- 100% {
- transform: translate(var(--ex), var(--ey));
- }
- }
-`;
-
-export const StyledLoadingItem = styled('div')<{ size: number }>(
- ({ size = 40 }) => {
- return `
- position: absolute;
- width: ${size}px;
- height: ${size}px;
- background: #9dacf9;
- animation: slide 0.9s cubic-bezier(0.65, 0.53, 0.59, 0.93) infinite;
-
- &::before,
- &::after {
- content: '';
- position: absolute;
- width: 100%;
- height: 100%;
- }
-
- &::before {
- background: #5260b9;
- transform: skew(0deg, -45deg);
- right: 100%;
- top: 50%;
- }
-
- &::after {
- background: #6880ff;
- transform: skew(-45deg, 0deg);
- top: 100%;
- right: 50%;
- }
-
- &:nth-of-type(1) {
- --sx: 50%;
- --sy: -50%;
- --ex: 150%;
- --ey: 50%;
- }
-
- &:nth-of-type(2) {
- --sx: -50%;
- --sy: -50%;
- --ex: 50%;
- --ey: -50%;
- }
-
- &:nth-of-type(3) {
- --sx: 150%;
- --sy: 50%;
- --ex: 50%;
- --ey: 50%;
- }
-
- &:nth-of-type(4) {
- --sx: 50%;
- --sy: 50%;
- --ex: -50%;
- --ey: -50%;
- }
-`;
- }
-);
diff --git a/packages/component/src/components/affine-loading/index.tsx b/packages/component/src/components/affine-loading/index.tsx
index 6a98207fb9..aeb5490838 100644
--- a/packages/component/src/components/affine-loading/index.tsx
+++ b/packages/component/src/components/affine-loading/index.tsx
@@ -1,18 +1,30 @@
import { useTheme } from '@mui/material';
+import type { FC } from 'react';
import { InternalLottie } from '../internal-lottie';
import dark from './loading-black.json';
import light from './loading-white.json';
-export const AffineLoading = () => {
+export type AffineLoadingProps = {
+ loop?: boolean;
+ autoplay?: boolean;
+ autoReverse?: boolean;
+};
+
+export const AffineLoading: FC = ({
+ loop = false,
+ autoplay = false,
+ autoReverse = false,
+}) => {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
return (
= ({
height,
}) => {
const element = useRef(null);
- const lottieInstance = useRef();
+ const lottieInstance = useRef();
const lottie = useAtomValue(lottieAtom);
+ const directionRef = useRef<1 | -1>(1);
useEffect(() => {
+ const callback = () => {
+ if (!lottieInstance.current) {
+ return;
+ }
+ const frame = lottieInstance.current.currentFrame.toFixed(0);
+ if (frame === '1' || frame === '0') {
+ directionRef.current = 1;
+ lottieInstance.current.setDirection(directionRef.current);
+ lottieInstance.current.goToAndStop(0, true);
+ lottieInstance.current.play();
+ } else {
+ directionRef.current = -1;
+ lottieInstance.current.setDirection(directionRef.current);
+ lottieInstance.current.goToAndStop(
+ lottieInstance.current.totalFrames - 1,
+ true
+ );
+ lottieInstance.current.play();
+ }
+ };
if (element.current) {
- lottieInstance.current = lottie.loadAnimation({
- ...options,
- container: element.current,
- });
+ if (options.autoReverse && options.autoplay) {
+ lottieInstance.current = lottie.loadAnimation({
+ ...options,
+ autoplay: false,
+ loop: false,
+ container: element.current,
+ });
+ } else {
+ lottieInstance.current = lottie.loadAnimation({
+ ...options,
+ container: element.current,
+ });
+ }
+ if (options.autoReverse) {
+ lottieInstance.current.addEventListener('complete', callback);
+ }
}
return () => {
+ if (options.autoReverse) {
+ lottieInstance.current?.removeEventListener('complete', callback);
+ }
lottieInstance.current?.destroy();
};
}, [lottie, options]);
diff --git a/packages/component/src/stories/AffineLoading.stories.tsx b/packages/component/src/stories/AffineLoading.stories.tsx
index a551099a98..7f89f101fa 100644
--- a/packages/component/src/stories/AffineLoading.stories.tsx
+++ b/packages/component/src/stories/AffineLoading.stories.tsx
@@ -7,4 +7,19 @@ export default {
component: AffineLoading,
};
-export const Default: StoryFn = () => ;
+export const Default: StoryFn = ({ width, loop, autoplay, autoReverse }) => (
+
+);
+Default.args = {
+ width: 100,
+ loop: true,
+ autoplay: true,
+ autoReverse: true,
+};