合并主干 C3 交互改造并接入 C7 版本切换
- 合并 feat/agc-canvas-resource-workbench-v3:资源卡改为选中+浮出工具条、支持多选、删除资源详情面板与 resourceEditorRoute - index.tsx 冲突以 C3 新结构为底:保留本包的素材重命名回调,丢弃被 C3 取消的 focusedResource 详情面板派生状态与浮层 JSX - 重命名入口从已删除的详情面板搬到选中工具条 extraActions,与「分类与标签」并列 - check-config.mjs 的 allowlist 保持主干版本(normalize_local_project_raster_resource 一行已删) - 还原 normalize_local_project_raster_resource Tauri 命令与注册:C3 已在前端接回调用方 - C7:新增 GameRunVersionPicker 运行模块右上角版本入口,无版本不渲染 - C7:版本状态收敛到 WorkspaceLauncherShell 一份,透传给资源画布与 @ 面板;换项目或版本消失时清回最新版本 - C7:currentVersionResourceBindingIds 接入 activeVersionId,复用 resolveActiveIterationVersion 口径,资源卡「当前使用」高亮随版本切换 - C7:切换版本时复用既有 onPlay 预览入口重载画面,不新增运行时资源重映射 - 新增 10 条测试覆盖版本判定口径、版本入口与切换、当前使用高亮、素材重命名链路
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
Sparkles,
|
||||
WandSparkles,
|
||||
} from 'lucide-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import type { CSSProperties, ReactNode } from 'react';
|
||||
|
||||
import { EditorIconButton } from './ImageCanvasEditorPrimitives';
|
||||
import type { CanvasLayer } from './ImageCanvasEditorTypes';
|
||||
@@ -19,7 +19,28 @@ import {
|
||||
isQuickEditSupportedLayer,
|
||||
} from './ImageCanvasGenerationModel';
|
||||
|
||||
/** 选中工具条上的动作标识,供宿主声明自己实际可用的动作集合。 */
|
||||
export type ImageCanvasSelectedToolbarAction =
|
||||
| 'quick-edit'
|
||||
| 'redraw'
|
||||
| 'crop-expand'
|
||||
| 'remove-background'
|
||||
| 'perfect-pixel'
|
||||
| 'split-icon-spritesheet'
|
||||
| 'extract-ui-design'
|
||||
| 'character-animation'
|
||||
| 'download';
|
||||
|
||||
type ImageCanvasSelectedLayerToolbarViewProps = {
|
||||
/**
|
||||
* 宿主显式声明的可用动作集合。
|
||||
*
|
||||
* 传 `null`(默认)时完全沿用美术画布内置判定;传入集合时以集合为准,
|
||||
* 让没有对应后端链路的画布宿主不必把「点了没反应」的按钮渲染出来。
|
||||
*/
|
||||
supportedActions?: ReadonlySet<ImageCanvasSelectedToolbarAction> | null;
|
||||
/** 宿主在工具条上追加的动作节点,渲染在下载按钮之前。 */
|
||||
extraActions?: ReactNode;
|
||||
selectedLayer: CanvasLayer | null;
|
||||
selectedToolbarStyle: CSSProperties | null;
|
||||
onOpenQuickEditPanel: (layer: CanvasLayer) => void;
|
||||
@@ -38,6 +59,8 @@ type ImageCanvasSelectedLayerToolbarViewProps = {
|
||||
};
|
||||
|
||||
export function ImageCanvasSelectedLayerToolbarView({
|
||||
supportedActions = null,
|
||||
extraActions,
|
||||
selectedLayer,
|
||||
selectedToolbarStyle,
|
||||
onOpenQuickEditPanel,
|
||||
@@ -57,7 +80,20 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
if (!selectedLayer || !selectedToolbarStyle) {
|
||||
return null;
|
||||
}
|
||||
const canRedraw = canOpenRedrawPanel(selectedLayer);
|
||||
const isActionSupported = (
|
||||
action: ImageCanvasSelectedToolbarAction,
|
||||
builtInAvailability: boolean,
|
||||
) => (supportedActions ? supportedActions.has(action) : builtInAvailability);
|
||||
const canRedraw = isActionSupported(
|
||||
'redraw',
|
||||
canOpenRedrawPanel(selectedLayer),
|
||||
);
|
||||
const extraActionDivider = extraActions ? (
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="image-canvas-editor__floating-toolbar-divider"
|
||||
/>
|
||||
) : null;
|
||||
|
||||
if (selectedLayer.mediaType === 'audio') {
|
||||
return (
|
||||
@@ -79,12 +115,16 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
<span>改造</span>
|
||||
</CanvasChromeButton>
|
||||
) : null}
|
||||
<EditorIconButton
|
||||
label="下载按钮"
|
||||
title="下载按钮"
|
||||
icon={Download}
|
||||
onClick={() => onDownloadLayer(selectedLayer)}
|
||||
/>
|
||||
{extraActionDivider}
|
||||
{extraActions}
|
||||
{isActionSupported('download', true) ? (
|
||||
<EditorIconButton
|
||||
label="下载按钮"
|
||||
title="下载按钮"
|
||||
icon={Download}
|
||||
onClick={() => onDownloadLayer(selectedLayer)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -93,6 +133,14 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
selectedLayer.mediaType !== 'video' &&
|
||||
selectedLayer.mediaType !== 'image-sequence' &&
|
||||
selectedLayer.assetKind !== 'character-animation';
|
||||
const showQuickEdit = isActionSupported(
|
||||
'quick-edit',
|
||||
isQuickEditSupportedLayer(selectedLayer),
|
||||
);
|
||||
const showCharacterAnimation = isActionSupported(
|
||||
'character-animation',
|
||||
selectedLayer.assetKind === 'character',
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
@@ -102,7 +150,7 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
aria-label="图片工具栏"
|
||||
onPointerDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
{isQuickEditSupportedLayer(selectedLayer) ? (
|
||||
{showQuickEdit ? (
|
||||
<>
|
||||
<CanvasChromeButton
|
||||
className="image-canvas-editor__floating-toolbar-text-button"
|
||||
@@ -119,74 +167,77 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
{canRasterEdit ? (
|
||||
<>
|
||||
<EditorIconButton
|
||||
label="裁扩按钮"
|
||||
title="裁扩按钮"
|
||||
icon={Crop}
|
||||
onClick={() => onOpenCropExpandPanel(selectedLayer)}
|
||||
/>
|
||||
<EditorIconButton
|
||||
label="去除背景按钮"
|
||||
title="去除背景按钮"
|
||||
icon={ImageOff}
|
||||
onClick={() => onRemoveBackground(selectedLayer)}
|
||||
/>
|
||||
<CanvasChromeButton
|
||||
className="image-canvas-editor__floating-toolbar-text-button"
|
||||
// 中文注释:保存态名称不能直接用「素材类型保存中」——icon-spritesheet 图层上
|
||||
// 拆分图集按钮同时显示该文案,两个控件会撞同一个无障碍名称。
|
||||
label={
|
||||
isPersistingAssetKind
|
||||
? '完美像素等待素材类型保存'
|
||||
: isPerfectPixelProcessing
|
||||
? '完美像素处理中'
|
||||
: isPerfectPixelPendingConfirmation
|
||||
? '完美像素结果待确认'
|
||||
: '完美像素'
|
||||
}
|
||||
title={
|
||||
isPersistingAssetKind
|
||||
? '完美像素等待素材类型保存'
|
||||
: isPerfectPixelProcessing
|
||||
? '完美像素处理中'
|
||||
: isPerfectPixelPendingConfirmation
|
||||
? '结果待确认,双击原占位继续核对或重试'
|
||||
: '自动识别并规整像素网格'
|
||||
}
|
||||
icon={
|
||||
isPersistingAssetKind || isPerfectPixelProcessing ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Grid2X2 className="h-4 w-4" />
|
||||
)
|
||||
}
|
||||
// 中文注释:素材类型保存在途时必须一并禁用。请求同时带 assetKind 和
|
||||
// sourceResourceId,本地类型已改但资源尚未落库时两者不一致,后端
|
||||
// resolve_editor_pixel_art_snap_asset_kind 会直接 400,只留下失败占位。
|
||||
// 与相邻的拆分图集按钮保持同一套门禁。
|
||||
disabled={
|
||||
isPersistingAssetKind ||
|
||||
isPerfectPixelProcessing ||
|
||||
isPerfectPixelPendingConfirmation
|
||||
}
|
||||
aria-busy={isPersistingAssetKind || isPerfectPixelProcessing}
|
||||
onClick={() => onPerfectPixel(selectedLayer)}
|
||||
>
|
||||
<span>
|
||||
{isPersistingAssetKind
|
||||
? '保存中'
|
||||
: isPerfectPixelProcessing
|
||||
? '处理中'
|
||||
: isPerfectPixelPendingConfirmation
|
||||
? '待确认'
|
||||
: '完美像素'}
|
||||
</span>
|
||||
</CanvasChromeButton>
|
||||
</>
|
||||
{canRasterEdit && isActionSupported('crop-expand', true) ? (
|
||||
<EditorIconButton
|
||||
label="裁扩按钮"
|
||||
title="裁扩按钮"
|
||||
icon={Crop}
|
||||
onClick={() => onOpenCropExpandPanel(selectedLayer)}
|
||||
/>
|
||||
) : null}
|
||||
{selectedLayer.assetKind === 'icon-spritesheet' ? (
|
||||
{canRasterEdit && isActionSupported('remove-background', true) ? (
|
||||
<EditorIconButton
|
||||
label="去除背景按钮"
|
||||
title="去除背景按钮"
|
||||
icon={ImageOff}
|
||||
onClick={() => onRemoveBackground(selectedLayer)}
|
||||
/>
|
||||
) : null}
|
||||
{canRasterEdit && isActionSupported('perfect-pixel', true) ? (
|
||||
<CanvasChromeButton
|
||||
className="image-canvas-editor__floating-toolbar-text-button"
|
||||
// 中文注释:保存态名称不能直接用「素材类型保存中」——icon-spritesheet 图层上
|
||||
// 拆分图集按钮同时显示该文案,两个控件会撞同一个无障碍名称。
|
||||
label={
|
||||
isPersistingAssetKind
|
||||
? '完美像素等待素材类型保存'
|
||||
: isPerfectPixelProcessing
|
||||
? '完美像素处理中'
|
||||
: isPerfectPixelPendingConfirmation
|
||||
? '完美像素结果待确认'
|
||||
: '完美像素'
|
||||
}
|
||||
title={
|
||||
isPersistingAssetKind
|
||||
? '完美像素等待素材类型保存'
|
||||
: isPerfectPixelProcessing
|
||||
? '完美像素处理中'
|
||||
: isPerfectPixelPendingConfirmation
|
||||
? '结果待确认,双击原占位继续核对或重试'
|
||||
: '自动识别并规整像素网格'
|
||||
}
|
||||
icon={
|
||||
isPersistingAssetKind || isPerfectPixelProcessing ? (
|
||||
<Loader2 className="h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<Grid2X2 className="h-4 w-4" />
|
||||
)
|
||||
}
|
||||
// 中文注释:素材类型保存在途时必须一并禁用。请求同时带 assetKind 和
|
||||
// sourceResourceId,本地类型已改但资源尚未落库时两者不一致,后端
|
||||
// resolve_editor_pixel_art_snap_asset_kind 会直接 400,只留下失败占位。
|
||||
// 与相邻的拆分图集按钮保持同一套门禁。
|
||||
disabled={
|
||||
isPersistingAssetKind ||
|
||||
isPerfectPixelProcessing ||
|
||||
isPerfectPixelPendingConfirmation
|
||||
}
|
||||
aria-busy={isPersistingAssetKind || isPerfectPixelProcessing}
|
||||
onClick={() => onPerfectPixel(selectedLayer)}
|
||||
>
|
||||
<span>
|
||||
{isPersistingAssetKind
|
||||
? '保存中'
|
||||
: isPerfectPixelProcessing
|
||||
? '处理中'
|
||||
: isPerfectPixelPendingConfirmation
|
||||
? '待确认'
|
||||
: '完美像素'}
|
||||
</span>
|
||||
</CanvasChromeButton>
|
||||
) : null}
|
||||
{selectedLayer.assetKind === 'icon-spritesheet' &&
|
||||
isActionSupported('split-icon-spritesheet', true) ? (
|
||||
<CanvasChromeButton
|
||||
className="image-canvas-editor__floating-toolbar-text-button"
|
||||
label={
|
||||
@@ -223,7 +274,8 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
</span>
|
||||
</CanvasChromeButton>
|
||||
) : null}
|
||||
{selectedLayer.assetKind === 'ui-design' ? (
|
||||
{selectedLayer.assetKind === 'ui-design' &&
|
||||
isActionSupported('extract-ui-design', true) ? (
|
||||
<CanvasChromeButton
|
||||
className="image-canvas-editor__floating-toolbar-text-button"
|
||||
label="提取素材"
|
||||
@@ -234,7 +286,7 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
<span>提取素材</span>
|
||||
</CanvasChromeButton>
|
||||
) : null}
|
||||
{selectedLayer.assetKind === 'character' ? (
|
||||
{showCharacterAnimation ? (
|
||||
<CanvasChromeButton
|
||||
className="image-canvas-editor__floating-toolbar-text-button"
|
||||
label="生成动画"
|
||||
@@ -262,12 +314,16 @@ export function ImageCanvasSelectedLayerToolbarView({
|
||||
</CanvasChromeButton>
|
||||
</>
|
||||
) : null}
|
||||
<EditorIconButton
|
||||
label="下载按钮"
|
||||
title="下载按钮"
|
||||
icon={Download}
|
||||
onClick={() => onDownloadLayer(selectedLayer)}
|
||||
/>
|
||||
{extraActionDivider}
|
||||
{extraActions}
|
||||
{isActionSupported('download', true) ? (
|
||||
<EditorIconButton
|
||||
label="下载按钮"
|
||||
title="下载按钮"
|
||||
icon={Download}
|
||||
onClick={() => onDownloadLayer(selectedLayer)}
|
||||
/>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -493,7 +493,7 @@ export async function navigateWechatMiniProgramPage(
|
||||
success() {
|
||||
resolve();
|
||||
},
|
||||
fail(_error) {
|
||||
fail(_error: unknown) {
|
||||
console.error('[host-bridge] wechat mini program navigation failed');
|
||||
reject(new Error(errorMessage));
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user