优化SFX生成面板界面
按设计稿重排音效 composer 的底部控件,并统一循环开关的选中态配色。 - 一键优化与撤销移入底部行,与时长、循环、ElevenLabs 和生成同处一排 - SFX footer 改 flex 并作用域隔离,共享的 5 列 grid 不受影响;模型 chip 起分组作用,它与生成按钮靠右 - option-cluster 的 width: 100% 是给 grid 单元格写的,在 flex 行里会把基准 宽度撑成整行再收缩,循环按钮因此被拉到 355px,改为按内容取宽 - footer 直接子 anchor 改 flex,消除块级盒与 inline-block 之间的基线空隙 - 时长按钮补时钟图标,新建音效默认改为自动时长 - 循环按钮补图标,Loop 改为「循环 开 / 关」,aria-label 同步 - 选中态由实心橙底配近白文字改为设计稿的淡橙底 + 橙字 + 橙描边;实底方案在 通用 hover 用 background 简写换掉底色后会变成白底白字,hover 变体一并写死 - 删除 sound-effect-loop 选中态的重复规则,其声明与通用规则完全相同 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -961,13 +961,20 @@ describe('ImageCanvasGenerationComposerView', () => {
|
||||
'.image-canvas-editor__generation-composer-footer',
|
||||
);
|
||||
expect(footer).toBeTruthy();
|
||||
// 底部一行:左侧一键优化与时长、循环,右侧 ElevenLabs + 生成。
|
||||
expect(footer?.children[0]?.className).toContain(
|
||||
'image-canvas-editor__sound-effect-prompt-actions',
|
||||
);
|
||||
expect(footer?.children[1]?.className).toContain(
|
||||
'image-canvas-editor__option-popover-anchor--dimensions',
|
||||
);
|
||||
expect(footer?.children[2]?.className).toContain(
|
||||
'image-canvas-editor__option-popover-anchor--model',
|
||||
'image-canvas-editor__sound-effect-loop',
|
||||
);
|
||||
expect(footer?.children[3]?.className).toContain(
|
||||
'image-canvas-editor__option-popover-anchor--model',
|
||||
);
|
||||
expect(footer?.children[4]?.className).toContain(
|
||||
'image-canvas-editor__generation-submit',
|
||||
);
|
||||
const prompt = within(panel).getByRole('textbox', { name: 'prompt' });
|
||||
@@ -988,7 +995,7 @@ describe('ImageCanvasGenerationComposerView', () => {
|
||||
expect(within(panel).queryByText('单次')).toBeNull();
|
||||
expect(
|
||||
within(panel)
|
||||
.getByRole('button', { name: 'Loop' })
|
||||
.getByRole('button', { name: '循环 关' })
|
||||
.getAttribute('aria-pressed'),
|
||||
).toBe('false');
|
||||
expect(within(panel).queryByText(/BPM/u)).toBeNull();
|
||||
@@ -1034,7 +1041,7 @@ describe('ImageCanvasGenerationComposerView', () => {
|
||||
|
||||
expect(screen.getByLabelText('当前音效时长').textContent).toBe('8');
|
||||
expect(screen.getByRole('button', { name: '音效时长 8秒' })).toBeTruthy();
|
||||
fireEvent.click(within(panel).getByRole('button', { name: 'Loop' }));
|
||||
fireEvent.click(within(panel).getByRole('button', { name: '循环 关' }));
|
||||
|
||||
fireEvent.submit(panel);
|
||||
expect(onSubmitImageGeneration).toHaveBeenCalledWith(
|
||||
@@ -1082,7 +1089,7 @@ describe('ImageCanvasGenerationComposerView', () => {
|
||||
'一键优化',
|
||||
'撤销',
|
||||
'音效时长 5秒',
|
||||
'Loop',
|
||||
'循环 关',
|
||||
'生成游戏音效',
|
||||
]) {
|
||||
expect(
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import {
|
||||
Check,
|
||||
ChevronDown,
|
||||
Clock,
|
||||
Cpu,
|
||||
Film,
|
||||
ImageIcon,
|
||||
Music,
|
||||
Repeat,
|
||||
Sparkles,
|
||||
Undo2,
|
||||
WandSparkles,
|
||||
@@ -1183,7 +1185,7 @@ function ImageCanvasAudioGenerationComposerView({
|
||||
|
||||
return (
|
||||
<form
|
||||
className="image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__generation-composer--audio"
|
||||
className="image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__generation-composer--audio image-canvas-editor__generation-composer--sound-effect"
|
||||
style={style}
|
||||
role="dialog"
|
||||
aria-label={dialogLabel}
|
||||
@@ -1255,48 +1257,48 @@ function ImageCanvasAudioGenerationComposerView({
|
||||
{soundEffectDialog.errorMessage}
|
||||
</PlatformStatusMessage>
|
||||
) : null}
|
||||
<div className="image-canvas-editor__background-music-prompt-actions image-canvas-editor__sound-effect-prompt-actions">
|
||||
<PlatformActionButton
|
||||
type="button"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
shape="pill"
|
||||
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__sound-effect-prompt-action--optimize"
|
||||
aria-label="一键优化"
|
||||
disabled={!canOptimize}
|
||||
onClick={() => {
|
||||
void controller.optimizePrompt(soundEffectDialog.id);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="image-canvas-editor__background-music-prompt-action-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<WandSparkles />
|
||||
</span>
|
||||
<span>{isAiProcessing ? '优化中' : '一键优化'}</span>
|
||||
</PlatformActionButton>
|
||||
{isUndoVisible ? (
|
||||
<div className="image-canvas-editor__generation-composer-footer">
|
||||
<div className="image-canvas-editor__background-music-prompt-actions image-canvas-editor__sound-effect-prompt-actions">
|
||||
<PlatformActionButton
|
||||
type="button"
|
||||
tone="ghost"
|
||||
tone="secondary"
|
||||
size="xs"
|
||||
shape="pill"
|
||||
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__background-music-prompt-action--undo"
|
||||
disabled={!canUndo}
|
||||
onClick={() => controller.undoPrompt(soundEffectDialog.id)}
|
||||
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__sound-effect-prompt-action--optimize"
|
||||
aria-label="一键优化"
|
||||
disabled={!canOptimize}
|
||||
onClick={() => {
|
||||
void controller.optimizePrompt(soundEffectDialog.id);
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className="image-canvas-editor__background-music-prompt-action-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Undo2 />
|
||||
<WandSparkles />
|
||||
</span>
|
||||
<span>撤销</span>
|
||||
<span>{isAiProcessing ? '优化中' : '一键优化'}</span>
|
||||
</PlatformActionButton>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="image-canvas-editor__generation-composer-footer">
|
||||
{isUndoVisible ? (
|
||||
<PlatformActionButton
|
||||
type="button"
|
||||
tone="ghost"
|
||||
size="xs"
|
||||
shape="pill"
|
||||
className="image-canvas-editor__background-music-prompt-action image-canvas-editor__background-music-prompt-action--undo"
|
||||
disabled={!canUndo}
|
||||
onClick={() => controller.undoPrompt(soundEffectDialog.id)}
|
||||
>
|
||||
<span
|
||||
className="image-canvas-editor__background-music-prompt-action-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Undo2 />
|
||||
</span>
|
||||
<span>撤销</span>
|
||||
</PlatformActionButton>
|
||||
) : null}
|
||||
</div>
|
||||
<div className="image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--dimensions">
|
||||
<PlatformInlineOptionButton
|
||||
ref={soundOptionsButtonRef}
|
||||
@@ -1307,7 +1309,13 @@ function ImageCanvasAudioGenerationComposerView({
|
||||
trailingIcon={<ChevronDown className="h-3 w-3" />}
|
||||
onClick={() => setIsSoundOptionsOpen((isOpen) => !isOpen)}
|
||||
>
|
||||
{soundOptionLabel}
|
||||
<span
|
||||
className="image-canvas-editor__sound-effect-option-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Clock />
|
||||
</span>
|
||||
<span>{soundOptionLabel}</span>
|
||||
</PlatformInlineOptionButton>
|
||||
{isSoundOptionsOpen
|
||||
? renderEditorPortal(
|
||||
@@ -1366,12 +1374,21 @@ function ImageCanvasAudioGenerationComposerView({
|
||||
</div>
|
||||
<PlatformInlineOptionButton
|
||||
className="image-canvas-editor__option-cluster image-canvas-editor__sound-effect-loop"
|
||||
aria-label="Loop"
|
||||
aria-label={`循环 ${soundLoop ? '开' : '关'}`}
|
||||
aria-pressed={soundLoop}
|
||||
disabled={isLocked}
|
||||
onClick={() => updateAudioDialog({ soundLoop: !soundLoop })}
|
||||
>
|
||||
Loop
|
||||
<span
|
||||
className="image-canvas-editor__sound-effect-option-icon"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<Repeat />
|
||||
</span>
|
||||
<span>循环</span>
|
||||
<span className="image-canvas-editor__sound-effect-loop-state">
|
||||
{soundLoop ? '开' : '关'}
|
||||
</span>
|
||||
</PlatformInlineOptionButton>
|
||||
<div className="image-canvas-editor__option-popover-anchor image-canvas-editor__option-popover-anchor--model image-canvas-editor__readonly-generation-option">
|
||||
<PlatformInlineOptionButton
|
||||
|
||||
@@ -254,7 +254,7 @@ describe('ImageCanvasGenerationDialogModel', () => {
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
soundModel: 'eleven_text_to_sound_v2',
|
||||
soundDurationMode: 'manual',
|
||||
soundDurationMode: 'auto',
|
||||
soundDurationSeconds: 5,
|
||||
soundLoop: false,
|
||||
placeholder: {
|
||||
|
||||
@@ -404,7 +404,8 @@ export function createSoundEffectGenerationDialogDraft({
|
||||
status: 'idle',
|
||||
composerOpen: true,
|
||||
soundModel: DEFAULT_SOUND_EFFECT_MODEL,
|
||||
soundDurationMode: 'manual',
|
||||
// 与设计稿一致:新建音效默认自动时长,由模型按描述决定长度。
|
||||
soundDurationMode: 'auto',
|
||||
soundDurationSeconds: DEFAULT_SOUND_EFFECT_DURATION_SECONDS,
|
||||
soundLoop: false,
|
||||
placeholder: {
|
||||
|
||||
+88
-10
@@ -7541,11 +7541,18 @@ button.image-canvas-editor__reference-chip:disabled {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/*
|
||||
* 开启态按设计稿取淡橙底 + 橙字 + 橙描边,不用实心橙底配近白文字:实底方案在通用 hover
|
||||
* 把底色换成 #f8fafc 后会变成白底白字。hover 变体必须一并写死,否则通用 hover 的
|
||||
* `background` 简写仍会把淡橙底洗掉;阴影仍由通用规则提供,悬停反馈不丢。
|
||||
*/
|
||||
.image-canvas-editor__generation-composer
|
||||
.platform-inline-option-button[aria-pressed='true'] {
|
||||
.platform-inline-option-button[aria-pressed='true'],
|
||||
.image-canvas-editor__generation-composer
|
||||
.platform-inline-option-button[aria-pressed='true']:hover:not(:disabled) {
|
||||
border-color: var(--image-canvas-brand-border-strong);
|
||||
background: var(--image-canvas-brand-fill);
|
||||
color: var(--image-canvas-brand-text-on-fill);
|
||||
background: var(--image-canvas-brand-soft);
|
||||
color: var(--image-canvas-brand-accent);
|
||||
}
|
||||
|
||||
.image-canvas-editor__generation-submit {
|
||||
@@ -16892,8 +16899,85 @@ button {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* 助手动作与时长、循环、模型和生成同处底部一行:共享 footer 的 5 列 grid 在这里换成
|
||||
* flex,作用域限定在 SFX,图片 / 视频 composer 仍走原来的 grid。
|
||||
*/
|
||||
.image-canvas-editor__generation-composer--sound-effect
|
||||
.image-canvas-editor__generation-composer-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding-top: 0.5rem;
|
||||
}
|
||||
|
||||
/*
|
||||
* `.image-canvas-editor__option-cluster` 的 `width: 100%` 是给 grid 单元格写的,在 flex
|
||||
* 行里会把基准宽度撑成整行再收缩。循环按钮是 footer 的直接子元素,必须改回按内容取宽;
|
||||
* 时长和模型包在 anchor 里,宽度由 anchor 收敛,不受影响。
|
||||
*/
|
||||
.image-canvas-editor__generation-composer--sound-effect
|
||||
.image-canvas-editor__generation-composer-footer
|
||||
> .image-canvas-editor__option-cluster {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
/* anchor 是块级盒、内部按钮是 inline-block,行盒下沿会多出基线空隙导致高度错位。 */
|
||||
.image-canvas-editor__generation-composer--sound-effect
|
||||
.image-canvas-editor__generation-composer-footer
|
||||
> .image-canvas-editor__option-popover-anchor {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 模型 chip 起分组作用:它和生成按钮被顶到右侧,左侧留给动作与参数。 */
|
||||
.image-canvas-editor__generation-composer--sound-effect
|
||||
.image-canvas-editor__option-popover-anchor--model {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* flex 布局下提交按钮硬编码的 grid-column 自动失效,无需覆盖。 */
|
||||
.image-canvas-editor__generation-composer--sound-effect
|
||||
.image-canvas-editor__generation-submit {
|
||||
height: 2.25rem;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.image-canvas-editor__sound-effect-prompt-actions {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* 共享的 label 是无 gap 的 inline-flex,加了图标和状态后必须自己撑开间距。 */
|
||||
.image-canvas-editor__generation-composer--sound-effect
|
||||
.image-canvas-editor__option-cluster
|
||||
.platform-inline-option-button__label {
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
/* 时长与循环的前置图标,尺寸对齐助手按钮里的图标。 */
|
||||
.image-canvas-editor__sound-effect-option-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.image-canvas-editor__sound-effect-option-icon svg {
|
||||
width: 0.85rem;
|
||||
height: 0.85rem;
|
||||
}
|
||||
|
||||
/* 开 / 关状态跟在「循环」后面,用弱化色,避免和标签抢读。 */
|
||||
.image-canvas-editor__sound-effect-loop-state {
|
||||
color: #94a3b8;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.image-canvas-editor__sound-effect-loop[aria-pressed='true']
|
||||
.image-canvas-editor__sound-effect-loop-state {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.image-canvas-editor__sound-effect-prompt-action--optimize:not(:disabled) {
|
||||
@@ -16920,12 +17004,6 @@ button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.image-canvas-editor__sound-effect-loop[aria-pressed='true'] {
|
||||
border-color: var(--image-canvas-brand-border-strong);
|
||||
background: var(--image-canvas-brand-soft);
|
||||
color: var(--image-canvas-brand-accent);
|
||||
}
|
||||
|
||||
/*
|
||||
* 助手动作按钮:`.platform-button` 给的是 2.9rem 高与 0.7rem/1rem 内边距,比同一面板里
|
||||
* 2.25rem 的生成按钮高出一截。这里按两级选择器压回去,避免整排按钮比主操作还重。
|
||||
|
||||
Reference in New Issue
Block a user