588 lines
26 KiB
TypeScript
588 lines
26 KiB
TypeScript
import styles from '@/components/StupWizard/index.module.scss'
|
||
import ImageUrl from '@/utils/package/ImageUrl';
|
||
import {Button, Checkbox, Empty, Input, message, Modal, Popover, Radio, Select, Slider} from 'antd';
|
||
import {forwardRef, useEffect, useImperativeHandle, useState} from "react";
|
||
import agora from '@/utils/package/agora'
|
||
import {CloseOutlined, LoadingOutlined, QuestionCircleOutlined} from '@ant-design/icons';
|
||
import {storage} from '@/utils';
|
||
import path from 'path';
|
||
|
||
const fs = require('fs').promises;
|
||
const {exec} = require('child_process');
|
||
const StupWizard = forwardRef((props: any, ref: any) => {
|
||
useImperativeHandle(ref, () => ({
|
||
changeModal: () => {
|
||
if (location.hash.indexOf('/meeting') === -1) {
|
||
agora.init()
|
||
}
|
||
setIsStupWizard(true)
|
||
}
|
||
}))
|
||
const [list, setList] = useState([
|
||
{
|
||
title: '通用',
|
||
icon: ImageUrl.icon39,
|
||
iconActive: ImageUrl.icon39Active,
|
||
active: true,
|
||
},
|
||
{
|
||
title: '视频',
|
||
icon: ImageUrl.icon39,
|
||
iconActive: ImageUrl.icon39Active,
|
||
active: false,
|
||
},
|
||
{
|
||
title: '音频',
|
||
icon: ImageUrl.icon40,
|
||
iconActive: ImageUrl.icon40Active,
|
||
active: false,
|
||
},
|
||
{
|
||
title: '录制',
|
||
icon: ImageUrl.icon41,
|
||
iconActive: ImageUrl.icon41Active,
|
||
active: false,
|
||
},
|
||
{
|
||
title: '共享文件',
|
||
icon: ImageUrl.icon42,
|
||
iconActive: ImageUrl.icon42Active,
|
||
active: false,
|
||
}
|
||
])
|
||
const [isStupWizard, setIsStupWizard] = useState(false);
|
||
return (
|
||
<>
|
||
<Modal
|
||
title=""
|
||
open={isStupWizard}
|
||
footer={null}
|
||
closable={false}
|
||
destroyOnClose={true}
|
||
centered
|
||
width={'70vw'}
|
||
className='modal-padding'>
|
||
<div className={styles.stupWizard}>
|
||
<div className={styles.stupWizardLeft}>
|
||
{list.map((row: any, index: number) => {
|
||
return (
|
||
<div key={index} className={`${row.active ? styles.active : ''}`} onClick={async () => {
|
||
const newList = [...list];
|
||
newList.forEach(item => item.active = false);
|
||
newList[index].active = true;
|
||
setList(newList)
|
||
agora.stopPlaybackDeviceTest()
|
||
agora.stopRecordingDeviceTest()
|
||
}}>
|
||
<img src={row.active ? row.iconActive : row.icon} alt=""/>
|
||
<span>{row.title}</span>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
<div className={styles.stupWizardRight}>
|
||
<CloseOutlined style={{
|
||
position: 'absolute',
|
||
color: 'white',
|
||
right: '20px',
|
||
top: '16px',
|
||
cursor: 'pointer'
|
||
}}
|
||
onClick={() => {
|
||
if (location.hash.indexOf('/meeting') === -1) {
|
||
agora.release()
|
||
}
|
||
setIsStupWizard(false)
|
||
}}
|
||
/>
|
||
{list[0].active ? <CurrencyComponents/> : null}
|
||
{list[1].active ? <VideoComponents/> : null}
|
||
{list[2].active ? <AudioComponents/> : null}
|
||
{list[3].active ? <RecordingComponents/> : null}
|
||
{list[4].active ? <FileComponents/> : null}
|
||
</div>
|
||
</div>
|
||
</Modal>
|
||
</>
|
||
)
|
||
})
|
||
const CurrencyComponents = () => {
|
||
const [optionsValue, setOperationValue] = useState<'hide' | 'quit'>('hide');
|
||
const setting = JSON.parse(storage.getItem('setting') as string)
|
||
useEffect(() => {
|
||
setOperationValue(setting.closeSetting)
|
||
}, []);
|
||
return (
|
||
<>
|
||
<div>
|
||
<span>通用</span>
|
||
<div className={styles.currencyComponents}>
|
||
<div>
|
||
<div>
|
||
<span>关闭按钮设置</span>
|
||
<Radio.Group onChange={(e: any) => {
|
||
setting.closeSetting = e.target.value;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setOperationValue(e.target.value)
|
||
}} style={{flexShrink: 0, margin: '10px 0'}} value={optionsValue}>
|
||
<Radio value={'quit'}>退出主程序</Radio>
|
||
<Radio value={'hide'}>不退出程序,最小化到托盘</Radio>
|
||
</Radio.Group>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
const VideoComponents = () => {
|
||
const [videoDeviceManager, setVideoDeviceManager] = useState<any>({
|
||
list: [],
|
||
item: null,
|
||
});
|
||
const setting = JSON.parse(storage.getItem('setting') as string)
|
||
useEffect(() => {
|
||
getVideoDeviceList()
|
||
}, []);
|
||
const getVideoDeviceList = async (): Promise<void> => {
|
||
const userInfo = JSON.parse(storage.getItem('user') as string)
|
||
agora.getVideoDeviceManager().then(async (res) => {
|
||
const {item, list} = res
|
||
if ((!setting.videoDeviceId && item) || (!(list.find((item: any) => item.deviceId === setting.videoDeviceId)) && item)) {
|
||
setting.videoDeviceId = item
|
||
}
|
||
if (!list.length) {
|
||
setting.videoDeviceId = ''
|
||
}
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setVideoDeviceManager({
|
||
list: list.map((row: any) => {
|
||
return {
|
||
value: row.deviceId,
|
||
label: row.deviceName
|
||
}
|
||
}),
|
||
item: setting.videoDeviceId ? setting.videoDeviceId : item || null,
|
||
})
|
||
if (setting.videoDeviceId && list.length) {
|
||
await agora.setVideoDeviceManager(setting.videoDeviceId)
|
||
await agora.startPreview('videoPreview', Number(userInfo.account))
|
||
}
|
||
})
|
||
}
|
||
return (
|
||
<>
|
||
<div>
|
||
<span>视频</span>
|
||
<div className={styles.videoComponents}>
|
||
{
|
||
videoDeviceManager.item ?
|
||
<div>
|
||
<div id='videoPreview'>
|
||
<LoadingOutlined style={{
|
||
position: 'absolute',
|
||
color: 'white',
|
||
right: '50%',
|
||
fontSize: '30px',
|
||
top: '50%',
|
||
}}/>
|
||
</div>
|
||
</div> :
|
||
<div>
|
||
<div style={{display: 'flex', justifyContent: 'center', alignItems: 'center'}}>
|
||
<Empty description={'未检测到摄像头'}/>
|
||
</div>
|
||
</div>
|
||
}
|
||
<div>
|
||
<span>摄像头:</span>
|
||
<Popover
|
||
content={
|
||
<span
|
||
style={{
|
||
color: 'white'
|
||
}}>
|
||
解释说明:如未检测到摄像头请插拔后重试
|
||
</span>
|
||
}
|
||
title=""
|
||
>
|
||
<QuestionCircleOutlined style={{
|
||
color: 'white',
|
||
cursor: 'pointer',
|
||
marginRight: '10px'
|
||
}}/>
|
||
</Popover>
|
||
<Select
|
||
placeholder={videoDeviceManager.list.length ? '请选择设备' : '未检测到摄像头'}
|
||
options={videoDeviceManager.list} style={{flexGrow: 1, marginRight: '10px'}}
|
||
value={videoDeviceManager.item} onChange={async (e) => {
|
||
setting.videoDeviceId = e;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setVideoDeviceManager({
|
||
...videoDeviceManager,
|
||
item: e
|
||
})
|
||
agora.setVideoDeviceManager(e)
|
||
}}/>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
const AudioComponents = () => {
|
||
const [audioDeviceManager, setAudioDeviceManager] = useState<any>({
|
||
playBackList: [],
|
||
ecordingList: [],
|
||
playBackItem: null,
|
||
ecordingItem: null,
|
||
playBackVolume: 0,
|
||
playBackActive: false,
|
||
ecordingActive: false,
|
||
ecordingVolume: 0,
|
||
autoEcordingVolume: true,
|
||
});
|
||
const setting = JSON.parse(storage.getItem('setting') as string)
|
||
useEffect(() => {
|
||
getAudioMediaList()
|
||
agora.registerEventHandler({
|
||
onAudioVolumeIndication: (percentage: number) => {
|
||
const dom = document.getElementById('deviceTest') as any;
|
||
if (dom) {
|
||
dom.style.width = `${percentage}%`
|
||
}
|
||
}
|
||
})
|
||
}, []);
|
||
const getAudioMediaList = async (): Promise<void> => {
|
||
const {
|
||
playBackList,
|
||
ecordingList,
|
||
playBackItem,
|
||
ecordingItem,
|
||
ecordingVolume
|
||
} = await agora.getAudioMediaList();
|
||
if ((!setting.ecordingDeviceId && ecordingItem.deviceId) || (!(ecordingList.find((item: any) => item.deviceId === setting.ecordingDeviceId)) && ecordingItem.deviceId)) {
|
||
setting.ecordingDeviceId = ecordingItem.deviceId
|
||
}
|
||
if ((!setting.playBackDeviceId && playBackItem.deviceId) || (!(playBackList.find((item: any) => item.deviceId === setting.playBackDeviceId)) && playBackItem.deviceId)) {
|
||
setting.playBackDeviceId = playBackItem.deviceId
|
||
}
|
||
if (!ecordingList.length) {
|
||
setting.ecordingDeviceId = ''
|
||
}
|
||
if (!playBackList.length) {
|
||
setting.playBackDeviceId = ''
|
||
}
|
||
if (!setting.ecordingVolume) {
|
||
setting.ecordingVolume = ecordingVolume;
|
||
}
|
||
if (!setting.playBackVolume) {
|
||
setting.playBackVolume = 127;
|
||
}
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
playBackList: playBackList.map((row: any) => {
|
||
return {
|
||
value: row.deviceId,
|
||
label: row.deviceName
|
||
}
|
||
}),
|
||
ecordingList: ecordingList.map((row: any) => {
|
||
return {
|
||
value: row.deviceId,
|
||
label: row.deviceName
|
||
}
|
||
}),
|
||
playBackItem: setting.playBackDeviceId ? setting.playBackDeviceId : playBackItem.deviceId || null,
|
||
ecordingItem: setting.ecordingDeviceId ? setting.ecordingDeviceId : ecordingItem.deviceId || null,
|
||
ecordingVolume: setting.ecordingVolume,
|
||
playBackVolume: setting.playBackVolume,
|
||
autoEcordingVolume: setting.autoEcordingVolume
|
||
})
|
||
}
|
||
return (
|
||
<>
|
||
<div>
|
||
<span>音频</span>
|
||
<div className={styles.audioComponents}>
|
||
<div>
|
||
<div className={styles.audioComponentsSelect}>
|
||
<span>麦克风:</span>
|
||
<Select
|
||
placeholder={audioDeviceManager.ecordingList.length ? '请选择设备' : '未检测到麦克风'}
|
||
options={audioDeviceManager.ecordingList} style={{flexGrow: 1}}
|
||
value={audioDeviceManager.ecordingItem} onChange={async (e) => {
|
||
setting.ecordingDeviceId = e;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
ecordingItem: e
|
||
})
|
||
agora.setRecordingDevice(e)
|
||
}}/>;
|
||
{audioDeviceManager.ecordingActive ? <div onClick={() => {
|
||
agora.stopRecordingDeviceTest()
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
playBackActive: false,
|
||
ecordingActive: false,
|
||
})
|
||
}}>结束</div> : <div onClick={() => {
|
||
agora.stopPlaybackDeviceTest()
|
||
agora.startRecordingDeviceTest(100)
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
playBackActive: false,
|
||
ecordingActive: true,
|
||
})
|
||
}}>测试</div>}
|
||
</div>
|
||
{audioDeviceManager.ecordingActive ? <div className={styles.audioComponentsVolume}>
|
||
<img src={ImageUrl.icon36} alt=""/>
|
||
<div>
|
||
<img src={ImageUrl.icon34} alt=""/>
|
||
<div id='deviceTest'>
|
||
<img src={ImageUrl.icon35} alt=""/>
|
||
</div>
|
||
</div>
|
||
</div> : null}
|
||
<div className={styles.audioComponentsSlider}>
|
||
<span>输入音量:</span>
|
||
<Slider value={audioDeviceManager.ecordingVolume} style={{flexGrow: 1}} max={255}
|
||
onChange={async (e) => {
|
||
setting.ecordingVolume = e;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
await agora.setRecordingDeviceVolume(e)
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
ecordingVolume: e,
|
||
})
|
||
}} disabled={!audioDeviceManager.ecordingItem}/>
|
||
</div>
|
||
<div>
|
||
<Checkbox onChange={async (e) => {
|
||
setting.autoEcordingVolume = e.target.checked;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
autoEcordingVolume: e.target.checked
|
||
})
|
||
}} checked={audioDeviceManager.autoEcordingVolume}>自动调整麦克风音量</Checkbox>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div className={styles.audioComponentsSelect}>
|
||
<span>扬声器:</span>
|
||
<Select
|
||
placeholder={audioDeviceManager.playBackList.length ? '请选择设备' : '未检测到麦克风'}
|
||
options={audioDeviceManager.playBackList} style={{flexGrow: 1}}
|
||
value={audioDeviceManager.playBackItem} onChange={async (e) => {
|
||
setting.playBackDeviceId = e;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
playBackItem: e
|
||
})
|
||
agora.setPlaybackDevice(e)
|
||
}}/>;
|
||
{audioDeviceManager.playBackActive ? <div onClick={() => {
|
||
agora.stopPlaybackDeviceTest()
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
playBackActive: false,
|
||
ecordingActive: false,
|
||
})
|
||
}}>结束</div> : <div onClick={() => {
|
||
agora.stopRecordingDeviceTest()
|
||
agora.startPlaybackDeviceTest()
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
playBackActive: true,
|
||
ecordingActive: false,
|
||
})
|
||
}}>测试</div>}
|
||
</div>
|
||
{audioDeviceManager.playBackActive ? <div className={styles.audioComponentsVolume}>
|
||
<img src={ImageUrl.icon36} alt=""/>
|
||
<div>
|
||
<img src={ImageUrl.icon34} alt=""/>
|
||
<div id='deviceTest'>
|
||
<img src={ImageUrl.icon35} alt=""/>
|
||
</div>
|
||
</div>
|
||
</div> : null}
|
||
<div className={styles.audioComponentsSlider}>
|
||
<span>输出音量:</span>
|
||
<Slider value={audioDeviceManager.playBackVolume} style={{flexGrow: 1}} max={255}
|
||
onChange={async (e) => {
|
||
setting.playBackVolume = e;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
agora.setPlaybackDeviceVolume(e)
|
||
setAudioDeviceManager({
|
||
...audioDeviceManager,
|
||
playBackVolume: e,
|
||
})
|
||
}} disabled={!audioDeviceManager.playBackItem}/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
const RecordingComponents = () => {
|
||
const [filePath, setFilePath] = useState('')
|
||
const setting = JSON.parse(storage.getItem('setting') as string)
|
||
useEffect(() => {
|
||
if (!setting.recordingFilesPath) {
|
||
// 获取安装父目录
|
||
const currentDirectory = __dirname;
|
||
const parentDirectory = path.resolve(currentDirectory, '../../Downloads');
|
||
setting.recordingFilesPath = parentDirectory;
|
||
setFilePath(setting.recordingFilesPath)
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
} else {
|
||
setFilePath(setting.recordingFilesPath);
|
||
}
|
||
window.addEventListener('customStorageChange', handleCustomStorageChange);
|
||
return () => {
|
||
window.removeEventListener('customStorageChange', handleCustomStorageChange);
|
||
};
|
||
}, [])
|
||
const handleCustomStorageChange = (e: any): void => {
|
||
if (e.key === 'setting') {
|
||
setFilePath(setting.recordingFilesPath)
|
||
}
|
||
};
|
||
return (
|
||
<>
|
||
<div>
|
||
<span>录制</span>
|
||
<div className={styles.recordingComponents}>
|
||
<span>本地录制</span>
|
||
<div>
|
||
<span>本地录制文件路径</span>
|
||
<Input
|
||
disabled={true}
|
||
placeholder="请填入文件路径"
|
||
style={{margin: '0 14px', flexGrow: 1}}
|
||
value={filePath}
|
||
onChange={async (e) => {
|
||
setting.recordingFilesPath = e.target.value;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setFilePath(e.target.value)
|
||
}}
|
||
/>
|
||
<Button type="primary" onClick={() => {
|
||
window.electron.selectFilePath({
|
||
key: 'recordingFilesPath',
|
||
})
|
||
}} style={{backgroundColor: '#31353A', marginRight: '10px'}}>选择保存目录</Button>
|
||
<Button type="primary" onClick={async () => {
|
||
try {
|
||
await fs.access(filePath, fs.constants.F_OK);
|
||
if (process.platform === 'win32') {
|
||
exec(`explorer "${filePath}"`);
|
||
} else if (process.platform === 'darwin') {
|
||
exec(`open "${filePath}"`);
|
||
}
|
||
} catch (error: any) {
|
||
if (error.code === 'ENOENT') {
|
||
message.error('文件夹不存在!')
|
||
} else {
|
||
message.error(error)
|
||
}
|
||
}
|
||
}} style={{backgroundColor: '#31353A'}}>打开</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
const FileComponents = () => {
|
||
const [filePath, setFilePath] = useState('')
|
||
const [isShareSavePath, setIsShareSavePath] = useState(true)
|
||
const setting = JSON.parse(storage.getItem('setting') as string)
|
||
useEffect(() => {
|
||
if (!setting.shareFilesPath) {
|
||
// 获取安装父目录
|
||
const currentDirectory = __dirname;
|
||
const parentDirectory = path.resolve(currentDirectory, '../../Downloads');
|
||
setting.shareFilesPath = parentDirectory
|
||
setFilePath(setting.shareFilesPath)
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
} else {
|
||
setFilePath(setting.shareFilesPath);
|
||
}
|
||
setIsShareSavePath(setting.isShareSavePath)
|
||
window.addEventListener('customStorageChange', handleCustomStorageChange);
|
||
return () => {
|
||
window.removeEventListener('customStorageChange', handleCustomStorageChange);
|
||
};
|
||
}, [])
|
||
const handleCustomStorageChange = (e: any): void => {
|
||
if (e.key === 'setting') {
|
||
setFilePath(setting.shareFilesPath)
|
||
}
|
||
};
|
||
return (
|
||
<>
|
||
<div>
|
||
<span>共享文件</span>
|
||
<div className={styles.fileComponents}>
|
||
<span>文件下载</span>
|
||
<div>
|
||
<span>保存位置</span>
|
||
<Input
|
||
disabled={true}
|
||
placeholder="请填入保存目录"
|
||
style={{margin: '0 14px', flexGrow: 1}}
|
||
value={filePath}
|
||
onChange={async (e) => {
|
||
setting.shareFilesPath = e.target.value;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setFilePath(e.target.value)
|
||
}}
|
||
/>
|
||
<Button type="primary" onClick={() => {
|
||
window.electron.selectFilePath({
|
||
key: 'shareFilesPath',
|
||
})
|
||
}} style={{backgroundColor: '#31353A', marginRight: '10px'}}>选择保存目录</Button>
|
||
<Button type="primary" onClick={async () => {
|
||
try {
|
||
await fs.access(filePath, fs.constants.F_OK);
|
||
if (process.platform === 'win32') {
|
||
exec(`explorer "${filePath}"`);
|
||
} else if (process.platform === 'darwin') {
|
||
exec(`open "${filePath}"`);
|
||
}
|
||
} catch (error: any) {
|
||
if (error.code === 'ENOENT') {
|
||
message.error('文件夹不存在!')
|
||
} else {
|
||
message.error(error)
|
||
}
|
||
}
|
||
}} style={{backgroundColor: '#31353A'}}>打开</Button>
|
||
</div>
|
||
<div>
|
||
<Checkbox onChange={async (e) => {
|
||
setting.isShareSavePath = e.target.checked;
|
||
storage.setItem('setting', JSON.stringify(setting))
|
||
setIsShareSavePath(e.target.checked)
|
||
}} checked={isShareSavePath}>下载前询问每个文件保存位置</Checkbox>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
)
|
||
}
|
||
|
||
export default StupWizard
|