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 617f24059..e89f6e217 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 @@ -1,8 +1,5 @@ import type { ImageCanvasHostScope } from '@genarrative/image-canvas-core'; -import { - type CanvasViewport, - resolveViewportFromWheel, -} from '@genarrative/image-canvas-core'; +import type { CanvasViewport } from '@genarrative/image-canvas-core'; import { Code2, FileText, @@ -113,11 +110,6 @@ import { } from './resourceBookViewport'; import { createResourceCanvasPageCategorySignature, - createResourceCanvasPageWheelState, - queueResourceCanvasPageWheel, - resetResourceCanvasPageWheel, - type ResourceCanvasPageWheelState, - takeResourceCanvasPageWheelStep, } from './resourceCanvasController'; import { createResourceCanvasCardSizeByResourceId, @@ -417,7 +409,7 @@ export type ProjectDevelopmentViewProps = { /** * 本项目是从「做方案」入口建的。 * - * 只用来决定工作台在拿到第一个已登记资源之前是否收掉左边的资源画布,不参与任何 + * 只用来决定工作台在拿到第一个已登记资源之前是否收掉左侧的资源区域,不参与任何 * runtime 分流——做游戏与做素材的链路不受影响。 */ planningStartMode?: boolean; @@ -931,7 +923,7 @@ function ResourceBookThumbnail({ onOpen(category); } }} - aria-label={`进入${categoryLabels[category]}子画布`} + aria-label={`打开${categoryLabels[category]}`} > - {resources.length > 0 ? '点击进入子画布' : '打开空子画布'} + 打开{categoryLabels[category]} @@ -987,10 +979,10 @@ function ResourceBookTitleBar({ className="game-resource-page-collapse" onClick={onCollapse} aria-label="收起资源" - title="缩回主画布" + title="返回资源总览" > @@ -1311,19 +1303,12 @@ export default function ProjectDevelopmentView({ element: HTMLMediaElement | null; } | null>(null); const resourceCanvasRef = useRef(null); - const resourceCanvasWheelHandlerRef = useRef< - ((event: WheelEvent) => void) | null - >(null); const resourceCanvasViewportRef = useRef({ x: 48, y: 48, scale: 1, }); const resourceCanvasFitKeysRef = useRef>(new Set()); - const resourceCanvasPageWheelRef = useRef< - ResourceCanvasPageWheelState - >(createResourceCanvasPageWheelState()); - const renderedResourceCategoryRef = useRef(null); const resourceCanvasPanRef = useRef<{ pointerId: number; category: ResourceCategory; @@ -1623,7 +1608,7 @@ export default function ProjectDevelopmentView({ () => resources.filter((resource) => resource.category !== 'code'), [resources], ); - // 做方案在拿到第一个产物之前,左边的资源画布是空的(文档 0 项、项目版本 0 项),而澄清 + // 做方案在拿到第一个产物之前,左侧的资源区域是空的(文档 0 项、项目版本 0 项),而澄清 // 问答、决定卡和 GDD 审批全挤在右边那一列里。这段时间先把画布收掉让对话铺满,一旦有第 // 一个已登记资源就自动恢复双栏。用 CSS 收而不是不渲染:画布里的页签、缩放和选中状态都 // 留着,恢复双栏时不会被重置。 @@ -2152,14 +2137,7 @@ export default function ProjectDevelopmentView({ [resourcePageCategories, sortMode], ); useLayoutEffect(() => { - const pageWheelState = resourceCanvasPageWheelRef.current; - renderedResourceCategoryRef.current = activePageCategory; - if (pageWheelState.activeCategory === null) { - pageWheelState.activeCategory = activePageCategory; - } - if (pageWheelState.activeCategory === activePageCategory) { - resourceCanvasViewportRef.current = activeResourceCanvasViewport; - } + resourceCanvasViewportRef.current = activeResourceCanvasViewport; }, [activePageCategory, activeResourceCanvasViewport]); const selectedResource = canvasResources.find((resource) => resource.id === selectedResourceId) ?? @@ -2368,14 +2346,6 @@ export default function ProjectDevelopmentView({ [queueResourceSectionZoomScroll, setResourceSectionZoom], ); - const cancelQueuedResourceCanvasPageWheel = useCallback(() => { - const state = resourceCanvasPageWheelRef.current; - if (state.pendingTimerId !== null) { - window.clearTimeout(state.pendingTimerId); - } - resetResourceCanvasPageWheel(state, renderedResourceCategoryRef.current); - }, []); - const cancelResourceCanvasPan = useCallback(() => { const pan = resourceCanvasPanRef.current; if (!pan) { @@ -2475,10 +2445,9 @@ export default function ProjectDevelopmentView({ ]); const openResourceBookChild = useCallback( - (category: ResourceCategory, fromWheel = false) => { + (category: ResourceCategory) => { cancelResourceCardDrag(); cancelResourceCanvasPan(); - if (!fromWheel) cancelQueuedResourceCanvasPageWheel(); if ( resourceBookState.view === 'child' && @@ -2491,11 +2460,6 @@ export default function ProjectDevelopmentView({ const transitionToken = resourceBookTransitionControllerRef.current.begin( resourceBookManagerRef.current, ); - // Keep the paged-canvas projection's logical target synchronous with the - // reducer action. React may commit the visible page later, but wheel and - // focus handlers arriving in the same turn must calculate from the new - // target rather than the previous rendered category. - resourceCanvasPageWheelRef.current.activeCategory = category; dispatchResourceBook({ type: 'open-category', category, @@ -2543,11 +2507,9 @@ export default function ProjectDevelopmentView({ } setResourceCanvasViewport(category, categoryViewport); - resourceCanvasPageWheelRef.current.activeCategory = category; setActiveResourceCategory(category); }, [ - cancelQueuedResourceCanvasPageWheel, cancelResourceCanvasPan, cancelResourceCardDrag, manifest.projectId, @@ -2567,13 +2529,11 @@ export default function ProjectDevelopmentView({ } cancelResourceCardDrag(); cancelResourceCanvasPan(); - cancelQueuedResourceCanvasPageWheel(); const transitionToken = resourceBookTransitionControllerRef.current.begin( resourceBookManagerRef.current, ); dispatchResourceBook({ type: 'return-to-main', token: transitionToken }); }, [ - cancelQueuedResourceCanvasPageWheel, cancelResourceCanvasPan, cancelResourceCardDrag, resourceBookState, @@ -2586,37 +2546,6 @@ export default function ProjectDevelopmentView({ [openResourceBookChild], ); - const openResourceBookChildRef = useRef(openResourceBookChild); - useLayoutEffect(() => { - openResourceBookChildRef.current = openResourceBookChild; - }, [openResourceBookChild]); - - const advanceResourceCanvasPage = useCallback( - (stepOffset: number) => { - const currentCategory = resourceCanvasPageWheelRef.current.activeCategory; - const normalizedStepOffset = Math.trunc(stepOffset); - if ( - !currentCategory || - resourcePageCategories.length < 2 || - !Number.isFinite(stepOffset) || - normalizedStepOffset === 0 - ) { - return; - } - const index = resourcePageCategories.indexOf(currentCategory); - const nextIndex = - (((index + normalizedStepOffset) % resourcePageCategories.length) + - resourcePageCategories.length) % - resourcePageCategories.length; - const nextCategory = resourcePageCategories[nextIndex]; - if (!nextCategory) { - return; - } - openResourceBookChildRef.current(nextCategory, true); - }, - [resourcePageCategories], - ); - useEffect(() => { if (mode !== 'resources') { return undefined; @@ -2626,48 +2555,16 @@ export default function ProjectDevelopmentView({ return undefined; } const zoomFrames = resourceSectionZoomFramesRef.current; - const pageWheelState = resourceCanvasPageWheelRef.current; - const pageWheelSwitchIntervalMs = 180; - const maxQueuedPageWheelSteps = Math.max( - 1, - resourcePageCategories.length - 1, - ); - const flushPageWheel = () => { - const state = resourceCanvasPageWheelRef.current; - const result = takeResourceCanvasPageWheelStep( - state, - performance.now(), - pageWheelSwitchIntervalMs, - ); - const waitMs = result.waitMs; - if (waitMs > 0) { - if (state.pendingTimerId === null) { - const timerId = window.setTimeout(() => { - const latestState = resourceCanvasPageWheelRef.current; - if (latestState.pendingTimerId !== timerId) { - return; - } - latestState.pendingTimerId = null; - flushPageWheel(); - }, waitMs); - state.pendingTimerId = timerId; - } - return; - } - if (result.stepOffset !== 0) { - advanceResourceCanvasPage(result.stepOffset); - } - }; const resolveViewport = (event: Event) => { const target = event.target; return target instanceof Element ? (target.closest('[data-resource-section-scroll]') ?? resourceCanvasRef.current?.querySelector( - `[data-resource-section-scroll="${renderedResourceCategoryRef.current ?? ''}"]`, + `[data-resource-section-scroll="${activePageCategory ?? ''}"]`, ) ?? null) : (resourceCanvasRef.current?.querySelector( - `[data-resource-section-scroll="${renderedResourceCategoryRef.current ?? ''}"]`, + `[data-resource-section-scroll="${activePageCategory ?? ''}"]`, ) ?? null); }; const wheel = (event: WheelEvent) => { @@ -2675,44 +2572,13 @@ export default function ProjectDevelopmentView({ if (target?.closest('[data-resource-wheel-native="true"]')) { return; } - if (resourcePageCategories.length > 0) { - event.preventDefault(); - if (event.ctrlKey || event.metaKey) { - resourceBookTransitionControllerRef.current.settle(); - cancelQueuedResourceCanvasPageWheel(); - const viewport = resolveViewport(event); - const category = viewport?.dataset.resourceSectionScroll as - | ResourceCategory - | undefined; - if (!viewport || !category) { - return; - } - const rect = viewport.getBoundingClientRect(); - const result = resolveViewportFromWheel({ - viewport: resourceCanvasViewportRef.current, - deltaX: event.deltaX, - deltaY: event.deltaY, - shiftKey: event.shiftKey, - ctrlKey: event.ctrlKey, - metaKey: event.metaKey, - screenPoint: { - x: event.clientX - rect.left, - y: event.clientY - rect.top, - }, - }); - setResourceCanvasViewport(category, result.viewport, true); - return; - } - if (!Number.isFinite(event.deltaY) || event.deltaY === 0) { - return; - } - queueResourceCanvasPageWheel(resourceCanvasPageWheelRef.current, { - deltaY: event.deltaY, - deltaMode: event.deltaMode, - canvasHeight: canvas.clientHeight, - maxQueuedSteps: maxQueuedPageWheelSteps, - }); - flushPageWheel(); + if ( + resourcePageCategories.length > 0 && + !event.ctrlKey && + !event.metaKey + ) { + // Ordinary wheel input is handled by the resource-book manager and + // pans the active resource view instead of changing sections. return; } if (!event.ctrlKey && !event.metaKey) { @@ -2736,11 +2602,9 @@ export default function ProjectDevelopmentView({ event.clientY, ); }; - resourceCanvasWheelHandlerRef.current = wheel; const gestureStart = (rawEvent: Event) => { resourceBookTransitionControllerRef.current.settle(); const event = rawEvent as WebKitGestureEvent; - cancelQueuedResourceCanvasPageWheel(); const viewport = resolveViewport(event); const category = viewport?.dataset.resourceSectionScroll as | ResourceCategory @@ -2806,33 +2670,21 @@ export default function ProjectDevelopmentView({ canvas.addEventListener('gestureend', gestureEnd, { passive: false }); return () => { canvas.removeEventListener('wheel', wheel); - if (resourceCanvasWheelHandlerRef.current === wheel) { - resourceCanvasWheelHandlerRef.current = null; - } canvas.removeEventListener('gesturestart', gestureStart); canvas.removeEventListener('gesturechange', gestureChange); canvas.removeEventListener('gestureend', gestureEnd); resourceSectionGestureRef.current = null; - if (pageWheelState.pendingTimerId !== null) { - window.clearTimeout(pageWheelState.pendingTimerId); - } - resetResourceCanvasPageWheel( - pageWheelState, - renderedResourceCategoryRef.current, - ); for (const pending of zoomFrames.values()) { window.cancelAnimationFrame(pending.frameId); } zoomFrames.clear(); }; }, [ - advanceResourceCanvasPage, applyAnchoredResourceSectionZoom, - cancelQueuedResourceCanvasPageWheel, + activePageCategory, mode, queueResourceSectionZoomScroll, resourcePageCategories.length, - setResourceCanvasViewport, setResourceSectionZoom, sortMode, ]); @@ -3047,10 +2899,6 @@ export default function ProjectDevelopmentView({ setResourceCanvasViewports(defaultViewports); resourceCanvasFitKeysRef.current.clear(); resourceCanvasViewportRef.current = defaultViewports.dependency.document; - if (resourceCanvasPageWheelRef.current.pendingTimerId !== null) { - window.clearTimeout(resourceCanvasPageWheelRef.current.pendingTimerId); - } - resetResourceCanvasPageWheel(resourceCanvasPageWheelRef.current, null); cancelResourceCardDrag(); resourceCanvasPanRef.current = null; resourceDependencyOverlayRef.current?.clearDragPreview(); @@ -3508,7 +3356,6 @@ export default function ProjectDevelopmentView({ const zoomResourceBookBy = useCallback( (factor: number) => { resourceBookTransitionControllerRef.current.settle(); - cancelQueuedResourceCanvasPageWheel(); const sceneElement = resourceBookManagerRef.current; const rect = sceneElement?.getBoundingClientRect(); const anchorX = rect ? rect.width / 2 : 0; @@ -3540,20 +3387,12 @@ export default function ProjectDevelopmentView({ activePageCategory, resourceBookView, setResourceCanvasViewport, - cancelQueuedResourceCanvasPageWheel, ], ); const handleResourceBookWheel = useCallback( (event: ReactWheelEvent | WheelEvent) => { - if (resourceBookView !== 'main' && !(event.ctrlKey || event.metaKey)) { - const nativeEvent = 'nativeEvent' in event ? event.nativeEvent : event; - resourceCanvasWheelHandlerRef.current?.(nativeEvent); - nativeEvent.preventDefault(); - return; - } resourceBookTransitionControllerRef.current.settle(); - cancelQueuedResourceCanvasPageWheel(); const sceneElement = resourceBookManagerRef.current; if (!sceneElement) { return; @@ -3598,19 +3437,18 @@ export default function ProjectDevelopmentView({ activePageCategory, resourceBookView, setResourceCanvasViewport, - cancelQueuedResourceCanvasPageWheel, ], ); useEffect(() => { - const main = resourceBookManagerRef.current?.querySelector( - '.game-resource-book-main', - ); - if (!main) return; + const manager = resourceBookManagerRef.current; + if (!manager) return; // React wheel listeners are passive; canvas gestures must suppress native // scrolling/zooming even when all content has been panned off screen. - main.addEventListener('wheel', handleResourceBookWheel, { passive: false }); - return () => main.removeEventListener('wheel', handleResourceBookWheel); + manager.addEventListener('wheel', handleResourceBookWheel, { + passive: false, + }); + return () => manager.removeEventListener('wheel', handleResourceBookWheel); }, [handleResourceBookWheel, mode]); const handleResourceBookMainPointerDown = useCallback( @@ -3693,7 +3531,6 @@ export default function ProjectDevelopmentView({ if (!Number.isFinite(scale) || scale <= 0) { return; } - cancelQueuedResourceCanvasPageWheel(); event.currentTarget.setPointerCapture?.(event.pointerId); resourceCardDragRef.current = { pointerId: event.pointerId, @@ -3708,7 +3545,7 @@ export default function ProjectDevelopmentView({ changed: false, }; }, - [activePageCategory, cancelQueuedResourceCanvasPageWheel], + [activePageCategory], ); const handleResourceCardPointerMove = useCallback( @@ -3813,7 +3650,6 @@ export default function ProjectDevelopmentView({ ) { return; } - cancelQueuedResourceCanvasPageWheel(); event.currentTarget.setPointerCapture(event.pointerId); resourceBookTransitionControllerRef.current.settle(); resourceCanvasPanRef.current = { @@ -3824,7 +3660,7 @@ export default function ProjectDevelopmentView({ startViewport: resourceCanvasViewportRef.current, }; }, - [activePageCategory, cancelQueuedResourceCanvasPageWheel], + [activePageCategory], ); const handleResourceCanvasPointerMove = useCallback( @@ -3866,7 +3702,6 @@ export default function ProjectDevelopmentView({ advanceFocusGeneration(); cancelResourceCardDrag(); cancelResourceCanvasPan(); - cancelQueuedResourceCanvasPageWheel(); captureResourceListScrollPosition(); captureResourceSectionScrollPositions(); setSortMode(nextMode); @@ -5731,7 +5566,7 @@ export default function ProjectDevelopmentView({ ) : null}
- 资源画本 -

主画布

+

资源总览

{canvasResources.length > 0 ? `${canvasResources.length} 项正式与候选资源` - : '从子画布开始整理资源'} + : '从资源栏目开始整理资源'}
@@ -5867,7 +5701,7 @@ export default function ProjectDevelopmentView({