8000 :sparkles: feat: 添加重置舞蹈动作 · lobehub/lobe-vidol@054d94a · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Commit 054d94a

Browse files
committed
✨ feat: 添加重置舞蹈动作
1 parent 95d5d8f commit 054d94a

File tree

11 files changed

+320
-70
lines changed

11 files changed

+320
-70
lines changed

src/features/Actions/Discord.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export default () => {
2929
icon={SiDiscord}
3030
key="discord"
3131
title={t('support')}
32-
onClick={() => window.open('https://discord.gg/8Gq83ZCK9u', '_blank')}
32+
onClick={() => window.open('https://discord.gg/AYFPHvv2jT', '_blank')}
3333
style={{ border: `1px solid ${theme.colorFillSecondary}` }}
3434
/>
3535
);

src/features/AgentViewer/ToolBar/index.tsx

+12-2
< 6D47 tr class="diff-line-row">
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ActionIconGroup } from '@lobehub/ui';
22
import dayjs from 'dayjs';
3-
import { Aperture, Axis3D, Grid3x3, Orbit, RotateCw, SwitchCamera } from 'lucide-react';
3+
import { Aperture, Axis3D, Grid3x3, Orbit, Power, SwitchCamera } from 'lucide-react';
44
import React from 'react';
55
import { useTranslation } from 'react-i18next';
66

@@ -41,7 +41,12 @@ const ToolBar = (props: ToolBarProps) => {
4141
dropdownMenu={dropdownMenu}
4242
items={[
4343
{
44-
icon: RotateCw,
44+
icon: Power,
45+
key: 'power',
46+
label: t('toolBar.resetToIdle'),
47+
},
48+
{
49+
icon: SwitchCamera,
4550
key: 'resetCamera',
4651
label: t('toolBar.resetCamera'),
4752
},
@@ -63,6 +68,10 @@ const ToolBar = (props: ToolBarProps) => {
6368

6469
break;
6570
}
71+
case 'power': {
72+
viewer.model?.resetToIdle();
73+
break;
74+
}
6675
case 'screenShot': {
6776
const canvas = document.querySelector('#canvas') as HTMLCanvasElement;
6877
const imageType = 'png';
@@ -103,6 +112,7 @@ const ToolBar = (props: ToolBarProps) => {
103112
}}
104113
style={style}
105114
type={'block'}
115+
size={'normal'}
106116
/>
107117
);
108118
};

src/features/DanceList/Item/index.tsx

+85
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import { Avatar } from '@lobehub/ui';
2+
import { useHover } from 'ahooks';
3+
import { Progress, Typography } from 'antd';
4+
import React, { memo, useRef, useState } from 'react';
5+
6+
import ListItem from '@/components/ListItem';
7+
import Actions from '@/features/DanceList/Item/Actions';
8+
import { useLoadAudio } from '@/hooks/useLoadAudio';
9+
import { useLoadDance } from '@/hooks/useLoadDance';
10+
import { useDanceStore } from '@/store/dance';
11+
import { useGlobalStore } from '@/store/global';
12+
import { Dance } from '@/types/dance';
13+
14+
import { useStyles } from './style';
15+
16+
const { Text } = Typography;
17+
18+
interface DanceItemProps {
19+
danceItem: Dance;
20+
}
21+
22+
const DanceItem = (props: DanceItemProps) => {
23+
const { danceItem } = props;
24+
const [open, setOpen] = useState(false);
25+
26+
const { styles } = useStyles();
27+
const [currentPlayId, setCurrentPlayId] = useDanceStore((s) => [
28+
s.currentPlayId,
29+
s.setCurrentPlayId,
30+
]);
31+
32+
const isCurrentPlay = currentPlayId ? currentPlayId === danceItem.danceId : false;
33+
const hoverRef = useRef(null);
34+
const isHovered = useHover(hoverRef);
35+
36+
const { downloading: audioDownloading, percent: audioPercent, fetchAudioBuffer } = useLoadAudio();
37+
const { downloading: danceDownloading, percent: dancePercent, fetchDanceBuffer } = useLoadDance();
38+
const viewer = useGlobalStore((s) => s.viewer);
39+
40+
const handlePlayPause = () => {
41+
const audioPromise = fetchAudioBuffer(danceItem.danceId, danceItem.audio);
42+
const dancePromise = fetchDanceBuffer(danceItem.danceId, danceItem.src);
43+
Promise.all([dancePromise, audioPromise]).then((res) => {
44+
if (!res) return;
45+
const [danceBuffer, audioBuffer] = res;
46+
viewer.model?.dance(danceBuffer, audioBuffer);
47+
});
48+
setCurrentPlayId(danceItem.danceId);
49+
};
50+
51+
return (
52+
<ListItem
53+
ref={hoverRef}
54+
showAction={isHovered || open || audioDownloading || danceDownloading}
55+
actions={[
56+
audioDownloading || danceDownloading ? (
57+
<Progress
58+
key={`progress-${danceItem.danceId}`}
59+
type="circle"
60+
className={styles.progress}
61+
percent={Math.ceil((dancePercent + audioPercent) / 2)}
62+
size={[32, 32]}
63+
/>
64+
) : null,
65+
<Actions danceItem={danceItem} setOpen={setOpen} key={`actions-${danceItem.danceId}`} />,
66+
]}
67+
onClick={handlePlayPause}
68+
className={styles.listItem}
69+
avatar={
70+
<div style={{ position: 'relative' }}>
71+
<Avatar src={danceItem?.thumb} shape={'square'} size={48} />
72+
</div>
73+
}
74+
title={danceItem?.name}
75+
description={
76+
<Text type="secondary" ellipsis={{ tooltip: true }}>
77+
{danceItem?.author}
78+
</Text>
79+
}
80+
active={isCurrentPlay || isHovered}
81+
/>
82+
);
83+
};
84+
85+
export default memo(DanceItem);

src/features/DanceList/Item/style.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { createStyles } from 'antd-style';
2+
3+
const useStyles = createStyles(({ css, token }) => ({
4+
mask: css`
5+
cursor: pointer;
6+
7+
position: absolute;
8+
top: 0;
9+
left: 0;
10+
11+
width: 100%;
12+
height: 100%;
13+
14+
background-color: ${token.colorBgMask};
15+
`,
16+
progress: css`
17+
background-color: rgba(${token.colorBgLayout}, 0.8);
18+
backdrop-filter: saturate(180%) blur(10px);
19+
border-radius: 100%;
20+
`,
21+
playIcon: css`
22+
position: absolute;
23+
top: 50%;
24+
left: 50%;
25+
transform: translate(-50%, -50%);
26+
27+
font-size: 24px;
28+
color: ${token.colorText};
29+
`,
30+
listItem: css`
31+
position: relative;
32+
33+
height: 64px;
34+
margin-block: 2px;
35+
36+
font-size: ${token.fontSize}px;
37+
38+
border-radius: ${token.borderRadius}px;
39+
`,
40+
}));
41+
42+
export { useStyles };

src/features/DanceList/index.tsx

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { ActionIcon } from '@lobehub/ui';
2+
import { Empty } from 'antd';
3+
import { createStyles } from 'antd-style';
4+
import classNames from 'classnames';
5+
import { PlusCircle } from 'lucide-react';
6+
import React, { memo } from 'react';
7+
import { useTranslation } from 'react-i18next';
8+
import { Flexbox } from 'react-layout-kit';
9+
10+
import Header from '@/components/Header';
11+
import { useDanceStore } from '@/store/dance';
12+
import { useGlobalStore } from '@/store/global';
13+
14+
import DanceItem from './Item';
15+
16+
interface PlayListProps {
17+
className?: string;
18+
style?: React.CSSProperties;
19+
}
20+
21+
const useStyles = createStyles(({ css, token }) => ({
22+
container: css`
23+
position: relative;
24+
height: 100%;
25+
background-color: rgba(255, 255, 255, 2%);
26+
border-radius: ${token.borderRadius}px;
27+
`,
28+
list: css`
29+
overflow-y: scroll;
30+
width: 100%;
31+
padding: 0 ${token.paddingSM}px;
32+
`,
33+
player: css`
34+
width: 100%;
35+
height: 64px;
36+
padding: 0 ${token.paddingSM}px;
37+
border-top: 1px solid ${token.colorBorder};
38+
`,
39+
}));
40+
41+
const DanceList = (props: PlayListProps) => {
42+
const danceList = useDanceStore((s) => s.danceList);
43+
const [openPanel] = useGlobalStore((s) => [s.openPanel]);
44+
45+
const { t } = useTranslation(['panel', 'common']);
46+
const { className, style } = props;
47+
const { styles } = useStyles();
48+
49+
return (
50+
<Flexbox className={classNames(className, styles.container)} style={style} id={'dance-list'}>
51+
<Flexbox className={styles.list} flex={1}>
52+
<Header
53+
title={t('danceList', { ns: 'common' })}
54+
extra={
55+
<ActionIcon
56+
icon={PlusCircle}
57+
onClick={() => {
58+
openPanel('dance');
59+
}}
60+
title={t('dance.musicAndDance')}
61+
/>
62+
}
63+
/>
64+
65+
{danceList.map((item) => {
66+
return <DanceItem danceItem={item} key={item.danceId} />;
67+
})}
68+
{danceList.length === 0 ? (
69+
<Empty description={t('dance.noPlayList')} image={Empty.PRESENTED_IMAGE_SIMPLE}></Empty>
70+
) : null}
71+
</Flexbox>
72+
</Flexbox>
73+
);
74+
};
75+
76+
export default memo(DanceList);
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
export class AudioPlayer {
2+
public readonly audio: AudioContext;
3+
public bufferSource: AudioBufferSourceNode | undefined;
4+
5+
public constructor(audio: AudioContext) {
6+
this.audio = audio;
7+
this.bufferSource = undefined;
8+
}
9+
10+
public async playFromArrayBuffer(buffer: ArrayBuffer, onEnded?: () => void) {
11+
const audioBuffer = await this.audio.decodeAudioData(buffer);
12+
13+
this.bufferSource = this.audio.createBufferSource();
14+
this.bufferSource.buffer = audioBuffer;
15+
16+
this.bufferSource.connect(this.audio.destination);
17+
this.bufferSource.start();
18+
if (onEnded) {
19+
this.bufferSource.addEventListener('ended', onEnded);
20+
}
21+
}
22+
23+
public stopPlay() {
24+
if (this.bufferSource) this.bufferSource.stop();
25+
}
26+
27+
public async playFromURL(url: string, onEnded?: () => void) {
28+
const res = await fetch(url);
29+
const buffer = await res.arrayBuffer();
30+
this.playFromArrayBuffer(buffer, onEnded);
31+
}
32+
}

0 commit comments

Comments
 (0)
0