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"]),并断言旧网格容器 / 宿主一个都不再存在
147 lines
4.7 KiB
TypeScript
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';
|
|
}
|