feat ui: improve audio addon look and style (#2118)
This commit is contained in:
parent
394b5ea404
commit
555e6836f6
5 changed files with 119 additions and 36 deletions
|
|
@ -41,7 +41,7 @@
|
|||
margin-top: 10px;
|
||||
}
|
||||
|
||||
& input {
|
||||
& input:not(.ant-input-number-input) {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
border: $gray-light solid 1px;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { MutedOutlined, SoundOutlined } from '@ant-design/icons';
|
||||
import { Button, Tooltip } from 'antd';
|
||||
import { MutedOutlined, SoundOutlined, CaretDownOutlined, ControlOutlined } from '@ant-design/icons';
|
||||
import { Button, Popover, InputNumber } from 'antd';
|
||||
import { Slider } from 'antd';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React, { useRef, useState } from 'react';
|
||||
|
||||
|
|
@ -8,8 +9,10 @@ import { PlayerContext } from '../../playerContext';
|
|||
function DropdownAudioPlayer({ url }: { url: string }) {
|
||||
const { store } = React.useContext(PlayerContext);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
const [volume, setVolume] = useState(0);
|
||||
const [delta, setDelta] = useState(0);
|
||||
const [isMuted, setIsMuted] = useState(false);
|
||||
const [deltaInputValue, setDeltaInputValue] = useState(0);
|
||||
const [isMuted, setIsMuted] = useState(true);
|
||||
const lastPlayerTime = React.useRef(0);
|
||||
const audioRef = useRef<HTMLAudioElement>(null);
|
||||
|
||||
|
|
@ -25,6 +28,34 @@ function DropdownAudioPlayer({ url }: { url: string }) {
|
|||
}
|
||||
};
|
||||
|
||||
const toggleVisible = () => {
|
||||
setIsVisible(!isVisible);
|
||||
};
|
||||
|
||||
const handleDelta = (value: any) => {
|
||||
setDeltaInputValue(parseFloat(value))
|
||||
}
|
||||
|
||||
const onSync = () => {
|
||||
setDelta(deltaInputValue)
|
||||
}
|
||||
const onCancel = () => {
|
||||
setDeltaInputValue(0)
|
||||
setIsVisible(false)
|
||||
}
|
||||
const onReset = () => {
|
||||
setDelta(0)
|
||||
setDeltaInputValue(0)
|
||||
setIsVisible(false)
|
||||
}
|
||||
|
||||
const onVolumeChange = (value: number) => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.volume = value / 100;
|
||||
}
|
||||
setVolume(value);
|
||||
};
|
||||
|
||||
const handleSeek = (timeMs: number) => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.currentTime = (timeMs + delta) / 1000;
|
||||
|
|
@ -38,11 +69,19 @@ function DropdownAudioPlayer({ url }: { url: string }) {
|
|||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
if (Math.abs(lastPlayerTime.current - time) > 1000) {
|
||||
if (Math.abs(lastPlayerTime.current - time) >= 250) {
|
||||
handleSeek(time);
|
||||
}
|
||||
lastPlayerTime.current = time;
|
||||
}, [time]);
|
||||
if (audioRef.current) {
|
||||
if (audioRef.current.paused && playing) {
|
||||
audioRef.current?.play();
|
||||
}
|
||||
if (audioRef.current.muted !== isMuted) {
|
||||
audioRef.current.muted = isMuted;
|
||||
}
|
||||
}
|
||||
lastPlayerTime.current = time + delta;
|
||||
}, [time, delta]);
|
||||
|
||||
React.useEffect(() => {
|
||||
changePlaybackSpeed(speed);
|
||||
|
|
@ -54,33 +93,80 @@ function DropdownAudioPlayer({ url }: { url: string }) {
|
|||
} else {
|
||||
audioRef.current?.pause();
|
||||
}
|
||||
const volume = audioRef.current?.volume ?? 0
|
||||
setVolume(volume * 100);
|
||||
}, [playing]);
|
||||
|
||||
return (
|
||||
<div className="relative mr-2">
|
||||
<Tooltip
|
||||
title={`This session contains audio recording. Press to ${
|
||||
isMuted ? 'unmute' : 'mute'
|
||||
}`}
|
||||
<div className={'relative'}>
|
||||
<div
|
||||
className={'flex items-center'}
|
||||
style={{ height: 24 }}
|
||||
>
|
||||
<Button
|
||||
className={'flex items-center justify-center'}
|
||||
onClick={toggleMute}
|
||||
size={'small'}
|
||||
<Popover
|
||||
trigger={'click'}
|
||||
className={'h-full'}
|
||||
content={
|
||||
<div
|
||||
className={'flex flex-col gap-2 rounded'}
|
||||
style={{ height: 200 }}
|
||||
>
|
||||
<Slider vertical value={volume} onChange={onVolumeChange} />
|
||||
<Button
|
||||
className={'flex items-center justify-center py-4 px-4'}
|
||||
onClick={toggleMute}
|
||||
shape={'circle'}
|
||||
>
|
||||
{isMuted ? <MutedOutlined /> : <SoundOutlined />}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
{isMuted ? <MutedOutlined /> : <SoundOutlined />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<div className={'px-2 h-full cursor-pointer border rounded-l border-gray-light hover:border-main hover:text-main hover:z-10'}>
|
||||
{isMuted ? <MutedOutlined /> : <SoundOutlined />}
|
||||
</div>
|
||||
</Popover>
|
||||
<div
|
||||
onClick={toggleVisible}
|
||||
style={{ marginLeft: -1 }}
|
||||
className={
|
||||
'px-2 h-full border rounded-r border-gray-light cursor-pointer hover:border-main hover:text-main hover:z-10'
|
||||
}
|
||||
>
|
||||
<CaretDownOutlined />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isVisible ? (
|
||||
<div className={"absolute left-1/2 top-0 border shadow border-gray-light rounded bg-white p-4 flex flex-col gap-4 mb-4"}
|
||||
style={{ width: 240, transform: 'translate(-75%, -110%)', zIndex: 101 }}>
|
||||
<div className={"font-semibold flex items-center gap-2"}>
|
||||
<ControlOutlined />
|
||||
<div>Audio Track Synchronization</div>
|
||||
</div>
|
||||
<InputNumber
|
||||
style={{ width: 180 }}
|
||||
value={deltaInputValue}
|
||||
size={"small"}
|
||||
step={"0.250"}
|
||||
name={"audio delta"}
|
||||
formatter={(value) => `${value}s`}
|
||||
parser={(value) => value?.replace('s', '') as unknown as number}
|
||||
stringMode
|
||||
onChange={handleDelta} />
|
||||
<div className={"w-full flex items-center gap-2"}>
|
||||
<Button size={"small"} type={"primary"} onClick={onSync}>Sync</Button>
|
||||
<Button size={"small"} onClick={onCancel}>Cancel</Button>
|
||||
|
||||
<Button size={"small"} type={"text"} className={"ml-auto"} onClick={onReset}>Reset</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: isVisible ? 'block' : 'none',
|
||||
zIndex: 2,
|
||||
width: 420,
|
||||
marginRight: 40,
|
||||
right: 0,
|
||||
top: -6,
|
||||
display: 'none',
|
||||
}}
|
||||
className="absolute shadow p-2 shadow-lg bg-white rounded border border-gray-lighter"
|
||||
>
|
||||
<audio
|
||||
ref={audioRef}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ import { BackLink, Link } from 'UI';
|
|||
import { toggleFavorite, setSessionPath } from 'Duck/sessions';
|
||||
import cn from 'classnames';
|
||||
import SessionMetaList from 'Shared/SessionItem/SessionMetaList';
|
||||
import DropdownAudioPlayer from "./AudioPlayer";
|
||||
import UserCard from './EventsBlock/UserCard';
|
||||
import Tabs from 'Components/Session/Tabs';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
|
|
@ -39,7 +38,6 @@ function PlayerBlockHeader(props: any) {
|
|||
siteId,
|
||||
setActiveTab,
|
||||
activeTab,
|
||||
location,
|
||||
history,
|
||||
sessionPath,
|
||||
fetchMetadata,
|
||||
|
|
@ -111,8 +109,6 @@ function PlayerBlockHeader(props: any) {
|
|||
<SessionMetaList className="" metaList={_metaList} maxLength={2} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{session.audio ? <DropdownAudioPlayer url={session.audio} /> : null}
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative border-l border-l-gray-lighter" style={{ minWidth: '270px' }}>
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import {
|
|||
import { fetchSessions } from 'Duck/liveSearch';
|
||||
import { Icon } from 'UI';
|
||||
|
||||
import DropdownAudioPlayer from '../../../Session/Player/ReplayPlayer/AudioPlayer';
|
||||
import ControlButton from './ControlButton';
|
||||
import Timeline from './Timeline';
|
||||
import PlayerControls from './components/PlayerControls';
|
||||
|
|
@ -179,6 +180,7 @@ function Controls(props: any) {
|
|||
toggleBottomTools={toggleBottomTools}
|
||||
bottomBlock={bottomBlock}
|
||||
disabled={disabled}
|
||||
audioUrl={session.audio}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -201,6 +203,7 @@ interface IDevtoolsButtons {
|
|||
toggleBottomTools: (blockName: number) => void;
|
||||
bottomBlock: number;
|
||||
disabled: boolean;
|
||||
audioUrl?: string;
|
||||
}
|
||||
|
||||
const DevtoolsButtons = observer(
|
||||
|
|
@ -209,6 +212,7 @@ const DevtoolsButtons = observer(
|
|||
toggleBottomTools,
|
||||
bottomBlock,
|
||||
disabled,
|
||||
audioUrl,
|
||||
}: IDevtoolsButtons) => {
|
||||
const { aiSummaryStore } = useStore();
|
||||
const { store, player } = React.useContext(PlayerContext);
|
||||
|
|
@ -247,11 +251,7 @@ const DevtoolsButtons = observer(
|
|||
};
|
||||
return (
|
||||
<>
|
||||
{isSaas ? (
|
||||
<SummaryButton
|
||||
onClick={showSummary}
|
||||
/>
|
||||
) : null}
|
||||
{isSaas ? <SummaryButton onClick={showSummary} /> : null}
|
||||
<ControlButton
|
||||
popover={
|
||||
<div className={'flex items-center gap-2'}>
|
||||
|
|
@ -349,6 +349,7 @@ const DevtoolsButtons = observer(
|
|||
label="Profiler"
|
||||
/>
|
||||
)}
|
||||
{audioUrl ? <DropdownAudioPlayer url={audioUrl} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ button {
|
|||
}
|
||||
|
||||
textarea:focus,
|
||||
input:focus {
|
||||
input:not(.ant-input-number-input):focus {
|
||||
border: solid thin $teal !important;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue