Skip to content

Commit c5ad511

Browse files
committed
feat: LottiePlayer / NetworkLottiePlayer 추가
1 parent b014d64 commit c5ad511

File tree

5 files changed

+100
-22
lines changed

5 files changed

+100
-22
lines changed

apps/pyconkr-admin/src/components/layouts/admin_editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ const ReadOnlyValueField: React.FC<{
105105
)}
106106
{fieldState.blob.type.startsWith("application/json") && fieldState.blobText && (
107107
<Box sx={{ maxWidth: "600px", overflow: "auto" }}>
108-
<Common.Components.LottieDebugPanel animationData={JSON.parse(fieldState.blobText)} />
108+
<Common.Components.LottieDebugPanel data={JSON.parse(fieldState.blobText)} />
109109
</Box>
110110
)}
111111
<a href={value as string}>링크</a>

apps/pyconkr-admin/src/consts/mdx_components.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ const MUIMDXComponents: MDXComponents = {
131131
};
132132

133133
const PyConKRCommonMDXComponents: MDXComponents = {
134+
Common__Components__Lottie: Common.Components.LottiePlayer,
135+
Common__Components__NetworkLottie: Common.Components.NetworkLottiePlayer,
134136
Common__Components__MDX__PrimaryStyledDetails: Common.Components.MDX.PrimaryStyledDetails,
135137
Common__Components__MDX__SecondaryStyledDetails: Common.Components.MDX.SecondaryStyledDetails,
136138
Common__Components__MDX__Map: Common.Components.MDX.Map,

apps/pyconkr/src/consts/mdx_components.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ const MUIMDXComponents: MDXComponents = {
131131
};
132132

133133
const PyConKRCommonMDXComponents: MDXComponents = {
134+
Common__Components__Lottie: Common.Components.LottiePlayer,
135+
Common__Components__NetworkLottie: Common.Components.NetworkLottiePlayer,
134136
Common__Components__MDX__PrimaryStyledDetails: Common.Components.MDX.PrimaryStyledDetails,
135137
Common__Components__MDX__SecondaryStyledDetails: Common.Components.MDX.SecondaryStyledDetails,
136138
Common__Components__MDX__Map: Common.Components.MDX.Map,

packages/common/src/components/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@ import {
66
RouteRenderer as RouteRendererComponent,
77
} from "./dynamic_route";
88
import { ErrorFallback as ErrorFallbackComponent } from "./error_handler";
9-
import { LottieDebugPanel as LottieDebugPanelComponent } from "./lottie";
9+
import {
10+
LottieDebugPanel as LottieDebugPanelComponent,
11+
LottiePlayer as LottiePlayerComponent,
12+
NetworkLottiePlayer as NetworkLottiePlayerComponent,
13+
} from "./lottie";
1014
import { MDXRenderer as MDXRendererComponent } from "./mdx";
1115
import type { MapPropType as MapComponentPropType } from "./mdx_components/map";
1216
import { Map as MapComponent } from "./mdx_components/map";
@@ -27,6 +31,8 @@ namespace Components {
2731
export const MDXRenderer = MDXRendererComponent;
2832
export const PythonKorea = PythonKoreaComponent;
2933
export const LottieDebugPanel = LottieDebugPanelComponent;
34+
export const LottiePlayer = LottiePlayerComponent;
35+
export const NetworkLottiePlayer = NetworkLottiePlayerComponent;
3036
export const ErrorFallback = ErrorFallbackComponent;
3137

3238
export namespace MDX {
Lines changed: 88 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,120 @@
11
import { Pause, PlayArrow, Stop } from "@mui/icons-material";
2-
import { Box, FormControlLabel, IconButton, Stack, Switch } from "@mui/material";
2+
import { Box, CircularProgress, FormControlLabel, IconButton, Stack, Switch } from "@mui/material";
3+
import { ErrorBoundary } from "@suspensive/react";
34
import * as React from "react";
4-
import Lottie from "react-lottie";
5+
import Lottie, { Options } from "react-lottie";
6+
7+
import { ErrorFallback } from "./error_handler";
8+
import { isValidHttpUrl } from "../utils/string";
59

610
type PlayState = "playing" | "paused" | "stopped";
711

12+
type LottiePlayerProps = {
13+
data: unknown;
14+
playState?: PlayState;
15+
disableLoop?: boolean;
16+
renderSettings?: Options["rendererSettings"];
17+
style?: React.CSSProperties;
18+
};
19+
820
type LottiePlayerStateType = {
21+
playState: PlayState;
22+
};
23+
24+
type LottieDebugPanelStateType = LottiePlayerStateType & {
925
loop: boolean;
10-
isStopped: boolean;
11-
isPaused: boolean;
1226
};
1327

14-
export const LottieDebugPanel: React.FC<{ animationData: unknown }> = ({ animationData }) => {
15-
const [playerState, setPlayerState] = React.useState<LottiePlayerStateType>({
16-
loop: true,
17-
isStopped: false,
18-
isPaused: false,
28+
const playStateToLottiePlayerState = (playState: PlayState): { isStopped: boolean; isPaused: boolean } => {
29+
if (playState === "playing") return { isStopped: false, isPaused: false };
30+
if (playState === "paused") return { isStopped: false, isPaused: true };
31+
return { isStopped: true, isPaused: true };
32+
};
33+
34+
export const LottieDebugPanel: React.FC<LottiePlayerProps> = ({
35+
data,
36+
playState = "playing",
37+
disableLoop = false,
38+
renderSettings = {},
39+
style,
40+
}) => {
41+
const [playerState, setPlayerState] = React.useState<LottieDebugPanelStateType>({
42+
playState,
43+
loop: !disableLoop,
1944
});
45+
const isPlaying = playerState.playState === "playing";
2046

2147
const toggleLoop = () => setPlayerState((ps) => ({ ...ps, loop: !ps.loop }));
22-
const setPlayState = (playState: PlayState) => {
23-
if (playState === "playing") setPlayerState((ps) => ({ ...ps, isStopped: false, isPaused: false }));
24-
if (playState === "paused") setPlayerState((ps) => ({ ...ps, isStopped: false, isPaused: true }));
25-
if (playState === "stopped") setPlayerState((ps) => ({ ...ps, isStopped: true, isPaused: true }));
26-
};
48+
const setPlayState = (playState: PlayState) => setPlayerState((ps) => ({ ...ps, playState }));
2749

2850
const stop = () => setPlayState("stopped");
29-
const togglePause = () => setPlayState(playerState.isPaused ? "playing" : "paused");
51+
const togglePause = () => setPlayState(!isPlaying ? "playing" : "paused");
3052

3153
return (
3254
<Stack direction="column">
3355
<Box>
3456
<Lottie
35-
isStopped={playerState.isStopped}
36-
isPaused={playerState.isPaused}
57+
{...playStateToLottiePlayerState(playerState.playState)}
3758
options={{
38-
animationData,
59+
animationData: data,
3960
loop: playerState.loop,
4061
autoplay: true,
41-
rendererSettings: { preserveAspectRatio: "xMidYMid slice" },
62+
rendererSettings: { preserveAspectRatio: "xMidYMid slice", ...renderSettings },
4263
}}
64+
style={style}
4365
/>
4466
</Box>
4567
<Stack direction="row" spacing={2}>
46-
<IconButton onClick={togglePause} children={playerState.isPaused ? <PlayArrow /> : <Pause />} />
68+
<IconButton onClick={togglePause} children={!isPlaying ? <PlayArrow /> : <Pause />} />
4769
<IconButton onClick={stop} children={<Stop />} />
4870
<FormControlLabel control={<Switch checked={playerState.loop} onChange={toggleLoop} />} label="반복 재생" />
4971
</Stack>
5072
</Stack>
5173
);
5274
};
75+
76+
export const LottiePlayer: React.FC<LottiePlayerProps> = ({
77+
data,
78+
playState = "playing",
79+
disableLoop = false,
80+
renderSettings = {},
81+
style,
82+
}) => (
83+
<Lottie
84+
{...playStateToLottiePlayerState(playState)}
85+
options={{
86+
animationData: data,
87+
loop: !disableLoop,
88+
autoplay: playState === "playing",
89+
rendererSettings: { preserveAspectRatio: "xMidYMid slice", ...renderSettings },
90+
}}
91+
style={style}
92+
/>
93+
);
94+
95+
type NetworkLottiePlayerProps = Omit<LottiePlayerProps, "data"> & {
96+
url: string;
97+
fetchOptions?: RequestInit;
98+
};
99+
100+
type NetworkLottiePlayerStateType = {
101+
data?: unknown | null;
102+
};
103+
104+
export const NetworkLottiePlayer: React.FC<NetworkLottiePlayerProps> = ErrorBoundary.with(
105+
{ fallback: ErrorFallback },
106+
(props) => {
107+
const [playerState, setPlayerState] = React.useState<NetworkLottiePlayerStateType>({});
108+
109+
React.useEffect(() => {
110+
(async () => {
111+
if (!isValidHttpUrl(props.url)) throw new Error("Invalid URL for NetworkLottiePlayer: " + props.url);
112+
113+
const data = JSON.parse(await (await fetch(props.url, props.fetchOptions)).text());
114+
setPlayerState((ps) => ({ ...ps, data }));
115+
})();
116+
}, [props.url, props.fetchOptions]);
117+
118+
return playerState.data === undefined ? <CircularProgress /> : <LottiePlayer {...props} data={playerState.data} />;
119+
}
120+
);

0 commit comments

Comments
 (0)