UI编辑器一批细节优化 #278

Merged
kdletters merged 44 commits from fix/ui-editor-minor-adjusts into master 2026-09-08 11:56:36 +08:00
7 changed files with 49 additions and 89 deletions
Showing only changes of commit 933ddca561 - Show all commits
@@ -1754,8 +1754,6 @@ for (const snippet of [
"'write_game_creator_app_config'",
'aria-label="运行时配置"',
'LLM API Key',
'External Editor Base URL',
'External Editor API Key',
'runtime_config.save',
"'/run:运行自检,启动本地 HTTP 预览并载入客户端运行视图'",
"'activate_local_game_preview'",
@@ -1791,6 +1789,19 @@ for (const snippet of [
}
}
for (const snippet of [
"label: '连接与工具'",
"id: 'connections'",
"activeSection === 'connections'",
'aria-label="外部连接"',
]) {
if (appSource.includes(snippet)) {
throw new Error(
`AI game creator shell removed settings page was reintroduced: ${snippet}`,
);
}
}
for (const script of [
'ai-game-creator-shell:dev',
'ai-game-creator-shell:dev-server',
@@ -1,6 +1,5 @@
import {
Bot,
Cable,
CheckCircle2,
CircleAlert,
Info,
@@ -91,7 +90,6 @@ const defaultRuntimeConfigDraft: GameCreatorAppConfig = {
type RuntimeSettingsSection =
| 'general'
| 'agents'
| 'connections'
| 'extensions'
| 'advanced'
| 'about';
@@ -116,12 +114,6 @@ const runtimeSettingsSections = [
description: '按角色覆盖默认模型',
icon: Bot,
},
{
id: 'connections',
label: '连接与工具',
description: '外部服务',
icon: Cable,
},
{
id: 'extensions',
label: '扩展',
@@ -1452,57 +1444,6 @@ export function RuntimeConfigDialog({
})}
</div>
) : null}
{activeSection === 'connections' ? (
<section
className="runtime-settings-section"
aria-label="外部连接"
>
<header className="runtime-settings-section-header">
<div>
<h3></h3>
<p>使</p>
</div>
</header>
{allowAdvancedExternalEditorConfig ? (
<div className="runtime-settings-fields">
<label>
External Editor Base URL
<input
aria-label="External Editor Base URL"
value={runtimeConfigDraft.editorApi.baseUrl}
onChange={(event) =>
setRuntimeConfigDraft((current) => ({
...current,
editorApi: {
...current.editorApi,
baseUrl: event.currentTarget.value,
},
}))
}
/>
</label>
<label>
External Editor API Key
<input
aria-label="External Editor API Key"
autoComplete="off"
type="password"
value={runtimeConfigDraft.editorApi.apiKey}
onChange={(event) =>
setRuntimeConfigDraft((current) => ({
...current,
editorApi: {
...current.editorApi,
apiKey: event.currentTarget.value,
},
}))
}
/>
</label>
</div>
) : null}
</section>
) : null}
{activeSection === 'extensions' ? (
<section
className="runtime-settings-section runtime-settings-extensions"
@@ -514,7 +514,14 @@ export function PreviewWorkspace({
{(actions) => (
<div
className="absolute bottom-3 right-3 z-10 flex items-center gap-1 rounded-lg border border-(--platform-subpanel-border) bg-white/90 p-1 shadow-lg"
data-ui-editor-zoom-controls
onMouseDown={(event) => {
if (
event.target instanceof Element &&
event.target.closest('button')
) {
event.preventDefault();
}
}}
>
<button
type="button"
@@ -21,11 +21,10 @@ function isEditableTarget(target: EventTarget | null) {
function isInteractiveTarget(target: EventTarget | null) {
const element = target instanceof Element ? target : null;
if (element?.closest('[data-ui-editor-zoom-controls]')) return false;
return Boolean(
(target instanceof HTMLElement && target.isContentEditable) ||
element?.closest(
'button, a, input:not([type="range"]), textarea, select, [contenteditable="true"], [role="button"], [role="dialog"], [aria-modal="true"]',
'button, a, input, textarea, select, [contenteditable="true"], [role="button"], [role="dialog"], [aria-modal="true"]',
),
);
}
@@ -832,7 +832,7 @@ export function registerPublishedRuntimeSettingsTests() {
expect(
screen.getByLabelText('LLM API Key').getAttribute('autocomplete'),
).toBe('off');
fireEvent.click(screen.getByRole('button', { name: /连接与工具/ }));
expect(screen.queryByRole('button', { name: /连接与工具/ })).toBeNull();
expect(screen.queryByLabelText('External Editor Base URL')).toBeNull();
expect(screen.queryByLabelText('External Editor API Key')).toBeNull();
fireEvent.click(screen.getByRole('button', { name: /Agent 模型/ }));
@@ -1007,7 +1007,7 @@ export function registerPublishedRuntimeSettingsTests() {
target: { value: 'ark' },
},
);
fireEvent.click(screen.getByRole('button', { name: /连接与工具/ }));
expect(screen.queryByRole('button', { name: /连接与工具/ })).toBeNull();
expect(screen.queryByLabelText('External Editor Base URL')).toBeNull();
expect(screen.queryByLabelText('External Editor API Key')).toBeNull();
fireEvent.click(
@@ -1166,7 +1166,7 @@ export function registerPublishedRuntimeSettingsTests() {
'value',
'12000',
);
fireEvent.click(screen.getByRole('button', { name: /连接与工具/ }));
expect(screen.queryByRole('button', { name: /连接与工具/ })).toBeNull();
expect(screen.queryByLabelText('External Editor Base URL')).toBeNull();
expect(screen.queryByLabelText('External Editor API Key')).toBeNull();
fireEvent.click(
@@ -60,30 +60,32 @@ describe('ui editor keyboard shortcuts', () => {
window.removeEventListener('keydown', listener);
});
it('keeps node deletion available when the zoom slider has focus', () => {
const slider = document.createElement('input');
slider.type = 'range';
document.body.append(slider);
const deleteNode = vi.fn(() => ({ ok: true }));
const listener = (event: KeyboardEvent) =>
handleUiEditorKeyDown(event, {
selectedNodeId: 'node-1',
activeImageId: 'page-1',
deleteNode,
historyUndo: vi.fn(() => true),
historyRedo: vi.fn(() => true),
});
window.addEventListener('keydown', listener);
it.each(['Delete', 'Backspace'])(
'does not delete a node when the zoom slider has focus (%s)',
(key) => {
const slider = document.createElement('input');
slider.type = 'range';
document.body.append(slider);
const deleteNode = vi.fn(() => ({ ok: true }));
const listener = (event: KeyboardEvent) =>
handleUiEditorKeyDown(event, {
selectedNodeId: 'node-1',
activeImageId: 'page-1',
deleteNode,
historyUndo: vi.fn(() => true),
historyRedo: vi.fn(() => true),
});
window.addEventListener('keydown', listener);
fireEvent.keyDown(slider, { key: 'Delete' });
fireEvent.keyDown(slider, { key });
expect(deleteNode).toHaveBeenCalledWith('node-1', 'page-1');
window.removeEventListener('keydown', listener);
});
expect(deleteNode).not.toHaveBeenCalled();
window.removeEventListener('keydown', listener);
},
);
it('keeps node deletion available after a zoom button is clicked', () => {
it('keeps node deletion available after zoom button focus is released', () => {
const zoomControls = document.createElement('div');
zoomControls.setAttribute('data-ui-editor-zoom-controls', '');
const zoomIn = document.createElement('button');
zoomControls.append(zoomIn);
document.body.append(zoomControls);
@@ -99,7 +101,7 @@ describe('ui editor keyboard shortcuts', () => {
window.addEventListener('keydown', listener);
fireEvent.click(zoomIn);
fireEvent.keyDown(zoomIn, { key: 'Delete' });
fireEvent.keyDown(window, { key: 'Delete' });
expect(deleteNode).toHaveBeenCalledWith('node-1', 'page-1');
window.removeEventListener('keydown', listener);
@@ -11,7 +11,7 @@
- 保留 ```+` 按钮。点击轨道可跳转,拖动滑块实时更新画布和百分比。
- 缩放以视口中心为中心,继续支持 Ctrl/Cmd + 滚轮缩放。
- 滑杆支持方向键、Home、End;百分比文本可点击编辑并在失焦时限制到有效范围。
- 缩放滑杆即使处于焦点,也不得拦截 UI 节点的 Delete/Backspace 快捷删除
- 缩放滑杆获得焦点时,Delete/Backspace 不触发 UI 节点快捷删除;缩放按钮鼠标点击不改变快捷键焦点
- Windows/Linux 使用 `Ctrl`macOS 使用 `Cmd``+``=``NumpadAdd` 放大,`-``NumpadSubtract` 缩小,允许按键重复时连续缩放。
- 快捷键直接复用现有 `+`/`` 按钮动作:以视口中心为锚点,按 `×1.16`/`×0.86` 改变缩放。
- 鼠标悬停在预览画布上,或预览画布已获得键盘焦点时,快捷键生效。预览画布需提供可聚焦语义与无障碍名称。