@@ -72,16 +82,25 @@ export function GameRunVersionPicker({
className="game-run-version-trigger"
aria-haspopup="listbox"
aria-expanded={open}
- aria-label={`当前版本:${formatIterationVersionLabel(currentVersion)}`}
+ aria-label={`当前版本:${formatIterationVersionLabel(
+ currentVersion,
+ currentVersionIndex,
+ )}`}
onClick={() => setOpen((current) => !current)}
>
{/*
- 版本名可能很长(`初始版本 · 2026/9/19 02:10:03`)。按钮是 flex 容器,直接放文本节点
- 时 `text-overflow: ellipsis` 不生效(匿名 flex item 不参与父级省略),所以套一层
- span 由它省略(见样式里的 `.game-run-version-trigger-label`)。
+ 按钮是 flex 容器,直接放文本节点时 `text-overflow: ellipsis` 不生效(匿名 flex item
+ 不参与父级省略),所以序号与括号里那一截各套一层 span(见样式里的
+ `.game-run-version-trigger-label`)。分两层还让「空间不够只留 `版本 1`」成为一次
+ `display: none`——顶栏窄到放不下完整标识时收掉的是括号那一层,不是整枚入口。
*/}
- {formatIterationVersionLabel(currentVersion)}
+
+ {formatIterationVersionTitle(currentVersionIndex)}
+
+
+ ({formatIterationVersionDetail(currentVersion)})
+
{open
@@ -106,7 +125,7 @@ export function GameRunVersionPicker({
: '0',
}}
>
- {versions.map((version) => {
+ {versions.map((version, index) => {
const selected = version.versionId === currentVersion.versionId;
return (
);
diff --git a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts
index e6dabc448..f59a2af60 100644
--- a/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts
+++ b/apps/ai-game-creator-shell/src/features/resource-canvas/resourceCanvasVersionBindingModel.ts
@@ -20,7 +20,20 @@ export const ITERATION_VERSION_REASON_LABELS: Record<
};
/**
- * 版本在 UI 里的可读标识:创建原因 + 创建时间。
+ * 版本序号:`manifest.versions[]` 的下标 + 1。
+ *
+ * 与资源画布「项目版本」卡片的 `版本 N`(`resourceProjectionModel` 里同一份下标口径)是同一个
+ * 编号——两处都按 manifest 里版本的落盘顺序数,不要在别处另起一套编号。
+ */
+export function formatIterationVersionTitle(ordinalIndex: number) {
+ return `版本 ${ordinalIndex + 1}`;
+}
+
+/**
+ * 版本标识里「括号里那一截」:创建原因 + 创建时间。
+ *
+ * 顶栏宽度不够时整截收起、只留 `版本 N`(见 `workbenchToolbarModel` 的档位),所以它是独立
+ * 一段文本,而不是拼在序号里的后缀。
*
* `GameIterationVersion.createdAt` 的单位是 **Unix 秒**(写入侧为
* `project/manifest.rs` 的 `created_at: unix_timestamp()`,而 `unix_timestamp()` 取
@@ -28,7 +41,7 @@ export const ITERATION_VERSION_REASON_LABELS: Record<
* 渲染成 1970 年(例如 1788075047 会显示成 1970/1/22)。
* 同一约定在 `ResourceAssetDeleteDialog` 与待确认资源编辑的创建时间标签里一致使用。
*/
-export function formatIterationVersionLabel(
+export function formatIterationVersionDetail(
version: Pick
,
) {
const reason =
@@ -41,6 +54,21 @@ export function formatIterationVersionLabel(
return `${reason} · ${time}`;
}
+/**
+ * 版本在 UI 里的完整标识:`版本 1(智能体修订 · 2026/10/3 14:21:05)`。
+ *
+ * 视觉上空间不够时可以只显示 `formatIterationVersionTitle`,但可访问名、菜单项与排障文案一律
+ * 用这一条完整的——收起来的是排版,不是信息。
+ */
+export function formatIterationVersionLabel(
+ version: Pick,
+ ordinalIndex: number,
+) {
+ return `${formatIterationVersionTitle(ordinalIndex)}(${formatIterationVersionDetail(
+ version,
+ )})`;
+}
+
/**
* C5 / C7 卡片边框:一个资源是否正在被「当前版本」使用。
*
diff --git a/apps/ai-game-creator-shell/src/styles.css b/apps/ai-game-creator-shell/src/styles.css
index 74d8e5e41..0266782c6 100644
--- a/apps/ai-game-creator-shell/src/styles.css
+++ b/apps/ai-game-creator-shell/src/styles.css
@@ -6212,14 +6212,19 @@ iframe.preview-frame {
min-height: 48px;
padding: 8px 12px;
background: transparent;
+ /* 一行到底:空间不够不再换行,而是按 `data-layout` 降级(顺序与判据见
+ * `view/project-development/workbenchToolbarModel.ts`)。`overflow: hidden` 有两个用处:
+ * 溢出的那一截不会画到对话列上;`scrollWidth` 仍然把溢出的内容算进内容宽度,档位判定靠它。 */
+ flex-wrap: nowrap;
+ overflow: hidden;
}
-/* 左侧控件组:「资源管理 / 运行」分段 + 紧贴其后的「播放」。
+/* 左侧控件组:只剩「资源管理 / 运行」分段(播放并进「运行」页签,见 index.tsx)。
* 整组在工具条里左对齐,取代播放按钮原先的居中悬浮(absolute + translateX(-50%))。 */
.game-workbench-view-tabs {
+ flex: 0 0 auto;
display: flex;
align-items: center;
- flex-wrap: wrap;
gap: 7px;
}
@@ -6227,10 +6232,16 @@ iframe.preview-frame {
.game-workbench-view-actions {
display: flex;
align-items: center;
- flex-wrap: wrap;
gap: 7px;
}
+/* 动作区:宽度够时按内容排布并贴右,不够时由 `min-width: 0` 让出宽度给降级判定
+ * (不设它的话 flex 子项不会缩到内容宽度以下,溢出永远检测不到)。 */
+.game-workbench-view-actions {
+ flex: 0 1 auto;
+ min-width: 0;
+}
+
.game-workbench-tabs {
padding: 3px;
border: 1px solid var(--platform-subpanel-border);
@@ -6239,6 +6250,10 @@ iframe.preview-frame {
}
.game-resource-sort-tabs {
+ /* 与版本入口同一课:`overflow: hidden` 的 flex 子项可以缩到 0,把溢出「吃掉」,
+ * 顶栏的档位判定就永远量不到真实溢出(实测窄宽度下「依赖 / 类型」被压成 0 宽)。
+ * 这一组不参与收缩。 */
+ flex: 0 0 auto;
flex-wrap: nowrap;
gap: 0;
padding: 0;
@@ -6267,7 +6282,8 @@ iframe.preview-frame {
border-color: transparent;
}
-/* 左侧控件组里的按钮(目前只有「播放」)与分段控件、右侧动作区共用同一套基础外观。 */
+/* 模式分段(含并进来的「运行 ▶」)、动作区按钮(含「更多」里的条目共用同一套基础外观的入口
+ * 那一枚)共用同一套基础外观。 */
.game-workbench-tabs button,
.game-workbench-view-tabs button,
.game-workbench-view-actions button {
@@ -6320,21 +6336,84 @@ iframe.preview-frame {
color: var(--platform-button-secondary-text);
}
-/* 「播放」与「资源管理 / 运行」同组,外观仍按主按钮走;禁用时退回次级按钮。 */
-.game-workbench-view-tabs .game-workbench-play-button {
- position: static;
- border-color: var(--platform-button-primary-border);
- background: var(--platform-button-primary-fill);
- color: var(--platform-button-primary-text);
- transform: none;
+/*
+ * 顶栏降级档位:`data-layout` 由 `useWorkbenchToolbarLayout` 按实测内容宽度写在工具条上,
+ * 取 `full` / `compact-version` / `collapsed-actions`,顺序与判据在 `workbenchToolbarModel`。
+ * 这里只声明每一档「藏什么」——藏起来的是排版,不是信息:可访问名与菜单项都保留完整口径。
+ */
+.game-workbench-toolbar[data-layout='compact-version']
+ .game-run-version-trigger-detail {
+ display: none;
}
-.game-workbench-view-tabs .game-workbench-play-button:disabled {
- border-color: var(--platform-surface-border);
- background: var(--platform-button-secondary-fill);
- color: var(--platform-text-muted);
+/* 「更多」那一档同样只留 `版本 N`:它比 `compact-version` 更窄,没有理由把括号段放回来。 */
+.game-workbench-toolbar[data-layout='collapsed-actions']
+ .game-run-version-trigger-detail {
+ display: none;
+}
+
+/* `打开项目目录 / 资源面板 / 整理画布`:收进「更多」的那一档才藏,且与菜单互为开关——
+ * 默认(未量到宽度、'full')保持行内形态,与脚本关闭的环境一致。 */
+.game-workbench-toolbar[data-layout='collapsed-actions']
+ .game-workbench-foldable-action {
+ display: none;
+}
+
+/* 「更多」下拉:只在动作收进菜单的那一档出现。 */
+.game-workbench-more-actions {
+ display: none;
+}
+
+.game-workbench-toolbar[data-layout='collapsed-actions']
+ .game-workbench-more-actions {
+ display: inline-flex;
+ align-items: center;
+}
+
+/* 最后一档仍放不下(视口远小于 1280 合同宽度)时改成右对齐:溢出的那一截甩到左边被裁掉,
+ * 而不是把钉在最右的版本入口一起裁没。放得下时这条与 `margin-left: auto` 结果相同。 */
+.game-workbench-toolbar[data-layout='collapsed-actions']
+ .game-workbench-view-actions {
+ justify-content: flex-end;
+}
+
+.game-workbench-more-menu {
+ display: grid;
+ gap: 4px;
+ min-width: 11rem;
+ padding: 6px;
+ border: 1px solid var(--platform-surface-border);
+ border-radius: 10px;
+ background: var(--platform-subpanel-fill);
+ box-shadow: 0 16px 34px rgb(75 47 32 / 18%);
+ z-index: 80;
+}
+
+.game-workbench-more-menu button {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ min-height: 34px;
+ gap: 8px;
+ padding: 0 9px;
+ border: 0;
+ border-radius: 7px;
+ background: transparent;
+ color: var(--platform-text-strong);
+ font-size: 12px;
+ font-weight: 700;
+ cursor: pointer;
+}
+
+.game-workbench-more-menu button:hover,
+.game-workbench-more-menu button:focus-visible {
+ background: var(--platform-nav-item-hover-fill);
+ outline: 0;
+}
+
+.game-workbench-more-menu button:disabled {
cursor: not-allowed;
- opacity: 0.6;
+ opacity: 0.45;
}
.game-workbench-view-actions .game-workbench-inspect-button.is-active {
@@ -8679,18 +8758,46 @@ iframe.preview-frame {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
+ /* 固定在最右:它是动作区的最后一枚,且行内空间富余时贴右缘(`margin-left: auto` 吃掉
+ * 左侧所有剩余空间)。空间不够时它自己先收成 `版本 N`(`data-layout` 档位),
+ * 而不是被换行甩到第二行。 */
+ margin-left: auto;
}
.game-run-version-trigger {
max-width: min(18rem, 60vw);
+ /* 默认**不参与收缩**(`max-content`):收缩 + 省略号会把溢出「吃掉」,让顶栏的档位判定
+ * (`workbenchToolbarModel`)永远量不到真实溢出——窄到该降档时它却原地把版本名截断。
+ * 只有降到最后一档、动作已经全收进「更多」时才允许它省略兜底(见下面那条选择器)。 */
+ min-width: max-content;
+}
+
+.game-workbench-toolbar[data-layout='collapsed-actions']
+ .game-run-version-trigger {
min-width: 0;
}
/*
* 版本名单独一层才省得掉:按钮是 flex 容器,文本直接挂在按钮上时 `text-overflow`
* 落在匿名 flex item 上、不生效(见 `GameRunVersionPicker` 里的注释)。
+ * 层里再分「序号」与「括号那一截」两层:窄宽度收掉的是后者(`display: none`),
+ * 序号永远留着。
*/
.game-run-version-trigger-label {
+ display: inline-flex;
+ align-items: baseline;
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.game-run-version-trigger-title {
+ flex: 0 0 auto;
+}
+
+.game-run-version-trigger-detail {
+ flex: 0 1 auto;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
@@ -8757,13 +8864,13 @@ iframe.preview-frame {
}
/*
- * 画面右下角的「全屏预览」。压在游戏画面上,所以用深色半透明底 + 白图标:任何游戏配色下都
- * 看得清,也不在画面中间抢位置。全屏那一格还是它自己(`:fullscreen` 铺满屏幕),所以这枚按钮
- * 在全屏里照旧可用,用户点它就能退出来。
+ * 画面右下角两枚浮起控件:「刷新运行画面」(左)与「全屏预览」(右)。压在游戏画面上,所以用
+ * 深色半透明底 + 白图标:任何游戏配色下都看得清,也不在画面中间抢位置。全屏那一格还是它自己
+ * (`:fullscreen` 铺满屏幕),所以两枚按钮在全屏里照旧可用——刷新照旧刷画面,点全屏那枚就退出。
*/
-.game-run-preview-fullscreen {
+.game-run-preview-fullscreen,
+.game-run-preview-refresh {
position: absolute;
- right: 10px;
bottom: 10px;
z-index: 2;
display: inline-flex;
@@ -8779,8 +8886,18 @@ iframe.preview-frame {
backdrop-filter: blur(6px);
}
+.game-run-preview-refresh {
+ right: 50px;
+}
+
+.game-run-preview-fullscreen {
+ right: 10px;
+}
+
.game-run-preview-fullscreen:hover,
-.game-run-preview-fullscreen:focus-visible {
+.game-run-preview-fullscreen:focus-visible,
+.game-run-preview-refresh:hover,
+.game-run-preview-refresh:focus-visible {
background: rgb(12 14 20 / 84%);
outline: 0;
}
@@ -10209,15 +10326,6 @@ iframe.preview-frame {
.game-workbench-layout {
grid-template-columns: minmax(0, 1fr) minmax(280px, 0.42fr);
}
-
- .game-workbench-toolbar {
- align-items: stretch;
- flex-direction: column;
- }
-
- .game-workbench-view-actions {
- justify-content: flex-end;
- }
}
@media (max-width: 760px) {
@@ -10242,17 +10350,6 @@ iframe.preview-frame {
border-left: 0;
}
- .game-workbench-toolbar,
- .game-workbench-view-actions {
- align-items: stretch;
- flex-direction: column;
- }
-
- .game-workbench-tabs {
- display: grid;
- grid-template-columns: repeat(2, minmax(0, 1fr));
- }
-
.game-resource-section {
min-width: 460px;
}
diff --git a/apps/ai-game-creator-shell/src/view/project-development/WorkbenchMoreActionsMenu.tsx b/apps/ai-game-creator-shell/src/view/project-development/WorkbenchMoreActionsMenu.tsx
new file mode 100644
index 000000000..13a8a93a7
--- /dev/null
+++ b/apps/ai-game-creator-shell/src/view/project-development/WorkbenchMoreActionsMenu.tsx
@@ -0,0 +1,111 @@
+import { MoreHorizontal } from 'lucide-react';
+import { type ReactNode, useEffect, useRef, useState } from 'react';
+import { createPortal } from 'react-dom';
+
+import { useImageCanvasFloatingOptionDismiss } from '../../../../../src/components/image-editor/useImageCanvasFloatingOptionDismiss';
+
+export type WorkbenchMoreAction = {
+ key: string;
+ label: string;
+ /** 与内联形态同一枚图标:菜单里逐条重画,不新造一套语义。 */
+ icon: ReactNode;
+ disabled?: boolean;
+ onSelect: () => void;
+};
+
+/**
+ * 顶栏动作的「更多」下拉:窄宽度下 `打开项目目录 / 资源面板 / 整理画布` 的收容处。
+ *
+ * 只在顶栏落到 `collapsed-actions` 档时由 CSS 显示(见 `styles.css` 里的 `[data-layout]`
+ * 选择器),档位判据在 `workbenchToolbarModel`。菜单 portal 到 `document.body` 并每次渲染
+ * 按触发钮的实时 rect 定位:顶栏会随对话列收放横移,菜单挂在按钮旁边而不是挂在某个坐标上。
+ */
+export function WorkbenchMoreActionsMenu({
+ actions,
+}: {
+ readonly actions: readonly WorkbenchMoreAction[];
+}) {
+ const [open, setOpen] = useState(false);
+ const rootRef = useRef(null);
+ const menuRef = useRef(null);
+ // 定位每次渲染都按触发钮实时 rect 重算;滚动 / 缩放后必须重算,否则 fixed 定位的菜单会漂在
+ // 旧位置上。用一个 tick 驱动重算,「打开当帧就有菜单」的时序不变。
+ const [, setMenuAnchorTick] = useState(0);
+ useEffect(() => {
+ if (!open) return undefined;
+ const refreshMenuAnchor = () => setMenuAnchorTick((tick) => tick + 1);
+ window.addEventListener('scroll', refreshMenuAnchor, true);
+ window.addEventListener('resize', refreshMenuAnchor);
+ return () => {
+ window.removeEventListener('scroll', refreshMenuAnchor, true);
+ window.removeEventListener('resize', refreshMenuAnchor);
+ };
+ }, [open]);
+
+ // 菜单在 `rootRef` 子树外的 portal 里,必须一起登记成「点这里不算外部」,否则落在菜单项上的
+ // 那一下会被当成点外部,菜单在选项收到 click 之前就被收掉。
+ useImageCanvasFloatingOptionDismiss({
+ isOpen: open,
+ boundaryRefs: [rootRef, menuRef],
+ onDismiss: () => setOpen(false),
+ });
+
+ if (actions.length === 0) {
+ return null;
+ }
+
+ return (
+
+
+ {open
+ ? createPortal(
+
+ {actions.map((action) => (
+
+ ))}
+
,
+ document.body,
+ )
+ : null}
+
+ );
+}
diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
index 50e0c00dc..ba1a6db96 100644
--- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx
+++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx
@@ -46,6 +46,7 @@ import {
Redo2,
Replace,
RotateCcw,
+ RotateCw,
Search,
Shapes,
SlidersHorizontal,
@@ -443,6 +444,11 @@ import {
import { useProjectResourceCardPreviews } from './useProjectResourceCardPreviews';
import { useProjectResourceSectionHeights } from './useProjectResourceSectionHeights';
import { useResourceAssetDeleteFlow } from './useResourceAssetDeleteFlow';
+import { useWorkbenchToolbarLayout } from './useWorkbenchToolbarLayout';
+import {
+ type WorkbenchMoreAction,
+ WorkbenchMoreActionsMenu,
+} from './WorkbenchMoreActionsMenu';
export type {
ProjectAgentResultSummary,
@@ -1831,6 +1837,12 @@ export default function ProjectDevelopmentView({
// 运行画面的「全屏预览」:全屏的是画面那一格(`.game-run-preview`),不是整块工作台,
// 所以按钮与 ref 都归运行表现层自己持有。
const runPreviewFullscreen = useElementFullscreen();
+ // 「刷新运行画面」的载入次数:递增一次=换 iframe 的元素身份=重新载入运行页面。
+ const [runPreviewReloadNonce, setRunPreviewReloadNonce] = useState(0);
+ // 顶栏按实测宽度自己在三档之间退(见 `workbenchToolbarModel`):档位只写在 DOM 属性上,
+ // 不参与 React 状态。
+ const workbenchToolbarRef = useRef(null);
+ useWorkbenchToolbarLayout(workbenchToolbarRef);
const [resourceBookState, dispatchResourceBook] = useReducer(
resourceBookReducer,
initialResourceBookState,
@@ -2783,6 +2795,17 @@ export default function ProjectDevelopmentView({
});
}
}, [embeddedPreviewUrl, onNotice]);
+ /**
+ * 「刷新运行画面」:先把预览确认成活的(`onPlay` 命中活体预览只切视图、不重启服务),再换一次
+ * iframe 的元素身份把页面重新载入。
+ *
+ * 游戏预览不是 vite dev 的实时刷新,改完代码只有重载页面才看得到新一版;以前只能靠切到资源
+ * 管理再切回来,这一枚把同一个动作放回运行画面本身。
+ */
+ const refreshRunPreview = useCallback(() => {
+ onPlay?.();
+ setRunPreviewReloadNonce((current) => current + 1);
+ }, [onPlay]);
const runAvailable =
embeddedPreviewUrl !== null ||
manifest.tasks.some(
@@ -7341,6 +7364,14 @@ export default function ProjectDevelopmentView({
uiEditorRoute,
]);
+ /**
+ * 切到运行视图**并载入游戏**:原来的「播放」按钮合并进「运行」页签,两者本来就是同一件事
+ * (切视图 + 启动本地预览)。页签因此在不可运行时不置灰、只给提示(见 `run-unavailable-hint`),
+ * 点了不切视图也不发播放请求。
+ *
+ * 自动切运行那两条路径(会话内已确认的预览、播放请求)不走这里,它们只是**呈现**运行结果,
+ * 不该再发起一次播放。
+ */
function showRunView() {
if (!runAvailable || uiEditorRoute) {
return;
@@ -7354,6 +7385,7 @@ export default function ProjectDevelopmentView({
captureResourceSectionScrollPositions();
stopActiveCardMedia();
setMode('run');
+ onPlay?.();
}
const showRunUnavailableHint = !runAvailable && !uiEditorRoute;
@@ -9582,6 +9614,54 @@ export default function ProjectDevelopmentView({
],
);
+ /**
+ * 「整理画布」的可用判据:布局还没读出来,或当前范围里一个素材都没有时不放行。行内按钮与
+ * 「更多」里的同一条动作共用这一份,别在两处各写一遍。
+ */
+ const resourceCanvasOrganizeDisabled =
+ !resourceLayoutReady ||
+ (resourceOrganizeSections !== null &&
+ resourceOrganizeSections.length === 0);
+
+ /**
+ * 顶栏动作区里可被「更多」下拉收容的三枚动作,与行内形态是**同一批动作**:出现条件与禁用
+ * 口径必须一起改(行内按钮见 `game-workbench-foldable-action`)。
+ */
+ const workbenchMoreActionCandidates: Array = [
+ onRevealProjectDirectory
+ ? {
+ key: 'reveal-project-directory',
+ label: '打开项目目录',
+ icon: ,
+ onSelect: () => void onRevealProjectDirectory(),
+ }
+ : null,
+ mode === 'resources' && !uiEditorRoute
+ ? {
+ key: 'resource-panel',
+ label: '资源面板',
+ icon: ,
+ disabled: canvasResources.length === 0,
+ onSelect: () => {
+ setResourcePanelNotice('');
+ setResourcePanelOpen((current) => !current);
+ },
+ }
+ : null,
+ mode === 'resources' && !uiEditorRoute
+ ? {
+ key: 'organize-canvas',
+ label: '整理画布',
+ icon: ,
+ disabled: resourceCanvasOrganizeDisabled,
+ onSelect: organizeResourceCanvasLayout,
+ }
+ : null,
+ ];
+ const workbenchMoreActions = workbenchMoreActionCandidates.filter(
+ (action): action is WorkbenchMoreAction => action !== null,
+ );
+
if (planningStartMode) {
return (
-
+
{/*
- 「资源管理 / 运行」与「播放」是同一组左侧控件:播放紧贴模式切换之后、整组左对齐,
- 不再居中悬浮。**两个页签下都常驻**——运行视图空态与预览失败态都要靠它重跑
- (空态文案就是「点击顶部播放按钮后将在这里直接运行游戏」)。
+ 左侧只剩模式切换:「资源管理 / 运行」。**运行页签本身就是播放入口**(前面带 ▶):
+ 切过去即按「播放」载入游戏资源,不再单开一枚「播放」按钮——两枚入口说的是同一件事,
+ 并排只会把右侧动作区挤到第二行。运行视图空态与预览失败态都靠再点一次这枚页签重跑。
*/}
@@ -9670,30 +9750,22 @@ export default function ProjectDevelopmentView({
disabled={uiEditorRoute !== null}
onClick={showRunView}
>
+
运行
-
+ {/*
+ 窄宽度收容:`打开项目目录 / 资源面板 / 整理画布` 在顶栏掉到 `collapsed-actions`
+ 档时改从这里进(宽的时候仍是行内按钮)。档位顺序与判据在
+ `workbenchToolbarModel`;两处渲染的是同一批动作,出现条件与禁用口径要一起改。
+ */}
+
{onRevealProjectDirectory ? (