reacthub-pixitweenjs 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.storybook/{main.js → main.ts} +9 -1
- package/.storybook/{preview.jsx → preview.tsx} +3 -7
- package/CHANGELOG.md +26 -0
- package/README.md +21 -2
- package/build-storybook.log +491 -0
- package/index.html +1 -1
- package/package.json +54 -19
- package/src/App.tsx +29 -0
- package/src/{stories/PixiImageSprite/PixiImageSprite.stories.js → components/PixiImageSprite/PixiImageSprite.stories.tsx} +25 -8
- package/src/components/PixiImageSprite/PixiImageSprite.tsx +346 -0
- package/src/components/PixiImageSprite/index.ts +1 -0
- package/src/hooks/usePixiTransformer.tsx +168 -0
- package/src/index.ts +3 -0
- package/src/providers/{GsapPixieContextProvider.jsx → GsapPixieContextProvider.tsx} +27 -24
- package/src/providers/index.ts +1 -0
- package/src/types/transformation.tsx +13 -0
- package/src/utils/AppStateProvider.tsx +70 -0
- package/src/utils/AppStateStage.tsx +27 -0
- package/src/utils/AppWrapper.tsx +43 -0
- package/src/utils/ContextBridge.tsx +25 -0
- package/src/{providers/GsapAnim.jsx → utils/GsapAnim.tsx} +35 -4
- package/src/utils/PixiStage.tsx +33 -0
- package/src/utils/VideoSeekBar.tsx +118 -0
- package/src/utils/useDeepEffect.tsx +23 -0
- package/src/vite-env.d.ts +1 -0
- package/tsconfig.json +37 -0
- package/tsconfig.node.json +10 -0
- package/{vite.config.js → vite.config.ts} +4 -2
- package/vitest.config.ts +11 -0
- package/src/App.jsx +0 -35
- package/src/index.js +0 -3
- package/src/providers/ContextBridge.jsx +0 -67
- package/src/providers/VideoSeekBar.jsx +0 -186
- package/src/providers/index.js +0 -3
- package/src/stories/PixiImageSprite/PixiImageSprite.jsx +0 -318
- package/src/stories/PixiImageSprite/index.js +0 -1
- /package/src/{main.jsx → main.tsx} +0 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { Transformer } from "@pixi-essentials/transformer";
|
|
2
|
+
import * as PIXI from "pixi.js";
|
|
3
|
+
import { useApp } from "@pixi/react";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { useEffect, useRef, useCallback } from "react";
|
|
6
|
+
import { Transformation } from "../types/transformation";
|
|
7
|
+
// @ts-ignore
|
|
8
|
+
import has from "lodash/has";
|
|
9
|
+
|
|
10
|
+
const colorYellow = PIXI.utils.string2hex("#F3C409");
|
|
11
|
+
const anchorFill = PIXI.utils.string2hex("#F3C409");
|
|
12
|
+
|
|
13
|
+
interface Events {
|
|
14
|
+
dblclick?: (e: any) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
uniqueId?: string;
|
|
19
|
+
onTransformEnd?: (data: {
|
|
20
|
+
uniqueId: string;
|
|
21
|
+
transformation: Transformation;
|
|
22
|
+
}) => void;
|
|
23
|
+
events?: Events;
|
|
24
|
+
mouseoverEvent?: (flag: boolean) => void;
|
|
25
|
+
setDragging?: (flag: boolean) => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const usePixiTransformer = ({
|
|
29
|
+
uniqueId = "",
|
|
30
|
+
onTransformEnd,
|
|
31
|
+
events = {},
|
|
32
|
+
mouseoverEvent = (f) => f,
|
|
33
|
+
setDragging = (f) => f,
|
|
34
|
+
}: Props) => {
|
|
35
|
+
const [isDragging, setIsDragging] = React.useState(false);
|
|
36
|
+
const PixiTransformer = React.useRef<PIXI.Container>(null);
|
|
37
|
+
const mounted = React.useRef(false);
|
|
38
|
+
const app = useApp();
|
|
39
|
+
const transformerRef = useRef<Transformer>(null);
|
|
40
|
+
const parentRef = useRef<PIXI.Container>(null);
|
|
41
|
+
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
mounted.current = true;
|
|
44
|
+
return () => {
|
|
45
|
+
mounted.current = false;
|
|
46
|
+
if (transformerRef.current) {
|
|
47
|
+
parentRef.current?.removeChild(transformerRef.current);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const transformChange = useCallback(() => {
|
|
53
|
+
setIsDragging(true);
|
|
54
|
+
setDragging(true);
|
|
55
|
+
}, []);
|
|
56
|
+
|
|
57
|
+
const transformCommit = useCallback(() => {
|
|
58
|
+
if (PixiTransformer.current === null) return;
|
|
59
|
+
const x = PixiTransformer.current.x;
|
|
60
|
+
const y = PixiTransformer.current.y;
|
|
61
|
+
const width = PixiTransformer.current.width;
|
|
62
|
+
const height = PixiTransformer.current.height;
|
|
63
|
+
const rotate = PixiTransformer.current.rotation;
|
|
64
|
+
const scale: [number, number] = [
|
|
65
|
+
PixiTransformer.current.scale.x,
|
|
66
|
+
PixiTransformer.current.scale.y,
|
|
67
|
+
];
|
|
68
|
+
if (onTransformEnd) {
|
|
69
|
+
onTransformEnd({
|
|
70
|
+
uniqueId,
|
|
71
|
+
transformation: {
|
|
72
|
+
x: Math.round(x),
|
|
73
|
+
y: Math.round(y),
|
|
74
|
+
width: Math.round(Math.max(5, width)),
|
|
75
|
+
height: Math.round(Math.max(5, height)),
|
|
76
|
+
rotate: Math.round(rotate),
|
|
77
|
+
scale: scale,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
setIsDragging(false);
|
|
82
|
+
setDragging(false);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const handleOnDblClick = useCallback((e: any) => {
|
|
86
|
+
if (events && has(events, "dblclick") && events.dblclick) {
|
|
87
|
+
events.dblclick(e);
|
|
88
|
+
}
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
const handleOnMouseOver = useCallback(
|
|
92
|
+
(msevent: any) => {
|
|
93
|
+
if (mouseoverEvent) {
|
|
94
|
+
mouseoverEvent(true);
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
[mouseoverEvent],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const handleOnMouseOut = useCallback(
|
|
101
|
+
(msevent: any) => {
|
|
102
|
+
if (mouseoverEvent) {
|
|
103
|
+
mouseoverEvent(false);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
[mouseoverEvent],
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const handleTransformer = useCallback(
|
|
110
|
+
(node: PIXI.Container, parentNode: PIXI.Container) => {
|
|
111
|
+
const isEnabled = true;
|
|
112
|
+
// @ts-ignore
|
|
113
|
+
PixiTransformer.current = node;
|
|
114
|
+
const transformer = new Transformer({
|
|
115
|
+
rotateEnabled: isEnabled,
|
|
116
|
+
boxRotationEnabled: isEnabled,
|
|
117
|
+
boxScalingEnabled: isEnabled,
|
|
118
|
+
scaleEnabled: isEnabled,
|
|
119
|
+
group: [node],
|
|
120
|
+
wireframeStyle: {
|
|
121
|
+
thickness: 2,
|
|
122
|
+
color: anchorFill,
|
|
123
|
+
},
|
|
124
|
+
lockAspectRatio: true,
|
|
125
|
+
handleStyle: {
|
|
126
|
+
// color: colorYellow,
|
|
127
|
+
outlineColor: colorYellow,
|
|
128
|
+
shape: "circle",
|
|
129
|
+
radius: 10,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// transformer.addListener("transformchange", transformChange);
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
transformer.addListener("transformcommit", transformCommit);
|
|
136
|
+
// transformer.addListener("dblclick", (evt) => {
|
|
137
|
+
// handleOnDblClick(evt);
|
|
138
|
+
// });
|
|
139
|
+
// transformer.on("mouseover", () => {
|
|
140
|
+
// handleOnMouseOver(true);
|
|
141
|
+
// });
|
|
142
|
+
// transformer.on("mouseout", () => {
|
|
143
|
+
// handleOnMouseOut(false);
|
|
144
|
+
// });
|
|
145
|
+
// @ts-ignore
|
|
146
|
+
transformerRef.current = transformer;
|
|
147
|
+
// @ts-ignore
|
|
148
|
+
parentRef.current = parentNode;
|
|
149
|
+
parentRef.current.addChild(transformerRef.current);
|
|
150
|
+
},
|
|
151
|
+
[
|
|
152
|
+
transformCommit,
|
|
153
|
+
transformChange,
|
|
154
|
+
handleOnMouseOver,
|
|
155
|
+
handleOnMouseOut,
|
|
156
|
+
handleOnDblClick,
|
|
157
|
+
],
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
const removeTransformer = useCallback(() => {
|
|
161
|
+
if (transformerRef.current) {
|
|
162
|
+
// @ts-ignore
|
|
163
|
+
parentRef.current.removeChild(transformerRef.current);
|
|
164
|
+
}
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
return { handleTransformer, removeTransformer, isDragging };
|
|
168
|
+
};
|
package/src/index.ts
ADDED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
2
3
|
useState,
|
|
3
4
|
useEffect,
|
|
4
5
|
useCallback,
|
|
@@ -7,10 +8,22 @@ import React, {
|
|
|
7
8
|
} from "react";
|
|
8
9
|
import gsap from "gsap";
|
|
9
10
|
import { emitCustomEvent } from "react-custom-events";
|
|
10
|
-
|
|
11
|
+
|
|
12
|
+
interface GsapPixieContextProps {
|
|
13
|
+
gsapCtx: React.MutableRefObject<any>;
|
|
14
|
+
tl: React.MutableRefObject<any>;
|
|
15
|
+
handlePlay: () => void;
|
|
16
|
+
handlePause: () => void;
|
|
17
|
+
handleReset: () => void;
|
|
18
|
+
handleSeek: () => void;
|
|
19
|
+
handleRestart: () => void;
|
|
20
|
+
handleRepeat: () => void;
|
|
21
|
+
playerTimeRef: React.MutableRefObject<number>;
|
|
22
|
+
}
|
|
11
23
|
|
|
12
24
|
// Context has been created
|
|
13
|
-
|
|
25
|
+
// @ts-ignore
|
|
26
|
+
const GsapPixieContext = React.createContext<GsapPixieContextProps>({});
|
|
14
27
|
|
|
15
28
|
const Events = {
|
|
16
29
|
STOP: "GSAP_STOP",
|
|
@@ -18,21 +31,19 @@ const Events = {
|
|
|
18
31
|
PLAY: "GSAP_PLAY",
|
|
19
32
|
RESUME: "GSAP_RESUME",
|
|
20
33
|
};
|
|
21
|
-
// Provider
|
|
22
|
-
const GsapPixieContextProvider = ({ children }) => {
|
|
23
|
-
const [play, setPlay] = useState(false);
|
|
24
|
-
const [frameNumber, setFrameNumber] = useState(0);
|
|
25
34
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
// Provider
|
|
36
|
+
const GsapPixieContextProvider: React.FC<{ children: React.ReactNode }> = ({
|
|
37
|
+
children,
|
|
38
|
+
}) => {
|
|
39
|
+
const [play, setPlay] = useState<boolean>(false);
|
|
40
|
+
const [frameNumber, setFrameNumber] = useState<number>(0);
|
|
29
41
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
// });
|
|
42
|
+
const tl = useRef<any>();
|
|
43
|
+
const gsapCtx = useRef<any>();
|
|
44
|
+
const playerTimeRef = useRef<number>(0.001);
|
|
34
45
|
|
|
35
|
-
const parentElementRef = useRef();
|
|
46
|
+
const parentElementRef = useRef<any>();
|
|
36
47
|
// передаем предка анимируемых элементов
|
|
37
48
|
const q = gsap.utils.selector(parentElementRef);
|
|
38
49
|
|
|
@@ -62,14 +73,6 @@ const GsapPixieContextProvider = ({ children }) => {
|
|
|
62
73
|
playerTimeRef.current = now;
|
|
63
74
|
}, []);
|
|
64
75
|
|
|
65
|
-
// const timeline = React.useCallback(
|
|
66
|
-
// gsap.timeline({
|
|
67
|
-
// paused: true,
|
|
68
|
-
// defaults: { duration: 0, onUpdate: onUpdate },
|
|
69
|
-
// }),
|
|
70
|
-
// [],
|
|
71
|
-
// );
|
|
72
|
-
|
|
73
76
|
useEffect(() => {
|
|
74
77
|
const timeline = tl.current;
|
|
75
78
|
timeline
|
|
@@ -93,7 +96,7 @@ const GsapPixieContextProvider = ({ children }) => {
|
|
|
93
96
|
}
|
|
94
97
|
}, [play]);
|
|
95
98
|
|
|
96
|
-
const addTotalDuration = (duration) => {
|
|
99
|
+
const addTotalDuration = (duration: number) => {
|
|
97
100
|
const timeline = tl.current;
|
|
98
101
|
timeline.totalDuration(duration);
|
|
99
102
|
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./GsapPixieContextProvider";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import useDeepEffect from "./useDeepEffect";
|
|
3
|
+
import { TransformationEnd } from "../types/transformation";
|
|
4
|
+
|
|
5
|
+
interface AppState {
|
|
6
|
+
transformation: {
|
|
7
|
+
[key: string]: unknown;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface AppStateContextProps extends AppState {
|
|
12
|
+
onAnchorTransformationEnd: (data: TransformationEnd) => void;
|
|
13
|
+
onMouseOverSprite: (data: unknown) => void;
|
|
14
|
+
onClickSprite: (data: unknown) => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const AppStateContext = React.createContext<AppStateContextProps>({
|
|
18
|
+
transformation: {},
|
|
19
|
+
onAnchorTransformationEnd: () => {},
|
|
20
|
+
onMouseOverSprite: () => {},
|
|
21
|
+
onClickSprite: () => {},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
interface AppStateContextProviderProps extends AppState {
|
|
25
|
+
children: React.ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const AppStateContextProvider: React.FC<
|
|
29
|
+
AppStateContextProviderProps
|
|
30
|
+
> = ({ children, ...props }) => {
|
|
31
|
+
const [appState, setAppState] = React.useState<AppState>(props);
|
|
32
|
+
useDeepEffect(() => {
|
|
33
|
+
setAppState(props);
|
|
34
|
+
}, [props]);
|
|
35
|
+
|
|
36
|
+
const onAnchorTransformationEnd = React.useCallback(
|
|
37
|
+
(data: {
|
|
38
|
+
uniqueId: string;
|
|
39
|
+
transformation: { [key: string]: unknown };
|
|
40
|
+
}) => {
|
|
41
|
+
const { uniqueId, transformation } = data;
|
|
42
|
+
const newAppState = {
|
|
43
|
+
...props,
|
|
44
|
+
transformation: {
|
|
45
|
+
...props.transformation,
|
|
46
|
+
...transformation,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
setAppState(newAppState);
|
|
50
|
+
},
|
|
51
|
+
[props],
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
const onMouseOverSprite = React.useCallback((data: unknown) => {}, []);
|
|
55
|
+
|
|
56
|
+
const onClickSprite = React.useCallback((data: unknown) => {}, []);
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<AppStateContext.Provider
|
|
60
|
+
value={{
|
|
61
|
+
...appState,
|
|
62
|
+
onAnchorTransformationEnd,
|
|
63
|
+
onMouseOverSprite,
|
|
64
|
+
onClickSprite,
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
</AppStateContext.Provider>
|
|
69
|
+
);
|
|
70
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import ContextBridge from "./ContextBridge";
|
|
3
|
+
import { AppStateContext } from "../providers/AppStateProvider";
|
|
4
|
+
import { Container as PixiContainer } from "@pixi/react";
|
|
5
|
+
|
|
6
|
+
interface AppStateStageProps {
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const AppStateStage: React.FC<AppStateStageProps> = ({
|
|
11
|
+
children,
|
|
12
|
+
...props
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<ContextBridge
|
|
16
|
+
Context={AppStateContext}
|
|
17
|
+
render={(children: React.ReactNode) => (
|
|
18
|
+
// @ts-ignore
|
|
19
|
+
<PixiContainer {...props}>{children}</PixiContainer>
|
|
20
|
+
)}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</ContextBridge>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export default AppStateStage;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { Flex, Box } from "@chakra-ui/react";
|
|
3
|
+
import * as PIXI from "pixi.js";
|
|
4
|
+
import { ChakraProvider } from "@chakra-ui/react";
|
|
5
|
+
import { GsapPixieContextProvider } from "../providers/GsapPixieContextProvider";
|
|
6
|
+
import { VideoSeekBar } from "./VideoSeekBar";
|
|
7
|
+
import Stage from "./PixiStage";
|
|
8
|
+
|
|
9
|
+
interface AppProps {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
backgroundColor?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const AppWrapper: React.FC<AppProps> = ({
|
|
15
|
+
children,
|
|
16
|
+
backgroundColor,
|
|
17
|
+
}) => {
|
|
18
|
+
const backgroundColorx = PIXI.utils.string2hex(backgroundColor || "#2D2E3C");
|
|
19
|
+
const width = 600;
|
|
20
|
+
const height = 500;
|
|
21
|
+
/// color: 0x1099bb,
|
|
22
|
+
return (
|
|
23
|
+
<div className="App">
|
|
24
|
+
<GsapPixieContextProvider>
|
|
25
|
+
<ChakraProvider>
|
|
26
|
+
<Flex flexDir={"column"}>
|
|
27
|
+
<Stage
|
|
28
|
+
width={width}
|
|
29
|
+
height={height}
|
|
30
|
+
// options={{ background: backgroundColor }}
|
|
31
|
+
options={{ backgroundColor: backgroundColorx, resolution: 2 }}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</Stage>
|
|
35
|
+
<Box mt={5} w={600}>
|
|
36
|
+
<VideoSeekBar />
|
|
37
|
+
</Box>
|
|
38
|
+
</Flex>
|
|
39
|
+
</ChakraProvider>
|
|
40
|
+
</GsapPixieContextProvider>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// your Stage:
|
|
2
|
+
// the context bridge:
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
|
|
5
|
+
const ContextBridge = <T,>({
|
|
6
|
+
children,
|
|
7
|
+
Context,
|
|
8
|
+
render,
|
|
9
|
+
}: {
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
Context: React.Context<T>;
|
|
12
|
+
render: (children: React.ReactNode) => JSX.Element;
|
|
13
|
+
}) => {
|
|
14
|
+
return (
|
|
15
|
+
<Context.Consumer>
|
|
16
|
+
{(value) => {
|
|
17
|
+
return render(
|
|
18
|
+
<Context.Provider value={value}>{children}</Context.Provider>,
|
|
19
|
+
);
|
|
20
|
+
}}
|
|
21
|
+
</Context.Consumer>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default ContextBridge;
|
|
@@ -1,8 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
interface Ease {
|
|
2
|
+
from?: {
|
|
3
|
+
duration: number;
|
|
4
|
+
ease: string;
|
|
5
|
+
y?: number;
|
|
6
|
+
x?: number;
|
|
7
|
+
};
|
|
8
|
+
fromTo?: {
|
|
9
|
+
from: {
|
|
10
|
+
y?: number;
|
|
11
|
+
x?: number;
|
|
12
|
+
alpha?: number;
|
|
13
|
+
};
|
|
14
|
+
to: {
|
|
15
|
+
ease: string;
|
|
16
|
+
y?: number;
|
|
17
|
+
x?: number;
|
|
18
|
+
alpha?: number;
|
|
19
|
+
duration: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
to?: {
|
|
23
|
+
ease: string;
|
|
24
|
+
alpha: number;
|
|
25
|
+
duration: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
3
28
|
|
|
4
|
-
export const getAnimByName = (
|
|
5
|
-
|
|
29
|
+
export const getAnimByName = (
|
|
30
|
+
animate: string,
|
|
31
|
+
stageWidth?: number,
|
|
32
|
+
stageHeight?: number,
|
|
33
|
+
x?: number,
|
|
34
|
+
y?: number,
|
|
35
|
+
): Ease => {
|
|
36
|
+
let ease: Ease = {};
|
|
6
37
|
switch (animate) {
|
|
7
38
|
case "NONE":
|
|
8
39
|
case "none":
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import ContextBridge from "./ContextBridge";
|
|
3
|
+
import { Stage as PixiStage } from "@pixi/react";
|
|
4
|
+
import { AppStateContextProvider } from "./AppStateProvider";
|
|
5
|
+
import { GsapPixieContext } from "../providers/GsapPixieContextProvider";
|
|
6
|
+
|
|
7
|
+
interface StageProps {
|
|
8
|
+
children: React.ReactNode;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
options: any;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const Stage: React.FC<StageProps> = ({ children, ...props }) => {
|
|
15
|
+
return (
|
|
16
|
+
<ContextBridge
|
|
17
|
+
Context={GsapPixieContext}
|
|
18
|
+
render={(children: React.ReactNode | React.ReactElement) => (
|
|
19
|
+
<PixiStage {...props}>
|
|
20
|
+
{/* @ts-ignore */}
|
|
21
|
+
<AppStateContextProvider {...props}>
|
|
22
|
+
{/* @ts-ignore */}
|
|
23
|
+
{children}
|
|
24
|
+
</AppStateContextProvider>
|
|
25
|
+
</PixiStage>
|
|
26
|
+
)}
|
|
27
|
+
>
|
|
28
|
+
{children}
|
|
29
|
+
</ContextBridge>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export default Stage;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {
|
|
3
|
+
Slider,
|
|
4
|
+
SliderTrack,
|
|
5
|
+
SliderFilledTrack,
|
|
6
|
+
SliderThumb,
|
|
7
|
+
SliderMark,
|
|
8
|
+
Flex,
|
|
9
|
+
Icon,
|
|
10
|
+
Text,
|
|
11
|
+
Box,
|
|
12
|
+
Button,
|
|
13
|
+
} from "@chakra-ui/react";
|
|
14
|
+
import { GsapPixieContext } from "../providers/GsapPixieContextProvider";
|
|
15
|
+
|
|
16
|
+
import { gsap } from "gsap";
|
|
17
|
+
import { Draggable } from "gsap/Draggable";
|
|
18
|
+
|
|
19
|
+
gsap.registerPlugin(Draggable);
|
|
20
|
+
|
|
21
|
+
export const VideoSeekBar: React.FC = () => {
|
|
22
|
+
const rafRef = React.useRef<number>();
|
|
23
|
+
const sliderRef = React.useRef<HTMLDivElement>(null);
|
|
24
|
+
const sliderThumbRef = React.useRef<HTMLDivElement>(null);
|
|
25
|
+
const sliderProgressRef = React.useRef<HTMLDivElement>(null);
|
|
26
|
+
const {
|
|
27
|
+
tl: timeline,
|
|
28
|
+
playerTimeRef,
|
|
29
|
+
handlePlay,
|
|
30
|
+
handlePause,
|
|
31
|
+
handleReset,
|
|
32
|
+
handleSeek,
|
|
33
|
+
handleRestart,
|
|
34
|
+
handleRepeat,
|
|
35
|
+
} = React.useContext(GsapPixieContext);
|
|
36
|
+
|
|
37
|
+
/// state
|
|
38
|
+
const [time, setTime] = React.useState<number>(0);
|
|
39
|
+
const [duration, setDuration] = React.useState<number>(0);
|
|
40
|
+
|
|
41
|
+
const totalVideoSeconds = 20;
|
|
42
|
+
|
|
43
|
+
const animate = (time: number) => {
|
|
44
|
+
/// console.log("totalDuration", timeline.current.totalDuration());
|
|
45
|
+
setDuration(timeline.current.totalDuration());
|
|
46
|
+
if (playerTimeRef && playerTimeRef.current) {
|
|
47
|
+
setTime(playerTimeRef.current);
|
|
48
|
+
}
|
|
49
|
+
rafRef.current = requestAnimationFrame(animate);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
rafRef.current = requestAnimationFrame(animate);
|
|
54
|
+
return () => cancelAnimationFrame(rafRef.current!);
|
|
55
|
+
}, []); // Make sure the effect runs only once
|
|
56
|
+
|
|
57
|
+
const handleOnSliderChange = (value: number) => {
|
|
58
|
+
const nTotSeconds =
|
|
59
|
+
totalVideoSeconds && totalVideoSeconds > 0 ? totalVideoSeconds : 1;
|
|
60
|
+
const progress = value / nTotSeconds;
|
|
61
|
+
console.log("progressbar", value, progress);
|
|
62
|
+
playerTimeRef.current = value;
|
|
63
|
+
// @ts-ignore
|
|
64
|
+
timeline && timeline.progress(progress);
|
|
65
|
+
gsap.set(sliderProgressRef.current!, {
|
|
66
|
+
scaleX: progress,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
|
+
const handleOnSliderChangeStart = (value: number) => {};
|
|
70
|
+
const handleOnSliderChangeEnd = (value: number) => {};
|
|
71
|
+
|
|
72
|
+
/** Create Draggable for the Gsap Player */
|
|
73
|
+
|
|
74
|
+
const updateDrag = React.useCallback(
|
|
75
|
+
(x: number, maxX: number) => {
|
|
76
|
+
console.log("updatgeDrag running", x, maxX);
|
|
77
|
+
const currentTime = (x / maxX) * totalVideoSeconds;
|
|
78
|
+
const prog = x / sliderRef.current!.offsetWidth;
|
|
79
|
+
console.log("drage update", currentTime, prog);
|
|
80
|
+
},
|
|
81
|
+
[totalVideoSeconds],
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<Flex flexDir={"column"}>
|
|
86
|
+
<Box>
|
|
87
|
+
<Button onClick={handleRestart}>Play</Button>
|
|
88
|
+
<Button onClick={handlePause}>Pause</Button>
|
|
89
|
+
<Button onClick={handlePlay}>Resume</Button>
|
|
90
|
+
<Button onClick={handleSeek}>Seek (4:00)</Button>
|
|
91
|
+
</Box>
|
|
92
|
+
<Flex px={1} mt={2}>
|
|
93
|
+
<Slider
|
|
94
|
+
aria-label="slider-ex-1"
|
|
95
|
+
data-testid="sliderthumb"
|
|
96
|
+
value={playerTimeRef.current || 0}
|
|
97
|
+
min={0}
|
|
98
|
+
max={duration}
|
|
99
|
+
step={0.1}
|
|
100
|
+
onChange={handleOnSliderChange}
|
|
101
|
+
onChangeStart={handleOnSliderChangeStart}
|
|
102
|
+
onChangeEnd={handleOnSliderChangeEnd}
|
|
103
|
+
>
|
|
104
|
+
<SliderTrack>
|
|
105
|
+
<SliderFilledTrack />
|
|
106
|
+
</SliderTrack>
|
|
107
|
+
<SliderThumb data-testid="sliderthumb" ref={sliderThumbRef} />
|
|
108
|
+
</Slider>
|
|
109
|
+
</Flex>
|
|
110
|
+
<Flex flexDir={"row"}>
|
|
111
|
+
<Text>
|
|
112
|
+
Gsap-CurrentTime: {Math.round((time + Number.EPSILON) * 100) / 100} /{" "}
|
|
113
|
+
{duration}
|
|
114
|
+
</Text>
|
|
115
|
+
</Flex>
|
|
116
|
+
</Flex>
|
|
117
|
+
);
|
|
118
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { useEffect, useRef } from "react";
|
|
2
|
+
// @ts-ignore
|
|
3
|
+
import isEqual from "react-fast-compare";
|
|
4
|
+
|
|
5
|
+
type EffectFunc = () => void;
|
|
6
|
+
type Deps = ReadonlyArray<unknown>;
|
|
7
|
+
|
|
8
|
+
const useDeepEffect = (effectFunc: EffectFunc, deps: Deps) => {
|
|
9
|
+
const isFirst = useRef(true);
|
|
10
|
+
const prevDeps = useRef(deps);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const isSame = prevDeps.current.every((obj, index) =>
|
|
13
|
+
isEqual(obj, deps[index]),
|
|
14
|
+
);
|
|
15
|
+
if (isFirst.current || !isSame) {
|
|
16
|
+
effectFunc();
|
|
17
|
+
}
|
|
18
|
+
isFirst.current = false;
|
|
19
|
+
prevDeps.current = deps;
|
|
20
|
+
}, deps);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default useDeepEffect;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="vite/client" />
|