refactor(core): 模型路由与画布 - 收敛渠道模型映射并优化交互
This commit is contained in:
@@ -195,12 +195,20 @@ export function CanvasTopBar({
|
||||
|
||||
<div className="canvas-topbar-cluster pointer-events-auto flex items-center gap-1.5">
|
||||
<Button type="text" className="!hidden !h-10 !w-10 !min-w-10 !rounded-xl !p-0 lg:!inline-flex" style={{ color: theme.node.text }} icon={<Search className="size-4" />} onClick={onOpenSearch} aria-label="搜索画布节点" title="搜索画布节点" />
|
||||
<Button type="text" className="canvas-topbar-import-button !h-10 !rounded-xl !px-2.5 !font-medium" style={{ color: theme.node.text }} icon={<CopyPlus className="size-4" />} onClick={onImportLibTV} aria-label="一键导入 LibTV 画布" title="一键导入 LibTV 画布">
|
||||
<span className="hidden xl:inline">导入 LibTV</span>
|
||||
</Button>
|
||||
<Button type="text" className="canvas-topbar-import-button !h-10 !rounded-xl !px-2.5 !font-medium" style={{ color: theme.node.text }} icon={<CloudDownload className="size-4" />} onClick={onImportTapNow} aria-label="导入 TapNow 画布" title="导入 TapNow 画布">
|
||||
<span className="hidden xl:inline">导入 TapNow</span>
|
||||
</Button>
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
placement="bottomRight"
|
||||
menu={{
|
||||
items: [
|
||||
{ key: "libtv", icon: <CopyPlus className="size-4" />, label: "导入 LibTV 画布", onClick: onImportLibTV },
|
||||
{ key: "tapnow", icon: <CloudDownload className="size-4" />, label: "导入 TapNow 画布", onClick: onImportTapNow },
|
||||
],
|
||||
}}
|
||||
>
|
||||
<Button type="text" className="canvas-topbar-import-button !h-10 !rounded-xl !px-2.5 !font-medium" style={{ color: theme.node.text }} icon={<CloudDownload className="size-4" />} aria-label="导入第三方画布" title="导入第三方画布">
|
||||
<span className="hidden lg:inline">导入第三方画布</span>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
menu={{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CSSProperties, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, ReactNode, RefObject } from "react";
|
||||
import { memo, type CSSProperties, type MouseEvent as ReactMouseEvent, type PointerEvent as ReactPointerEvent, type ReactNode, type RefObject } from "react";
|
||||
import { Link2 } from "lucide-react";
|
||||
|
||||
import { ConnectionPath } from "@/components/canvas/canvas-connections";
|
||||
@@ -75,7 +75,7 @@ type CanvasProjectWorldLayersProps = {
|
||||
const EMPTY_RESOURCE_REFERENCES: CanvasResourceReference[] = [];
|
||||
const EMPTY_CANVAS_NODES: CanvasNodeData[] = [];
|
||||
|
||||
export function CanvasProjectWorldLayers(props: CanvasProjectWorldLayersProps) {
|
||||
export const CanvasProjectWorldLayers = memo(function CanvasProjectWorldLayers(props: CanvasProjectWorldLayersProps) {
|
||||
const { viewportScale } = props;
|
||||
return (
|
||||
<>
|
||||
@@ -126,7 +126,6 @@ export function CanvasProjectWorldLayers(props: CanvasProjectWorldLayersProps) {
|
||||
isRelated={props.relatedNodeIds.has(node.id)}
|
||||
isFocusRelated={props.activeNodeId === node.id}
|
||||
isConnectionTarget={props.connectionTargetNodeId === node.id || props.batchConnectionPreview?.targetNodeId === node.id}
|
||||
isConnecting={Boolean(props.connectingParams || props.batchConnectionPreview)}
|
||||
forceInputVisible={Boolean(props.batchConnectionPreview)}
|
||||
batchCount={props.batchChildCountById.get(node.id) || 0}
|
||||
batchExpanded={Boolean(node.metadata?.imageBatchExpanded)}
|
||||
@@ -136,7 +135,7 @@ export function CanvasProjectWorldLayers(props: CanvasProjectWorldLayersProps) {
|
||||
batchPrimary={Boolean(node.metadata?.batchRootId && props.nodeById.get(node.metadata.batchRootId)?.metadata?.primaryImageId === node.id)}
|
||||
batchMotion={props.batchMotionById.get(node.id)}
|
||||
showImageInfo={props.showImageInfo}
|
||||
reduceMediaEffects={props.reduceMediaEffects || props.isNodeDragging || props.mediaEffectsDisabledNodeId === node.id}
|
||||
reduceMediaEffects={props.reduceMediaEffects || props.mediaEffectsDisabledNodeId === node.id}
|
||||
resourceLabel={props.resourceReferenceByNodeId.get(node.id)}
|
||||
mentionReferences={props.mentionReferencesByNodeId.get(node.id) || EMPTY_RESOURCE_REFERENCES}
|
||||
renderNodeContent={props.renderCanvasNodeContent}
|
||||
@@ -187,7 +186,7 @@ export function CanvasProjectWorldLayers(props: CanvasProjectWorldLayersProps) {
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function BatchConnectionHandle({ scale, count, active, onPointerDown }: { scale: number; count: number; active: boolean; onPointerDown: (event: ReactPointerEvent) => void }) {
|
||||
const inverseScale = 1 / Math.max(scale, 0.05);
|
||||
|
||||
@@ -560,6 +560,7 @@ function InfiniteCanvasPage() {
|
||||
setContextMenu,
|
||||
setDialogNodeId,
|
||||
});
|
||||
const replaceCanvasNodeMedia = useCallback((node: CanvasNodeData) => handleUploadRequest(node.id), [handleUploadRequest]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectLoaded || !assetsHydrated || searchParams.get("mode") !== "handoff") return;
|
||||
@@ -1840,7 +1841,7 @@ function InfiniteCanvasPage() {
|
||||
onOpenTaskDetails={openCanvasNodeTaskDetails}
|
||||
onOpenVersions={openCanvasNodeVersions}
|
||||
onViewImage={viewCanvasNodeImage}
|
||||
onReplaceMedia={(node) => handleUploadRequest(node.id)}
|
||||
onReplaceMedia={replaceCanvasNodeMedia}
|
||||
onOpenTextEditor={openTextNodeEditor}
|
||||
onOpenDirector={editCanvasDirector}
|
||||
onOpenDrawing={openDrawingNode}
|
||||
|
||||
@@ -244,7 +244,7 @@ export default function SharedCanvasPage() {
|
||||
const dragged = [node, ...(frameChildrenById.get(nodeId) || [])];
|
||||
dragRef.current = { primaryId: nodeId, nodeIds: dragged.map((item) => item.id), startX: event.clientX, startY: event.clientY, origins: new Map(dragged.map((item) => [item.id, item.position])), moved: false };
|
||||
document.body.style.cursor = "grabbing";
|
||||
}} onResize={() => undefined} onToggleCollapsed={toggleFrame} onTitleChange={unauthorized} onHoverStart={keepToolbar} onHoverEnd={hideToolbar} onContextMenu={(event, nodeId) => openContextMenu(event, nodeId)} /> : <CanvasNode key={node.id} data={node} dragOffset={dragRef.current?.nodeIds.includes(node.id) && dragOffset ? dragOffset : undefined} scale={viewport.k} isSelected={selectedNodeId === node.id} isRelated={false} isFocusRelated={false} isConnectionTarget={false} isConnecting={false} showImageInfo={false} readOnly renderNodeContent={renderSharedNode} onMouseDown={(event, nodeId) => {
|
||||
}} onResize={() => undefined} onToggleCollapsed={toggleFrame} onTitleChange={unauthorized} onHoverStart={keepToolbar} onHoverEnd={hideToolbar} onContextMenu={(event, nodeId) => openContextMenu(event, nodeId)} /> : <CanvasNode key={node.id} data={node} dragOffset={dragRef.current?.nodeIds.includes(node.id) && dragOffset ? dragOffset : undefined} scale={viewport.k} isSelected={selectedNodeId === node.id} isRelated={false} isFocusRelated={false} isConnectionTarget={false} showImageInfo={false} readOnly renderNodeContent={renderSharedNode} onMouseDown={(event, nodeId) => {
|
||||
event.stopPropagation();
|
||||
if (event.button !== 0) return;
|
||||
const target = nodes.find((item) => item.id === nodeId);
|
||||
|
||||
@@ -110,7 +110,7 @@ export function useCanvasRenderModel({
|
||||
return { left, top, width: Math.max(2, right - left), height: Math.max(2, bottom - top) };
|
||||
}, [nodes]);
|
||||
const renderBounds = useMemo(() => {
|
||||
const padding = (reduceMediaEffects ? Math.max(240, Math.max(viewportSize.width, viewportSize.height) * 0.4) : Math.max(800, Math.max(viewportSize.width, viewportSize.height) * 1.5)) / viewport.k;
|
||||
const padding = (reduceMediaEffects ? 240 : 360) / viewport.k;
|
||||
const viewLeft = -viewport.x / viewport.k - padding;
|
||||
const viewTop = -viewport.y / viewport.k - padding;
|
||||
const viewRight = viewLeft + viewportSize.width / viewport.k + padding * 2;
|
||||
@@ -121,11 +121,13 @@ export function useCanvasRenderModel({
|
||||
const frames: CanvasNodeData[] = [];
|
||||
const regular: CanvasNodeData[] = [];
|
||||
nodes.forEach((node) => {
|
||||
if (renderHiddenNodeIds.has(node.id) || node.position.x + node.width <= renderBounds.left || node.position.x >= renderBounds.right || node.position.y + node.height <= renderBounds.top || node.position.y >= renderBounds.bottom) return;
|
||||
if (renderHiddenNodeIds.has(node.id)) return;
|
||||
const retained = selectedNodeIds.has(node.id) || Boolean(dragPreview?.nodeIds.has(node.id));
|
||||
if (!retained && (node.position.x + node.width <= renderBounds.left || node.position.x >= renderBounds.right || node.position.y + node.height <= renderBounds.top || node.position.y >= renderBounds.bottom)) return;
|
||||
(isFrameNode(node) ? frames : regular).push(node);
|
||||
});
|
||||
return [...frames, ...regular];
|
||||
}, [nodes, renderBounds, renderHiddenNodeIds]);
|
||||
}, [dragPreview, nodes, renderBounds, renderHiddenNodeIds, selectedNodeIds]);
|
||||
|
||||
const imageAssets = useMemo(() => assets.filter((asset): asset is ImageAsset => asset.kind === "image"), [assets]);
|
||||
const canvasImageNodes = useMemo(() => nodes.filter((node) => node.type === CanvasNodeType.Image && Boolean(node.metadata?.content) && !collapsedBatchChildIds.has(node.id) && !(node.parentId && nodeById.get(node.parentId)?.metadata?.frame?.collapsed)), [collapsedBatchChildIds, nodeById, nodes]);
|
||||
|
||||
Reference in New Issue
Block a user