@umituz/react-native-mascot 1.0.1
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/LICENSE +21 -0
- package/README.md +370 -0
- package/package.json +89 -0
- package/skills/SKILL.md +188 -0
- package/src/assets/index.ts +104 -0
- package/src/domain/entities/Mascot.ts +216 -0
- package/src/domain/interfaces/IAnimationController.ts +78 -0
- package/src/domain/interfaces/IAssetManager.ts +51 -0
- package/src/domain/interfaces/IMascotRepository.ts +39 -0
- package/src/domain/types/MascotTypes.ts +75 -0
- package/src/index.ts +99 -0
- package/src/infrastructure/assets/lottie/dance.json +61 -0
- package/src/infrastructure/assets/lottie/error.json +48 -0
- package/src/infrastructure/assets/lottie/idle.json +49 -0
- package/src/infrastructure/assets/lottie/jump.json +48 -0
- package/src/infrastructure/assets/lottie/success.json +46 -0
- package/src/infrastructure/assets/lottie/wave.json +46 -0
- package/src/infrastructure/assets/svg/cartoon-bot.svg +20 -0
- package/src/infrastructure/assets/svg/minimal-cat.svg +19 -0
- package/src/infrastructure/controllers/AnimationController.ts +163 -0
- package/src/infrastructure/managers/AssetManager.ts +159 -0
- package/src/infrastructure/managers/MascotFactory.ts +239 -0
- package/src/infrastructure/repositories/MascotRepository.ts +93 -0
- package/src/presentation/components/MascotView.tsx +296 -0
- package/src/presentation/contexts/MascotContext.tsx +141 -0
- package/src/presentation/hooks/useMascot.ts +224 -0
- package/src/presentation/hooks/useMascotAnimation.ts +168 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mascot Context
|
|
3
|
+
* Provides mascot state and functionality to components
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { createContext, useContext, useState, useCallback, useRef } from 'react';
|
|
7
|
+
import { Mascot } from '../../domain/entities/Mascot';
|
|
8
|
+
import type { MascotConfig, MascotMood } from '../../domain/types/MascotTypes';
|
|
9
|
+
import { MascotFactory } from '../../infrastructure/managers/MascotFactory';
|
|
10
|
+
import { AnimationController } from '../../infrastructure/controllers/AnimationController';
|
|
11
|
+
import type { AnimationOptions } from '../../domain/interfaces/IAnimationController';
|
|
12
|
+
|
|
13
|
+
export interface MascotContextValue {
|
|
14
|
+
mascot: Mascot | null;
|
|
15
|
+
isPlaying: boolean;
|
|
16
|
+
currentAnimation: string | null;
|
|
17
|
+
initializeMascot: (config: MascotConfig) => void;
|
|
18
|
+
initializeFromTemplate: (template: string, customizations?: Partial<MascotConfig>) => void;
|
|
19
|
+
setMood: (mood: MascotMood) => void;
|
|
20
|
+
playAnimation: (animationId: string, options?: AnimationOptions) => Promise<void>;
|
|
21
|
+
stopAnimation: () => void;
|
|
22
|
+
updateAppearance: (appearance: Partial<MascotConfig['appearance']>) => void;
|
|
23
|
+
setVisible: (visible: boolean) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const MascotContext = createContext<MascotContextValue | undefined>(undefined);
|
|
27
|
+
|
|
28
|
+
export interface MascotProviderProps extends React.PropsWithChildren {
|
|
29
|
+
initialConfig?: MascotConfig;
|
|
30
|
+
template?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const MascotProvider: React.FC<MascotProviderProps> = ({
|
|
34
|
+
children,
|
|
35
|
+
initialConfig: _initialConfig,
|
|
36
|
+
template: _template,
|
|
37
|
+
}) => {
|
|
38
|
+
const [mascot, setMascot] = useState<Mascot | null>(null);
|
|
39
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
40
|
+
const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
|
|
41
|
+
const animationControllerRef = useRef<AnimationController | null>(null);
|
|
42
|
+
|
|
43
|
+
const initializeMascot = useCallback((config: MascotConfig) => {
|
|
44
|
+
const newMascot = new Mascot(config);
|
|
45
|
+
setMascot(newMascot);
|
|
46
|
+
if (!animationControllerRef.current) {
|
|
47
|
+
animationControllerRef.current = new AnimationController();
|
|
48
|
+
}
|
|
49
|
+
}, []);
|
|
50
|
+
|
|
51
|
+
const initializeFromTemplate = useCallback((
|
|
52
|
+
templateName: string,
|
|
53
|
+
customizations?: Partial<MascotConfig>
|
|
54
|
+
) => {
|
|
55
|
+
const template = templateName as 'friendly-bot' | 'cute-pet' | 'wise-owl' | 'pixel-hero';
|
|
56
|
+
const newMascot = MascotFactory.createFromTemplate(template, customizations);
|
|
57
|
+
setMascot(newMascot);
|
|
58
|
+
if (!animationControllerRef.current) {
|
|
59
|
+
animationControllerRef.current = new AnimationController();
|
|
60
|
+
}
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
const setMood = useCallback((mood: MascotMood) => {
|
|
64
|
+
setMascot((prev) => {
|
|
65
|
+
if (!prev) return null;
|
|
66
|
+
prev.setMood(mood);
|
|
67
|
+
return prev.clone();
|
|
68
|
+
});
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
const playAnimation = useCallback(async (animationId: string, options?: AnimationOptions) => {
|
|
72
|
+
if (!mascot || !animationControllerRef.current) return;
|
|
73
|
+
|
|
74
|
+
const animation = mascot.getAnimation(animationId);
|
|
75
|
+
if (!animation) {
|
|
76
|
+
console.warn(`Animation ${animationId} not found`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
setIsPlaying(true);
|
|
81
|
+
setCurrentAnimation(animationId);
|
|
82
|
+
|
|
83
|
+
if (animationControllerRef.current) {
|
|
84
|
+
await animationControllerRef.current.play(animation, options);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setIsPlaying(false);
|
|
88
|
+
setCurrentAnimation(null);
|
|
89
|
+
}, [mascot]);
|
|
90
|
+
|
|
91
|
+
const stopAnimation = useCallback(() => {
|
|
92
|
+
if (animationControllerRef.current) {
|
|
93
|
+
animationControllerRef.current.stop();
|
|
94
|
+
}
|
|
95
|
+
setIsPlaying(false);
|
|
96
|
+
setCurrentAnimation(null);
|
|
97
|
+
}, []);
|
|
98
|
+
|
|
99
|
+
const updateAppearance = useCallback((appearance: Partial<MascotConfig['appearance']>) => {
|
|
100
|
+
setMascot((prev) => {
|
|
101
|
+
if (!prev) return null;
|
|
102
|
+
prev.updateAppearance(appearance);
|
|
103
|
+
return prev.clone();
|
|
104
|
+
});
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
const setVisible = useCallback((visible: boolean) => {
|
|
108
|
+
setMascot((prev) => {
|
|
109
|
+
if (!prev) return null;
|
|
110
|
+
prev.setVisible(visible);
|
|
111
|
+
return prev.clone();
|
|
112
|
+
});
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
const value: MascotContextValue = {
|
|
116
|
+
mascot,
|
|
117
|
+
isPlaying,
|
|
118
|
+
currentAnimation,
|
|
119
|
+
initializeMascot,
|
|
120
|
+
initializeFromTemplate,
|
|
121
|
+
setMood,
|
|
122
|
+
playAnimation,
|
|
123
|
+
stopAnimation,
|
|
124
|
+
updateAppearance,
|
|
125
|
+
setVisible,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<MascotContext.Provider value={value}>
|
|
130
|
+
{children}
|
|
131
|
+
</MascotContext.Provider>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
export const useMascotContext = (): MascotContextValue => {
|
|
136
|
+
const context = useContext(MascotContext);
|
|
137
|
+
if (!context) {
|
|
138
|
+
throw new Error('useMascotContext must be used within a MascotProvider');
|
|
139
|
+
}
|
|
140
|
+
return context;
|
|
141
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMascot Hook
|
|
3
|
+
* Main hook for mascot management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useEffect, useState, useRef } from 'react';
|
|
7
|
+
import { Mascot } from '../../domain/entities/Mascot';
|
|
8
|
+
import type {
|
|
9
|
+
MascotConfig,
|
|
10
|
+
MascotMood,
|
|
11
|
+
MascotAppearance,
|
|
12
|
+
} from '../../domain/types/MascotTypes';
|
|
13
|
+
import { MascotFactory } from '../../infrastructure/managers/MascotFactory';
|
|
14
|
+
import { AnimationController } from '../../infrastructure/controllers/AnimationController';
|
|
15
|
+
import type { AnimationOptions } from '../../domain/interfaces/IAnimationController';
|
|
16
|
+
|
|
17
|
+
export interface UseMascotOptions {
|
|
18
|
+
config?: MascotConfig;
|
|
19
|
+
template?: string;
|
|
20
|
+
autoInitialize?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface UseMascotReturn {
|
|
24
|
+
mascot: Mascot | null;
|
|
25
|
+
isReady: boolean;
|
|
26
|
+
isPlaying: boolean;
|
|
27
|
+
currentAnimation: string | null;
|
|
28
|
+
initialize: (config: MascotConfig) => void;
|
|
29
|
+
initializeFromTemplate: (template: string, customizations?: Partial<MascotConfig>) => void;
|
|
30
|
+
setMood: (mood: MascotMood) => void;
|
|
31
|
+
setEnergy: (energy: number) => void;
|
|
32
|
+
playAnimation: (animationId: string, options?: AnimationOptions) => Promise<void>;
|
|
33
|
+
stopAnimation: () => void;
|
|
34
|
+
updateAppearance: (appearance: Partial<MascotAppearance>) => void;
|
|
35
|
+
setBaseColor: (color: string) => void;
|
|
36
|
+
setAccentColor: (color: string) => void;
|
|
37
|
+
addAccessory: (accessory: {
|
|
38
|
+
id: string;
|
|
39
|
+
type: string;
|
|
40
|
+
color?: string;
|
|
41
|
+
position?: { x: number; y: number };
|
|
42
|
+
}) => void;
|
|
43
|
+
removeAccessory: (accessoryId: string) => void;
|
|
44
|
+
setVisible: (visible: boolean) => void;
|
|
45
|
+
setPosition: (position: { x: number; y: number }) => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useMascot(options: UseMascotOptions = {}): UseMascotReturn {
|
|
49
|
+
const {
|
|
50
|
+
config: initialConfig,
|
|
51
|
+
template: initialTemplate,
|
|
52
|
+
autoInitialize = true,
|
|
53
|
+
} = options;
|
|
54
|
+
|
|
55
|
+
const [mascot, setMascot] = useState<Mascot | null>(null);
|
|
56
|
+
const [isReady, setIsReady] = useState(false);
|
|
57
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
58
|
+
const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
|
|
59
|
+
|
|
60
|
+
const animationControllerRef = useRef<AnimationController | null>(null);
|
|
61
|
+
|
|
62
|
+
// Initialize mascot
|
|
63
|
+
const initialize = useCallback((config: MascotConfig) => {
|
|
64
|
+
const newMascot = new Mascot(config);
|
|
65
|
+
setMascot(newMascot);
|
|
66
|
+
setIsReady(true);
|
|
67
|
+
if (!animationControllerRef.current) {
|
|
68
|
+
animationControllerRef.current = new AnimationController();
|
|
69
|
+
}
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
const initializeFromTemplate = useCallback((
|
|
73
|
+
template: string,
|
|
74
|
+
customizations?: Partial<MascotConfig>
|
|
75
|
+
) => {
|
|
76
|
+
const newMascot = MascotFactory.createFromTemplate(
|
|
77
|
+
template as 'friendly-bot' | 'cute-pet' | 'wise-owl' | 'pixel-hero',
|
|
78
|
+
customizations
|
|
79
|
+
);
|
|
80
|
+
setMascot(newMascot);
|
|
81
|
+
setIsReady(true);
|
|
82
|
+
if (!animationControllerRef.current) {
|
|
83
|
+
animationControllerRef.current = new AnimationController();
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
// Mood management
|
|
88
|
+
const setMood = useCallback((mood: MascotMood) => {
|
|
89
|
+
setMascot((prev) => {
|
|
90
|
+
if (!prev) return null;
|
|
91
|
+
prev.setMood(mood);
|
|
92
|
+
return prev.clone();
|
|
93
|
+
});
|
|
94
|
+
}, []);
|
|
95
|
+
|
|
96
|
+
const setEnergy = useCallback((energy: number) => {
|
|
97
|
+
setMascot((prev) => {
|
|
98
|
+
if (!prev) return null;
|
|
99
|
+
prev.setEnergy(energy);
|
|
100
|
+
return prev.clone();
|
|
101
|
+
});
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
// Animation management
|
|
105
|
+
const playAnimation = useCallback(async (animationId: string, options?: AnimationOptions) => {
|
|
106
|
+
if (!mascot || !animationControllerRef.current) return;
|
|
107
|
+
|
|
108
|
+
const animation = mascot.getAnimation(animationId);
|
|
109
|
+
if (!animation) {
|
|
110
|
+
console.warn(`Animation ${animationId} not found`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
setIsPlaying(true);
|
|
115
|
+
setCurrentAnimation(animationId);
|
|
116
|
+
|
|
117
|
+
if (animationControllerRef.current) {
|
|
118
|
+
await animationControllerRef.current.play(animation, options);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
setIsPlaying(false);
|
|
122
|
+
setCurrentAnimation(null);
|
|
123
|
+
}, [mascot]);
|
|
124
|
+
|
|
125
|
+
const stopAnimation = useCallback(() => {
|
|
126
|
+
if (animationControllerRef.current) {
|
|
127
|
+
animationControllerRef.current.stop();
|
|
128
|
+
}
|
|
129
|
+
setIsPlaying(false);
|
|
130
|
+
setCurrentAnimation(null);
|
|
131
|
+
}, []);
|
|
132
|
+
|
|
133
|
+
// Appearance management
|
|
134
|
+
const updateAppearance = useCallback((appearance: Partial<MascotAppearance>) => {
|
|
135
|
+
setMascot((prev) => {
|
|
136
|
+
if (!prev) return null;
|
|
137
|
+
prev.updateAppearance(appearance);
|
|
138
|
+
return prev.clone();
|
|
139
|
+
});
|
|
140
|
+
}, []);
|
|
141
|
+
|
|
142
|
+
const setBaseColor = useCallback((color: string) => {
|
|
143
|
+
setMascot((prev) => {
|
|
144
|
+
if (!prev) return null;
|
|
145
|
+
prev.setBaseColor(color);
|
|
146
|
+
return prev.clone();
|
|
147
|
+
});
|
|
148
|
+
}, []);
|
|
149
|
+
|
|
150
|
+
const setAccentColor = useCallback((color: string) => {
|
|
151
|
+
setMascot((prev) => {
|
|
152
|
+
if (!prev) return null;
|
|
153
|
+
prev.setAccentColor(color);
|
|
154
|
+
return prev.clone();
|
|
155
|
+
});
|
|
156
|
+
}, []);
|
|
157
|
+
|
|
158
|
+
const addAccessory = useCallback((accessory: {
|
|
159
|
+
id: string;
|
|
160
|
+
type: string;
|
|
161
|
+
color?: string;
|
|
162
|
+
position?: { x: number; y: number };
|
|
163
|
+
}) => {
|
|
164
|
+
setMascot((prev) => {
|
|
165
|
+
if (!prev) return null;
|
|
166
|
+
prev.addAccessory(accessory);
|
|
167
|
+
return prev.clone();
|
|
168
|
+
});
|
|
169
|
+
}, []);
|
|
170
|
+
|
|
171
|
+
const removeAccessory = useCallback((accessoryId: string) => {
|
|
172
|
+
setMascot((prev) => {
|
|
173
|
+
if (!prev) return null;
|
|
174
|
+
prev.removeAccessory(accessoryId);
|
|
175
|
+
return prev.clone();
|
|
176
|
+
});
|
|
177
|
+
}, []);
|
|
178
|
+
|
|
179
|
+
// Visibility and position
|
|
180
|
+
const setVisible = useCallback((visible: boolean) => {
|
|
181
|
+
setMascot((prev) => {
|
|
182
|
+
if (!prev) return null;
|
|
183
|
+
prev.setVisible(visible);
|
|
184
|
+
return prev.clone();
|
|
185
|
+
});
|
|
186
|
+
}, []);
|
|
187
|
+
|
|
188
|
+
const setPosition = useCallback((position: { x: number; y: number }) => {
|
|
189
|
+
setMascot((prev) => {
|
|
190
|
+
if (!prev) return null;
|
|
191
|
+
prev.setPosition(position);
|
|
192
|
+
return prev.clone();
|
|
193
|
+
});
|
|
194
|
+
}, []);
|
|
195
|
+
|
|
196
|
+
// Auto-initialize
|
|
197
|
+
useEffect(() => {
|
|
198
|
+
if (autoInitialize && initialConfig) {
|
|
199
|
+
initialize(initialConfig);
|
|
200
|
+
} else if (autoInitialize && initialTemplate) {
|
|
201
|
+
initializeFromTemplate(initialTemplate);
|
|
202
|
+
}
|
|
203
|
+
}, [autoInitialize, initialConfig, initialTemplate, initialize, initializeFromTemplate]);
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
mascot,
|
|
207
|
+
isReady,
|
|
208
|
+
isPlaying,
|
|
209
|
+
currentAnimation,
|
|
210
|
+
initialize,
|
|
211
|
+
initializeFromTemplate,
|
|
212
|
+
setMood,
|
|
213
|
+
setEnergy,
|
|
214
|
+
playAnimation,
|
|
215
|
+
stopAnimation,
|
|
216
|
+
updateAppearance,
|
|
217
|
+
setBaseColor,
|
|
218
|
+
setAccentColor,
|
|
219
|
+
addAccessory,
|
|
220
|
+
removeAccessory,
|
|
221
|
+
setVisible,
|
|
222
|
+
setPosition,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useMascotAnimation Hook
|
|
3
|
+
* Advanced animation control with queue and sequencing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useRef, useState } from 'react';
|
|
7
|
+
import type { Mascot } from '../../domain/entities/Mascot';
|
|
8
|
+
import type { AnimationSpeed } from '../../domain/types/MascotTypes';
|
|
9
|
+
import { AnimationController } from '../../infrastructure/controllers/AnimationController';
|
|
10
|
+
import type { AnimationOptions } from '../../domain/interfaces/IAnimationController';
|
|
11
|
+
|
|
12
|
+
export interface UseMascotAnimationOptions {
|
|
13
|
+
mascot: Mascot | null;
|
|
14
|
+
autoplay?: boolean;
|
|
15
|
+
queue?: boolean;
|
|
16
|
+
speed?: AnimationSpeed;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseMascotAnimationReturn {
|
|
20
|
+
isPlaying: boolean;
|
|
21
|
+
currentAnimation: string | null;
|
|
22
|
+
progress: number;
|
|
23
|
+
queue: string[];
|
|
24
|
+
play: (animationId: string, options?: AnimationOptions) => Promise<void>;
|
|
25
|
+
pause: () => void;
|
|
26
|
+
resume: () => void;
|
|
27
|
+
stop: () => void;
|
|
28
|
+
setSpeed: (speed: number) => void;
|
|
29
|
+
setProgress: (progress: number) => void;
|
|
30
|
+
queueAnimation: (animationId: string) => void;
|
|
31
|
+
clearQueue: () => void;
|
|
32
|
+
playSequence: (animationIds: string[]) => Promise<void>;
|
|
33
|
+
processQueue: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const SPEED_MULTIPLIERS: Record<AnimationSpeed, number> = {
|
|
37
|
+
'very-slow': 0.25,
|
|
38
|
+
'slow': 0.5,
|
|
39
|
+
'normal': 1,
|
|
40
|
+
'fast': 1.5,
|
|
41
|
+
'very-fast': 2,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export function useMascotAnimation(
|
|
45
|
+
options: UseMascotAnimationOptions
|
|
46
|
+
): UseMascotAnimationReturn {
|
|
47
|
+
const { mascot, speed = 'normal' } = options;
|
|
48
|
+
|
|
49
|
+
const [isPlaying, setIsPlaying] = useState(false);
|
|
50
|
+
const [currentAnimation, setCurrentAnimation] = useState<string | null>(null);
|
|
51
|
+
const [progress, setProgress] = useState(0);
|
|
52
|
+
const [queue, setQueue] = useState<string[]>([]);
|
|
53
|
+
|
|
54
|
+
const animationControllerRef = useRef<AnimationController | null>(null);
|
|
55
|
+
const isProcessingQueueRef = useRef(false);
|
|
56
|
+
|
|
57
|
+
// Initialize animation controller
|
|
58
|
+
if (!animationControllerRef.current) {
|
|
59
|
+
animationControllerRef.current = new AnimationController();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Setup progress tracking
|
|
63
|
+
const animationController = animationControllerRef.current;
|
|
64
|
+
animationController.on('progress', (data: unknown) => {
|
|
65
|
+
const { progress: newProgress } = data as { progress: number };
|
|
66
|
+
setProgress(newProgress);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const play = useCallback(async (animationId: string, options?: AnimationOptions) => {
|
|
70
|
+
if (!mascot) {
|
|
71
|
+
console.warn('Mascot not initialized');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const animation = mascot.getAnimation(animationId);
|
|
76
|
+
if (!animation) {
|
|
77
|
+
console.warn(`Animation ${animationId} not found`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setIsPlaying(true);
|
|
82
|
+
setCurrentAnimation(animationId);
|
|
83
|
+
|
|
84
|
+
const speedMultiplier = SPEED_MULTIPLIERS[speed];
|
|
85
|
+
const finalOptions: AnimationOptions = {
|
|
86
|
+
...options,
|
|
87
|
+
speed: (options?.speed || 1) * speedMultiplier,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
await animationController.play(animation, finalOptions);
|
|
91
|
+
|
|
92
|
+
setIsPlaying(false);
|
|
93
|
+
setCurrentAnimation(null);
|
|
94
|
+
setProgress(0);
|
|
95
|
+
}, [mascot, speed, animationController]);
|
|
96
|
+
|
|
97
|
+
const pause = useCallback(() => {
|
|
98
|
+
animationController.pause();
|
|
99
|
+
}, [animationController]);
|
|
100
|
+
|
|
101
|
+
const resume = useCallback(() => {
|
|
102
|
+
animationController.resume();
|
|
103
|
+
}, [animationController]);
|
|
104
|
+
|
|
105
|
+
const stop = useCallback(() => {
|
|
106
|
+
animationController.stop();
|
|
107
|
+
setIsPlaying(false);
|
|
108
|
+
setCurrentAnimation(null);
|
|
109
|
+
setProgress(0);
|
|
110
|
+
}, [animationController]);
|
|
111
|
+
|
|
112
|
+
const setSpeed = useCallback((newSpeed: number) => {
|
|
113
|
+
animationController.setSpeed(newSpeed);
|
|
114
|
+
}, [animationController]);
|
|
115
|
+
|
|
116
|
+
const setProgressValue = useCallback((newProgress: number) => {
|
|
117
|
+
animationController.setProgress(newProgress);
|
|
118
|
+
setProgress(newProgress);
|
|
119
|
+
}, [animationController]);
|
|
120
|
+
|
|
121
|
+
const queueAnimation = useCallback((animationId: string) => {
|
|
122
|
+
setQueue((prev) => [...prev, animationId]);
|
|
123
|
+
}, []);
|
|
124
|
+
|
|
125
|
+
const clearQueue = useCallback(() => {
|
|
126
|
+
setQueue([]);
|
|
127
|
+
}, []);
|
|
128
|
+
|
|
129
|
+
const playSequence = useCallback(async (animationIds: string[]) => {
|
|
130
|
+
for (const animationId of animationIds) {
|
|
131
|
+
await play(animationId);
|
|
132
|
+
}
|
|
133
|
+
}, [play]);
|
|
134
|
+
|
|
135
|
+
// Process queue automatically
|
|
136
|
+
const processQueue = useCallback(async () => {
|
|
137
|
+
if (isProcessingQueueRef.current || queue.length === 0 || !mascot) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
isProcessingQueueRef.current = true;
|
|
142
|
+
|
|
143
|
+
while (queue.length > 0) {
|
|
144
|
+
const nextAnimation = queue[0];
|
|
145
|
+
setQueue((prev) => prev.slice(1));
|
|
146
|
+
await play(nextAnimation);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
isProcessingQueueRef.current = false;
|
|
150
|
+
}, [queue, mascot, play]);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
isPlaying,
|
|
154
|
+
currentAnimation,
|
|
155
|
+
progress,
|
|
156
|
+
queue,
|
|
157
|
+
play,
|
|
158
|
+
pause,
|
|
159
|
+
resume,
|
|
160
|
+
stop,
|
|
161
|
+
setSpeed,
|
|
162
|
+
setProgress: setProgressValue,
|
|
163
|
+
queueAnimation,
|
|
164
|
+
clearQueue,
|
|
165
|
+
playSequence,
|
|
166
|
+
processQueue,
|
|
167
|
+
};
|
|
168
|
+
}
|