From a3fed35cbdc29c3532c13f7dc075026f9346887d Mon Sep 17 00:00:00 2001 From: kdletters Date: Mon, 15 Jun 2026 15:56:30 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=9B=BE=E7=89=87=E7=94=BB?= =?UTF-8?q?=E5=B8=83=E7=B4=A0=E6=9D=90=E4=B8=8E=E4=B8=8A=E4=B8=8B=E6=96=87?= =?UTF-8?q?=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加素材拖拽上传遮罩、素材移动和默认文件夹归一 补齐画布右键菜单、复制剪切粘贴、层级和翻转操作 补齐生成图入库、项目返回入口和项目名称标题 扩展画布测试覆盖素材、右键菜单、生成图和布局交互 --- .../ImageCanvasEditorPrimitives.tsx | 16 + .../ImageCanvasEditorView.test.tsx | 877 +++++++++- .../image-editor/ImageCanvasEditorView.tsx | 1449 ++++++++++++++--- src/index.css | 111 ++ 4 files changed, 2182 insertions(+), 271 deletions(-) diff --git a/src/components/image-editor/ImageCanvasEditorPrimitives.tsx b/src/components/image-editor/ImageCanvasEditorPrimitives.tsx index 86a7edb6..8b82d2b5 100644 --- a/src/components/image-editor/ImageCanvasEditorPrimitives.tsx +++ b/src/components/image-editor/ImageCanvasEditorPrimitives.tsx @@ -1,6 +1,7 @@ import type { ComponentType, DragEventHandler, + MouseEventHandler, PointerEventHandler, ReactNode, } from 'react'; @@ -68,9 +69,14 @@ export type SidebarMediaItemProps = { primaryClassName?: string; actions?: ReactNode; titleNode?: ReactNode; + draggable?: boolean; + onDragStart?: DragEventHandler; + onDragEnd?: DragEventHandler; onDragOver?: DragEventHandler; onDrop?: DragEventHandler; + onPointerDown?: PointerEventHandler; onPointerEnter?: PointerEventHandler; + onContextMenu?: MouseEventHandler; }; export function SidebarMediaItem({ @@ -87,16 +93,26 @@ export function SidebarMediaItem({ primaryClassName, actions, titleNode, + draggable, + onDragStart, + onDragEnd, onDragOver, onDrop, + onPointerDown, onPointerEnter, + onContextMenu, }: SidebarMediaItemProps) { return (
) : (
@@ -2625,16 +3493,24 @@ export function ImageCanvasEditorView() { selectSingleLayer(layer.id)} rowClassName="image-canvas-editor__layer-row" primaryClassName="image-canvas-editor__layer-row-button" thumbnailClassName="image-canvas-editor__layer-row-thumb" metaClassName="image-canvas-editor__layer-row-meta" + onContextMenu={(event) => handleLayerContextMenu(event, layer)} /> ))}
@@ -2644,9 +3520,19 @@ export function ImageCanvasEditorView() {
-
-

图片编辑器

- 画布 +
+ + +
+

{projectTitle}

+ 画布 +
@@ -2661,8 +3547,19 @@ export function ImageCanvasEditorView() { onPointerCancel={finishDrag} onWheel={handleWheel} onDragOver={handleCanvasDragOver} + onDragLeave={handleCanvasDragLeave} onDrop={handleCanvasDrop} + onContextMenu={handleViewportContextMenu} > + {uploadDropTarget === 'canvas' ? ( +
+ 添加到画布 + 松开即可添加 +
+ ) : null}
!layer.hidden) .sort((left, right) => left.zIndex - right.zIndex) .map((layer) => { const isSelected = selectedLayerIds.includes(layer.id); @@ -2696,7 +3594,7 @@ export function ImageCanvasEditorView() { + + + + + ) : ( + <> + + + + +
+ + + + +
+ + + + +
+ + + +
+ + + )} +
+ ) : null} + event.stopPropagation()} > -
- setIsZoomMenuOpen((open) => !open)} - > - {formatPercent(viewport.scale)} - - {isZoomMenuOpen ? ( - - { - updateScaleFromCenter(viewport.scale * 1.16); - setIsZoomMenuOpen(false); - }} - > - 放大 - - { - updateScaleFromCenter(viewport.scale * 0.86); - setIsZoomMenuOpen(false); - }} - > - 缩小 - - { - fitLayers(); - setIsZoomMenuOpen(false); - }} - > - 显示画布所有元素 - - {[0.5, 1, 2].map((scale) => ( - { - updateScaleFromCenter(scale); - setIsZoomMenuOpen(false); - }} - > - 缩放至{Math.round(scale * 100)}% - - ))} - - ) : null} -
+ +
setIsMinimapOpen((open) => !open)} /> +
+ setIsZoomMenuOpen((open) => !open)} + > + {formatPercent(viewport.scale)} + + {isZoomMenuOpen ? ( + + { + updateScaleFromCenter(viewport.scale * 1.16); + setIsZoomMenuOpen(false); + }} + > + 放大 + + { + updateScaleFromCenter(viewport.scale * 0.86); + setIsZoomMenuOpen(false); + }} + > + 缩小 + + { + fitLayers(); + setIsZoomMenuOpen(false); + }} + > + 显示画布所有元素 + + {[0.5, 1, 2].map((scale) => ( + { + updateScaleFromCenter(scale); + setIsZoomMenuOpen(false); + }} + > + 缩放至{Math.round(scale * 100)}% + + ))} + + ) : null} +
{isMinimapOpen && minimapModel ? ( diff --git a/src/index.css b/src/index.css index d8c799d3..548a2522 100644 --- a/src/index.css +++ b/src/index.css @@ -3328,6 +3328,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { } .image-canvas-editor__icon-button, +.image-canvas-editor__project-back-button, .image-canvas-editor__floating-toolbar button, .image-canvas-editor__bottom-toolbar button, .image-canvas-editor__reset-button { @@ -3345,6 +3346,7 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { } .image-canvas-editor__icon-button:hover, +.image-canvas-editor__project-back-button:hover, .image-canvas-editor__floating-toolbar button:hover, .image-canvas-editor__bottom-toolbar button:hover, .image-canvas-editor__reset-button:hover { @@ -3487,6 +3489,40 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { pointer-events: none; } +.image-canvas-editor__upload-drop-overlay { + position: absolute; + z-index: 30; + display: grid; + place-content: center; + gap: 0.3rem; + border: 1.5px dashed rgba(37, 99, 235, 0.72); + background: rgba(239, 246, 255, 0.78); + color: #1d4ed8; + pointer-events: none; + text-align: center; + backdrop-filter: blur(2px); +} + +.image-canvas-editor__upload-drop-overlay span { + font-size: 0.82rem; + font-weight: 820; +} + +.image-canvas-editor__upload-drop-overlay strong { + font-size: 1rem; + font-weight: 920; +} + +.image-canvas-editor__upload-drop-overlay--assets { + inset: 0.45rem; + border-radius: 0.55rem; +} + +.image-canvas-editor__upload-drop-overlay--canvas { + inset: 0.85rem; + border-radius: 0.65rem; +} + .image-canvas-editor__canvas-marquee { position: absolute; z-index: 7; @@ -3701,6 +3737,24 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { padding: 0.55rem 0.7rem; } +.image-canvas-editor__topbar-title { + display: inline-flex; + min-width: 0; + align-items: center; + gap: 0.55rem; +} + +.image-canvas-editor__project-back-button { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2.15rem; + height: 2.15rem; + flex-shrink: 0; + border-radius: 0.45rem; + text-decoration: none; +} + .image-canvas-editor__zoom-menu-wrap { position: relative; z-index: 20; @@ -3799,6 +3853,63 @@ html[data-mobile-keyboard-open='true'] .platform-mobile-bottom-dock { box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.9); } +.image-canvas-editor__layer--locked { + cursor: default; +} + +.image-canvas-editor__context-menu { + position: fixed; + z-index: 80; + display: grid; + min-width: 168px; + max-height: calc(100vh - 16px); + overflow: hidden; + padding: 0.35rem; + border: 1px solid rgba(148, 163, 184, 0.32); + border-radius: 0.45rem; + background: rgba(255, 255, 255, 0.96); + box-shadow: 0 18px 52px rgba(15, 23, 42, 0.18); + color: #172033; +} + +.image-canvas-editor__context-menu button { + display: flex; + align-items: center; + width: 100%; + min-height: 2rem; + border: 0; + border-radius: 0.32rem; + background: transparent; + padding: 0 0.65rem; + color: inherit; + font-size: 0.86rem; + font-weight: 720; + text-align: left; + cursor: default; +} + +.image-canvas-editor__context-menu button:hover:not(:disabled), +.image-canvas-editor__context-menu button:focus-visible:not(:disabled) { + background: rgba(37, 99, 235, 0.1); + outline: none; +} + +.image-canvas-editor__context-menu button:disabled { + color: rgba(100, 116, 139, 0.5); +} + +.image-canvas-editor__context-menu hr { + width: 100%; + height: 1px; + border: 0; + background: rgba(148, 163, 184, 0.25); + margin: 0.3rem 0; +} + +.image-canvas-editor__context-menu-danger { + color: #b42318 !important; +} + .image-canvas-editor__generation-frame { position: absolute; z-index: 6;