dbc8cb9fb2
CI Build & Deploy Pipeline / Build Staging API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (push) Has been skipped
CI Build & Deploy Pipeline / Build Production API Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Build Production Web Image (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (push) Has been skipped
CI Build & Deploy Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (push) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (push) Has been skipped
CI Build & Deploy Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Deploy Production (pull_request) Has been skipped
CI Build & Deploy Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI Build & Deploy Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Failing after 46s
CI/CD Pipeline / Frontend Lint (push) Failing after 52s
CI/CD Pipeline / Unit Tests (push) Successful in 2m18s
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Successful in 2m28s
CI/CD Pipeline / Unit Tests (pull_request) Successful in 2m40s
CI/CD Pipeline / Validate Code Quality And Tests (push) Successful in 2m57s
CI/CD Pipeline / Integration Tests (pull_request) Successful in 1m27s
CI/CD Pipeline / Integration Tests (push) Successful in 1m31s
- STATUS_CONFIG补充cancelled状态定义 - ClipData类型增加media_asset_id字段 - EditorClipList的onAdd包一层默认参数,修复类型不匹配 - handleClipUpdate改用useCallback包裹,修复exhaustive-deps - 修复no-explicit-any警告 - 字幕类型和常量抽离到types/subtitle.ts,满足react-refresh规则
237 lines
7.3 KiB
TypeScript
Executable File
237 lines
7.3 KiB
TypeScript
Executable File
/**
|
|
* 字幕样式配置面板 — Drawer 形式
|
|
* 字幕开关(手动 / ASR 自动识别)、字体大小、颜色、描边/阴影、位置、ASR 语言
|
|
*/
|
|
import React from "react";
|
|
import { Drawer, Slider, ColorPicker, Select } from "antd";
|
|
import type { Color } from "antd/es/color-picker";
|
|
import type { SubtitleMode, SubtitleStyleConfig } from "../types/subtitle";
|
|
import { DEFAULT_SUBTITLE_STYLE } from "../types/subtitle";
|
|
|
|
/* ──────────── 选项常量 ──────────── */
|
|
|
|
const POSITION_OPTIONS = [
|
|
{ value: "top", label: "顶部" },
|
|
{ value: "center", label: "居中" },
|
|
{ value: "bottom", label: "底部" },
|
|
];
|
|
|
|
const FONT_OPTIONS = [
|
|
"思源黑体",
|
|
"思源宋体",
|
|
"苹方",
|
|
"PingFang",
|
|
"微软雅黑",
|
|
"楷体",
|
|
"华康俪金黑",
|
|
];
|
|
|
|
const ANIMATION_OPTIONS = [
|
|
{ value: "none", label: "无" },
|
|
{ value: "fade", label: "淡入淡出" },
|
|
{ value: "slide", label: "滑动" },
|
|
{ value: "typewriter", label: "打字机" },
|
|
];
|
|
|
|
const ASR_LANGUAGE_OPTIONS = [
|
|
{ value: "zh", label: "中文" },
|
|
{ value: "en", label: "English" },
|
|
];
|
|
|
|
/* ──────────── Props ──────────── */
|
|
|
|
interface SubtitleStylePanelProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
config: SubtitleStyleConfig;
|
|
onChange: (config: SubtitleStyleConfig) => void;
|
|
}
|
|
|
|
const SubtitleStylePanel: React.FC<SubtitleStylePanelProps> = ({
|
|
open,
|
|
onClose,
|
|
config,
|
|
onChange,
|
|
}) => {
|
|
const update = (partial: Partial<SubtitleStyleConfig>) => {
|
|
onChange({ ...config, ...partial });
|
|
};
|
|
|
|
return (
|
|
<Drawer
|
|
title="💬 字幕样式配置"
|
|
placement="right"
|
|
width={380}
|
|
open={open}
|
|
onClose={onClose}
|
|
className="subtitle-style-drawer"
|
|
>
|
|
{/* ── 字幕开关 ── */}
|
|
<div className="sub-field">
|
|
<div className="sub-toggle-row">
|
|
<span className="sub-label">启用字幕</span>
|
|
<div
|
|
className={`ep-toggle${config.enabled ? " active" : ""}`}
|
|
onClick={() => update({ enabled: !config.enabled })}
|
|
>
|
|
<div className="ep-toggle-knob" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{config.enabled && (
|
|
<>
|
|
{/* ── 模式切换 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">字幕来源</label>
|
|
<div className="sub-mode-switch">
|
|
<button
|
|
className={`sub-mode-btn${config.mode === "manual" ? " active" : ""}`}
|
|
onClick={() => update({ mode: "manual" })}
|
|
>
|
|
✏️ 手动输入
|
|
</button>
|
|
<button
|
|
className={`sub-mode-btn${config.mode === "asr" ? " active" : ""}`}
|
|
onClick={() => update({ mode: "asr" })}
|
|
>
|
|
🤖 ASR 识别
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── ASR 语言(仅 ASR 模式) ── */}
|
|
{config.mode === "asr" && (
|
|
<div className="sub-field">
|
|
<label className="sub-label">识别语言</label>
|
|
<Select
|
|
className="sub-select"
|
|
value={config.asrLanguage}
|
|
onChange={(v) => update({ asrLanguage: v })}
|
|
options={ASR_LANGUAGE_OPTIONS}
|
|
popupMatchSelectWidth={false}
|
|
/>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── 字体大小 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">
|
|
字体大小 <span className="sub-value">{config.fontSize}px</span>
|
|
</label>
|
|
<Slider
|
|
min={12}
|
|
max={48}
|
|
value={config.fontSize}
|
|
onChange={(v) => update({ fontSize: v })}
|
|
/>
|
|
</div>
|
|
|
|
{/* ── 字体颜色 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">字体颜色</label>
|
|
<div className="sub-color-row">
|
|
<ColorPicker
|
|
value={config.fontColor}
|
|
onChange={(_color: Color, hex: string) =>
|
|
update({ fontColor: hex })
|
|
}
|
|
showText
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── 字体 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">字体</label>
|
|
<Select
|
|
className="sub-select"
|
|
value={config.font}
|
|
onChange={(v) => update({ font: v })}
|
|
options={FONT_OPTIONS.map((f) => ({ value: f, label: f }))}
|
|
popupMatchSelectWidth={false}
|
|
/>
|
|
</div>
|
|
|
|
{/* ── 字幕位置 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">位置</label>
|
|
<div className="sub-position-group">
|
|
{POSITION_OPTIONS.map((opt) => (
|
|
<button
|
|
key={opt.value}
|
|
className={`sub-position-btn${config.position === opt.value ? " active" : ""}`}
|
|
onClick={() =>
|
|
update({
|
|
position: opt.value as SubtitleStyleConfig["position"],
|
|
})
|
|
}
|
|
>
|
|
{opt.label}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── 描边 / 阴影 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">效果</label>
|
|
<div className="sub-effect-btns">
|
|
<button
|
|
className={`sub-effect-btn${config.stroke ? " active" : ""}`}
|
|
onClick={() => update({ stroke: !config.stroke })}
|
|
>
|
|
S 描边
|
|
</button>
|
|
<button
|
|
className={`sub-effect-btn${config.shadow ? " active" : ""}`}
|
|
onClick={() => update({ shadow: !config.shadow })}
|
|
>
|
|
☁ 阴影
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── 动画 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">动画效果</label>
|
|
<Select
|
|
className="sub-select"
|
|
value={config.animation}
|
|
onChange={(v) => update({ animation: v })}
|
|
options={ANIMATION_OPTIONS.map((o) => ({
|
|
value: o.value,
|
|
label: o.label,
|
|
}))}
|
|
popupMatchSelectWidth={false}
|
|
/>
|
|
</div>
|
|
|
|
{/* ── 预览 ── */}
|
|
<div className="sub-field">
|
|
<label className="sub-label">预览</label>
|
|
<div className="sub-preview-box">
|
|
<span
|
|
className="sub-preview-text"
|
|
style={{
|
|
fontSize: `${Math.min(config.fontSize, 28)}px`,
|
|
color: config.fontColor,
|
|
fontFamily: config.font,
|
|
WebkitTextStroke: config.stroke ? "1px #000" : undefined,
|
|
textShadow: config.shadow
|
|
? "2px 2px 4px rgba(0,0,0,0.8)"
|
|
: undefined,
|
|
}}
|
|
>
|
|
这是一段字幕预览
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</Drawer>
|
|
);
|
|
};
|
|
|
|
export default SubtitleStylePanel;
|