Files
Genarrative/apps/ai-game-creator-shell/src/view/project-development/resourceBookModel.ts
T
suzmii 9788248d9b 「所有资源」页改走画本场景:与栏目页同一套卡片宿主与几何,并可拖动
- 新增 resourceBookShowsAllCategories:把"子画布里的 all"这一态判定收在 resourceBookModel 一处
- resourceBookCategoryCardPresentation:展开态下真实栏目返回 child(卡片仍由各栏目组承载,每张资源只有一个宿主)、all 自己返回 null(只出标题栏与全量计数)
- 新增纯函数 buildResourceBookAllLayout:按栏目顺序纵向排"分带",用各栏目可见资源的 extent 定带宽高;卡片世界坐标 = 栏目内局部坐标 + 带原点
- 新增 resourceBookAllTitlebarRects:分带 → 栏目标题栏矩形,让展开态复用总览态"按矩形钉标题栏"的同一条渲染路径(ResourceBookScene 不需要知道"展开态"这个概念)
- 新增 resourceBookAllBandLocalPoint:展开态世界坐标 → 栏目内局部坐标,这是落盘前唯一的换算(减带原点)
- buildResourceBookScenePlan:展开态下 all 只出标题栏(钉在视口 + 全量计数),真实栏目按带偏移铺满 child 卡;真实栏目这一支仍按 presentation === child 取卡,"能出卡的判定"只有一处
- buildResourceBookScenePlan:返回总览的淡出改为按"刚离开的那段子画布"判定(exitingCategory 传 all 时覆盖所有栏目),并用带偏移位置淡出,否则返回动画会跳位置
- buildResourceBookScenePlan:「所有资源」组不再接 child / exiting 卡,避免同一张卡在两组里各挂一份(DOM 查询与 FLIP 键撞车)
- index.tsx:删掉独立网格页组件、其 host 块与 resourceBookAllResourcesSections memo,展开态整体交给画本场景
- index.tsx:新增内存态 resourceBookAllViewport(不落盘、不进按真实栏目键的 resourceCanvasViewports),滚轮 / 缩放 / 适应 / 平移 / 框选都作用到它
- index.tsx:分带几何按"项目 + 排序 + 可见资源集"冻结(签名 + ref 短路;签名不含 state.token——返回总览时 token 也会变,含它会在淡出期间重算),拖动不重算带高,避免"拖上面一带让后面所有带整体位移"
- index.tsx:拖动放开——展开态下每张卡都能拖,section 取 resource.category(栏目页仍只拖当前分页栏目)
- index.tsx:提交前用 resourceBookAllBandLocalPoint 把世界坐标减回带原点,写回的仍是同一 mode 同一 sidecar 的同一 section(.agent/workbench/resource-layouts/{dependency,type}.json),没有第二份空间,不改契约与 schema
- index.tsx:renderResourceBookCard 去掉 options.draggable:展开态已可拖,这个"只读"开关没有调用方了
- 测试:resourceBookModel.test 钉住展开态 presentation 与"栏目页仍是原判据";resourceBookLayout.test 退役旧分组函数用例,新增分带几何 / 标题栏矩形 / 减带原点换算 / 展开态计划(含"每张资源只有一个宿主"与按带位置淡出)断言
- 测试:appSurface 两处「所有资源」判据从旧网格宿主改为画本场景(.game-resource-book-scene-card[data-resource-book-category] 与 .is-active[data-resource-book-category="all"]),并断言旧网格容器 / 宿主一个都不再存在
2026-09-11 23:33:08 +08:00

147 lines
4.7 KiB
TypeScript

import type { ProjectResourceCategory } from './resourceProjectionModel';
export type ResourceBookCategory = ProjectResourceCategory;
/**
* 「所有资源」是渲染层的特殊项,不是真实资源栏目。
*
* 它只存在于画本(资源总览)自己的状态与渲染里:跨端契约的
* `ProjectResourceCanvasCategory` 不新增 `'all'`,落盘布局的 `section` 也不会出现它,
* 因此既不需要改 sidecar schema,也不会污染写入侧的分区口径。
*/
export const RESOURCE_BOOK_ALL_TARGET = 'all' as const;
/** 画本可以打开的目标:真实栏目,或渲染层的「所有资源」。 */
export type ResourceBookTarget =
| ResourceBookCategory
| typeof RESOURCE_BOOK_ALL_TARGET;
export function isResourceBookAllTarget(
value: unknown,
): value is typeof RESOURCE_BOOK_ALL_TARGET {
return value === RESOURCE_BOOK_ALL_TARGET;
}
export type ResourceBookPhase = 'idle' | 'entering' | 'returning-main';
export type ResourceBookState = {
view: 'main' | 'child';
category: ResourceBookTarget | null;
phase: ResourceBookPhase;
token: number;
};
export type ResourceBookAction =
| { type: 'open-category'; category: ResourceBookTarget; token: number }
| { type: 'finish-transition'; token: number }
| { type: 'return-to-main'; token: number }
| { type: 'reset'; token: number };
export const initialResourceBookState: ResourceBookState = {
view: 'main',
category: null,
phase: 'idle',
token: 0,
};
export function resourceBookReducer(
state: ResourceBookState,
action: ResourceBookAction,
): ResourceBookState {
switch (action.type) {
case 'open-category': {
if (
state.view === 'child' &&
state.category === action.category &&
state.phase === 'idle'
) {
return state;
}
return {
...state,
view: 'child',
category: action.category,
phase: 'entering',
token: action.token,
};
}
case 'finish-transition':
return state.token === action.token ? { ...state, phase: 'idle' } : state;
case 'return-to-main':
if (state.view !== 'child') {
return state;
}
return {
...state,
view: 'main',
category: null,
phase: 'returning-main',
token: action.token,
};
case 'reset':
return {
...initialResourceBookState,
token: action.token,
};
default:
return state;
}
}
export function resourceBookSceneCategory(state: ResourceBookState) {
return state.category;
}
export function resourceBookShowsOverviewCards(state: ResourceBookState) {
return state.view === 'main';
}
export function resourceBookShowsChildCards(state: ResourceBookState) {
return state.view === 'child';
}
/**
* 栏目卡片的呈现方式:
* - `child`:子画布布局,当前栏目;
* - `overview`:总览摞布局。进入栏目时其他栏目保留在 DOM 里淡出,总览/返回态全部用总览布局;
* - `null`:不渲染(子画布静止态的非活动栏目)。
*
* 返回总览时,源栏目里没有回到摞上的卡片由布局层额外标成 `exiting`。
*/
export type ResourceBookCardPresentation = 'child' | 'overview' | 'exiting';
/**
* 是否停在「所有资源」展开态(子画布里的 `all`)。
*
* 它与总览态(`main`)不是一回事:总览态每个栏目只铺一小摞,展开态把每个栏目的**全部**
* 卡片铺在自己那条分带上。判定收在这一处,计划层与渲染层不得各写一份。
*/
export function resourceBookShowsAllCategories(state: ResourceBookState) {
return state.view === 'child' && isResourceBookAllTarget(state.category);
}
export function resourceBookCategoryCardPresentation(
state: ResourceBookState,
category: ResourceBookTarget,
): Exclude<ResourceBookCardPresentation, 'exiting'> | null {
if (state.view === 'main') {
return 'overview';
}
// 「所有资源」展开态:卡片由各**真实栏目**组承载,而每张资源只属于一个栏目,
// 因此每张卡在画本场景里仍然只有一个宿主(DOM 查询与 FLIP 键都不会撞车);
// `all` 自己不再复制一份卡片,只出钉住的标题栏与全量计数。
if (isResourceBookAllTarget(state.category)) {
return isResourceBookAllTarget(category) ? null : 'child';
}
if (category === state.category) {
return 'child';
}
// 进入栏目期间其他栏目的卡片留在 DOM 里淡出,不再整层克隆。
return state.phase === 'entering' ? 'overview' : null;
}
/** 返回总览期间,需要把源栏目未回到摞上的卡片按子画布布局淡出。 */
export function resourceBookShowsExitingCards(state: ResourceBookState) {
return state.view === 'main' && state.phase === 'returning-main';
}