6ff8d48c15
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 2s
CI/CD Pipeline / Check push changed paths (push) Successful in 4s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m23s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 2m39s
CI/CD Pipeline / Build Staging API Image (push) Successful in 2m45s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m9s
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Successful in 4m10s
CI/CD Pipeline / Validate - Style (push) Successful in 4m33s
CI/CD Pipeline / Integration Tests (push) Successful in 5m1s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 1m42s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 2m32s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 6m47s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 3m10s
CI/CD Pipeline / Validate - Security (push) Successful in 10m36s
CI/CD Pipeline / Unit Tests (push) Successful in 10m43s
CI/CD Pipeline / Production Browser E2E (push) Has been skipped
CI/CD Pipeline / Build Production Worker Image (push) Failing after 60h28m16s
CI/CD Pipeline / Frontend Lint (push) Failing after 60h39m0s
CI/CD Pipeline / Retag skipped Staging API Image (push) Failing after 60h35m47s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 60h38m38s
CI/CD Pipeline / PR Build API Image (push) Failing after 60h38m38s
CI/CD Pipeline / Build Production Web Image (push) Failing after 60h27m50s
CI/CD Pipeline / CI Gate (push) Failing after 60h27m50s
CI/CD Pipeline / Canary Release to Production (push) Failing after 60h27m46s
CI/CD Pipeline / Retag skipped Staging Web Image (push) Failing after 60h35m46s
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Failing after 60h35m46s
CI/CD Pipeline / PR Build Web Image (push) Failing after 60h38m38s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 60h38m40s
CI/CD Pipeline / Deploy Production (push) Failing after 60h27m46s
CI/CD Pipeline / Build Production API Image (push) Failing after 60h27m50s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
196 lines
5.7 KiB
TypeScript
196 lines
5.7 KiB
TypeScript
import React from "react"
|
|
import {
|
|
PlayCircleOutlined,
|
|
PauseCircleOutlined,
|
|
AudioOutlined,
|
|
AudioMutedOutlined,
|
|
} from "@ant-design/icons"
|
|
import { PreviewProgressBar } from "./PreviewProgressBar"
|
|
|
|
interface PreviewControlsProps {
|
|
isPlaying: boolean
|
|
onTogglePlay: () => void
|
|
muted: boolean
|
|
onToggleMute: () => void
|
|
hasSegments: boolean
|
|
segmentIndex: number
|
|
segmentCount: number
|
|
currentTime: number
|
|
totalDuration: number
|
|
onSeek: (time: number) => void
|
|
compact?: boolean
|
|
}
|
|
|
|
/**
|
|
* 播放控制 UI 组件(静音按钮 / 片段指示器 / 中央播放按钮 / 底部毛玻璃控制条)
|
|
*/
|
|
export const PreviewControls: React.FC<PreviewControlsProps> = ({
|
|
isPlaying,
|
|
onTogglePlay,
|
|
muted,
|
|
onToggleMute,
|
|
hasSegments,
|
|
segmentIndex,
|
|
segmentCount,
|
|
currentTime,
|
|
totalDuration,
|
|
onSeek,
|
|
compact = false,
|
|
}) => {
|
|
return (
|
|
<>
|
|
{/* 静音/有声切换(#1741):左上角 */}
|
|
{hasSegments && (
|
|
<button
|
|
type="button"
|
|
aria-label={muted ? "取消静音" : "静音"}
|
|
title={muted ? "取消静音" : "静音"}
|
|
onClick={onToggleMute}
|
|
style={{
|
|
position: "absolute",
|
|
top: 8,
|
|
left: 8,
|
|
width: compact ? 26 : 30,
|
|
height: compact ? 26 : 30,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
background: "rgba(0,0,0,0.45)",
|
|
backdropFilter: "blur(8px)",
|
|
WebkitBackdropFilter: "blur(8px)",
|
|
border: "1px solid rgba(255,255,255,0.1)",
|
|
borderRadius: "50%",
|
|
color: muted ? "rgba(255,255,255,0.45)" : "rgba(255,255,255,0.92)",
|
|
fontSize: compact ? 13 : 15,
|
|
cursor: "pointer",
|
|
zIndex: 10,
|
|
padding: 0,
|
|
transition: "background 0.15s, color 0.15s",
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = "rgba(0,0,0,0.65)"
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = "rgba(0,0,0,0.45)"
|
|
}}
|
|
>
|
|
{muted ? <AudioMutedOutlined /> : <AudioOutlined />}
|
|
</button>
|
|
)}
|
|
|
|
{/* 片段指示器 — 右上角胶囊 */}
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
top: 8,
|
|
right: 8,
|
|
background: "rgba(0,0,0,0.45)",
|
|
backdropFilter: "blur(8px)",
|
|
WebkitBackdropFilter: "blur(8px)",
|
|
color: "rgba(255,255,255,0.9)",
|
|
fontSize: compact ? 9 : 10,
|
|
fontWeight: 500,
|
|
padding: compact ? "1px 6px" : "2px 8px",
|
|
borderRadius: 999,
|
|
zIndex: 10,
|
|
border: "1px solid rgba(255,255,255,0.1)",
|
|
letterSpacing: 0.3,
|
|
}}
|
|
>
|
|
{`${segmentIndex + 1} / ${segmentCount}`}
|
|
</div>
|
|
|
|
{/* 中央播放按钮 */}
|
|
{!isPlaying && (
|
|
<button
|
|
onClick={onTogglePlay}
|
|
style={{
|
|
position: "absolute",
|
|
top: "50%",
|
|
left: "50%",
|
|
transform: "translate(-50%, -50%)",
|
|
background: "rgba(0,0,0,0.45)",
|
|
backdropFilter: "blur(12px)",
|
|
WebkitBackdropFilter: "blur(12px)",
|
|
border: "1px solid rgba(255,255,255,0.15)",
|
|
borderRadius: "50%",
|
|
width: 52,
|
|
height: 52,
|
|
cursor: "pointer",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
color: "#fff",
|
|
fontSize: 26,
|
|
zIndex: 10,
|
|
transition: "transform 0.2s ease, background 0.2s ease",
|
|
boxShadow: "0 4px 20px rgba(0,0,0,0.4)",
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.transform = "translate(-50%, -50%) scale(1.08)"
|
|
e.currentTarget.style.background = "rgba(0,0,0,0.6)"
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.transform = "translate(-50%, -50%) scale(1)"
|
|
e.currentTarget.style.background = "rgba(0,0,0,0.45)"
|
|
}}
|
|
>
|
|
<PlayCircleOutlined />
|
|
</button>
|
|
)}
|
|
|
|
{/* 控制条 — 手机风格毛玻璃 */}
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
bottom: 0,
|
|
left: 0,
|
|
right: 0,
|
|
display: "flex",
|
|
alignItems: "center",
|
|
gap: compact ? 6 : 10,
|
|
padding: compact ? "8px 10px 10px" : "12px 16px 16px",
|
|
background: "linear-gradient(transparent, rgba(0,0,0,0.7))",
|
|
backdropFilter: "blur(4px)",
|
|
WebkitBackdropFilter: "blur(4px)",
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
<button
|
|
onClick={onTogglePlay}
|
|
style={{
|
|
background: "rgba(255,255,255,0.15)",
|
|
border: "none",
|
|
color: "#fff",
|
|
fontSize: compact ? 14 : 16,
|
|
cursor: "pointer",
|
|
width: compact ? 26 : 32,
|
|
height: compact ? 26 : 32,
|
|
borderRadius: "50%",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
flexShrink: 0,
|
|
transition: "background 0.15s",
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.background = "rgba(255,255,255,0.25)"
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.background = "rgba(255,255,255,0.15)"
|
|
}}
|
|
>
|
|
{isPlaying ? <PauseCircleOutlined /> : <PlayCircleOutlined />}
|
|
</button>
|
|
|
|
<PreviewProgressBar
|
|
currentTime={currentTime}
|
|
totalDuration={totalDuration}
|
|
onSeek={onSeek}
|
|
compact={compact}
|
|
/>
|
|
</div>
|
|
</>
|
|
)
|
|
}
|