Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 13 additions & 95 deletions src/components/canvas/CanvasWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,9 @@ import {
getAddChipPosition,
getAddFrameSize,
} from '@/lib/canvas/workspace-layout'
import {
applySnapping,
computeSnap,
} from '@/lib/canvas/helpers'
import {
getAbsoluteNodePosition,
setAbsoluteNodePosition,
} from '@/lib/canvas/coordinates'
import { rectsIntersectViewport } from '@/lib/canvas/perf/viewport'
import { useBatchDraw } from '@/lib/canvas/perf/batch-draw'
import { applyKonvaPixelRatio } from '@/lib/canvas/perf/konva-config'
import { exportActiveScreenBlobFromStage } from '@/lib/canvas/konva-export'
import {
SELECTION_BLUE,
SELECTION_HANDLE_FILL,
Expand All @@ -41,11 +32,13 @@ import { useCanvasSelection } from '@/hooks/useCanvasSelection'
import { useCanvasDrop } from '@/hooks/useCanvasDrop'
import { useCanvasTextEdit } from '@/hooks/useCanvasTextEdit'
import { useScreenContextMenu } from '@/hooks/useScreenContextMenu'
import { useKonvaStageBridge } from '@/hooks/useKonvaStageBridge'
import { useCanvasSnapping } from '@/hooks/useCanvasSnapping'
import { cn } from '@/lib/utils'
import { useEditorStore } from '@/stores/editor-store'
import { useProjectStore } from '@/stores/project-store'
import { useSettingsStore } from '@/stores/settings-store'
import type { Element, Screen } from '@/lib/types'
import type { Screen } from '@/lib/types'

interface CanvasWorkspaceProps {
screens: Screen[]
Expand All @@ -63,8 +56,6 @@ export function CanvasWorkspace({ screens, assetResolver }: CanvasWorkspaceProps

const screenLayout = useEditorStore((state) => state.screenLayout)
const syncScreenLayout = useEditorStore((state) => state.syncScreenLayout)
const setIsSpacePressed = useEditorStore((state) => state.setIsSpacePressed)
const setIsPanning = useEditorStore((state) => state.setIsPanning)
const requestFit = useEditorStore((state) => state.requestFit)

const scheduleDraw = useBatchDraw(stageRef)
Expand Down Expand Up @@ -160,7 +151,7 @@ export function CanvasWorkspace({ screens, assetResolver }: CanvasWorkspaceProps
const canvasCheckerboard = useSettingsStore((state) => state.preferences.workspace.canvasCheckerboard)
const highDpiCanvas = useSettingsStore((state) => state.preferences.workspace.highDpiCanvas)

const setKonvaStageBridge = useEditorStore((state) => state.setKonvaStageBridge)
useKonvaStageBridge({ stageRef, activeScreenId })

useEffect(() => {
applyKonvaPixelRatio(highDpiCanvas)
Expand All @@ -181,88 +172,15 @@ export function CanvasWorkspace({ screens, assetResolver }: CanvasWorkspaceProps
if (pos) overlayRef.current?.setScreenOffset(pos.x, pos.y)
}, [activeScreenId, screenLayout])

useEffect(() => {
const stage = stageRef.current
if (!stage) {
setKonvaStageBridge(null)
return
}

setKonvaStageBridge({
activeScreenId,
exportActiveScreen: async (screenId, options) => {
const currentStage = stageRef.current
if (!currentStage || screenId !== activeScreenId) return null
return exportActiveScreenBlobFromStage(
{ stage: currentStage, screenId, isActiveOnCanvas: true },
options,
)
},
})

return () => setKonvaStageBridge(null)
}, [activeScreenId, setKonvaStageBridge])

useEffect(() => {
const onKeyDown = (event: KeyboardEvent) => {
if (event.code === 'Space' && !(event.target as HTMLElement).matches('input, textarea')) {
setIsSpacePressed(true)
}
}
const onKeyUp = (event: KeyboardEvent) => {
if (event.code === 'Space') {
setIsSpacePressed(false)
setIsPanning(false)
}
}
window.addEventListener('keydown', onKeyDown)
window.addEventListener('keyup', onKeyUp)
return () => {
window.removeEventListener('keydown', onKeyDown)
window.removeEventListener('keyup', onKeyUp)
}
}, [setIsPanning, setIsSpacePressed])

const handleElementChange = (screenId: string, id: string, patch: Partial<Element>) => {
setActiveScreenId(screenId)
const screen = screens.find((item) => item.id === screenId)
const element = screen?.elements.find((item) => item.id === id)
if (!element || !screen) return

let next = { ...element, ...patch } as Element
if (showSmartGuides && ('x' in patch || 'y' in patch)) {
next = applySnapping(
next,
screen.elements.filter((item) => item.id !== id),
screen.width,
screen.height,
snapSensitivity,
)
}
overlayRef.current?.clear()
updateElement(id, next)
}

const handleDragMove = (screenId: string, id: string, node: Konva.Node) => {
setActiveScreenId(screenId)
if (!showSmartGuides) return
const screen = screens.find((item) => item.id === screenId)
const element = screen?.elements.find((item) => item.id === id)
if (!screen || !element) return
const absolute = getAbsoluteNodePosition(element, screen, node)
const moving = { ...element, x: absolute.x, y: absolute.y } as Element
const others = screen.elements.filter((item) => item.id !== id)
const { x, y, lines } = computeSnap(
moving,
others,
screen.width,
screen.height,
snapSensitivity,
)
setAbsoluteNodePosition(element, screen, node, x, y)
overlayRef.current?.setGuides(lines, screen.width, screen.height)
scheduleDraw()
}
const { handleElementChange, handleDragMove } = useCanvasSnapping({
screens,
showSmartGuides,
snapSensitivity,
updateElement,
setActiveScreenId,
overlayRef,
scheduleDraw,
})

const handleAddScreen = () => {
if ((project?.screens.length ?? 0) >= MAX_SCREENS) return
Expand Down
5 changes: 3 additions & 2 deletions src/components/canvas/ElementGroupNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { memo } from 'react'
import { Group } from 'react-konva'
import type Konva from 'konva'
import { ElementNode } from '@/components/canvas/ElementNode'
import { isAdditiveKonvaPointerEvent } from '@/lib/selection/is-additive-selection'
import { useEditorStore } from '@/stores/editor-store'
import type { Element } from '@/lib/types'

Expand Down Expand Up @@ -71,11 +72,11 @@ function ElementGroupNodeInner({
draggable={isActive && !anyLocked && allSelected}
onClick={(event) => {
event.cancelBubble = true
handleGroupSelect(event.evt.shiftKey)
handleGroupSelect(isAdditiveKonvaPointerEvent(event))
}}
onTap={(event) => {
event.cancelBubble = true
handleGroupSelect(event.evt.shiftKey)
handleGroupSelect(isAdditiveKonvaPointerEvent(event))
}}
onDragEnd={(event) => {
const dx = event.target.x() - minX
Expand Down
15 changes: 9 additions & 6 deletions src/components/canvas/ElementNode.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Group, Image as KonvaImage, Line, Rect, RegularPolygon, Text, Circle }
import useImage from 'use-image'
import { clearKonvaImageCache } from '@/lib/canvas/konva-lifecycle'
import { getElementShadowProps, getGradientProps } from '@/lib/canvas/helpers'
import { isAdditiveKonvaPointerEvent } from '@/lib/selection/is-additive-selection'
import { getCachedDeviceComposite } from '@/lib/canvas/perf/device-composite-cache'
import type { DeviceElement, Element, ImageElement, ShapeElement, TextElement } from '@/lib/types'

Expand Down Expand Up @@ -80,8 +81,8 @@ function ImageNode({
opacity={element.opacity}
visible={element.visible}
draggable={draggable}
onClick={(event) => props.onSelect(element.id, event.evt.shiftKey)}
onTap={(event) => props.onSelect(element.id, event.evt.shiftKey)}
onClick={(event) => props.onSelect(element.id, isAdditiveKonvaPointerEvent(event))}
onTap={(event) => props.onSelect(element.id, isAdditiveKonvaPointerEvent(event))}
onDragMove={(event) => props.onDragMove?.(element.id, event.target)}
onDragEnd={(event) => {
props.onChange(element.id, {
Expand Down Expand Up @@ -167,8 +168,8 @@ function DeviceNode({
opacity={element.opacity}
visible={element.visible}
draggable={draggable}
onClick={(event) => props.onSelect(element.id, event.evt.shiftKey)}
onTap={(event) => props.onSelect(element.id, event.evt.shiftKey)}
onClick={(event) => props.onSelect(element.id, isAdditiveKonvaPointerEvent(event))}
onTap={(event) => props.onSelect(element.id, isAdditiveKonvaPointerEvent(event))}
onDragMove={(event) => props.onDragMove?.(element.id, event.target)}
onDragEnd={(event) => {
props.onChange(element.id, {
Expand Down Expand Up @@ -262,8 +263,10 @@ function ElementNodeInner({
draggable: isDraggable,
listening: !element.locked,
perfectDrawEnabled: hasStroke ? false : undefined,
onClick: (event: { evt: { shiftKey: boolean } }) => onSelect(element.id, event.evt.shiftKey),
onTap: (event: { evt: { shiftKey: boolean } }) => onSelect(element.id, event.evt.shiftKey),
onClick: (event: { evt: { shiftKey: boolean; metaKey: boolean; ctrlKey: boolean } }) =>
onSelect(element.id, isAdditiveKonvaPointerEvent(event)),
onTap: (event: { evt: { shiftKey: boolean; metaKey: boolean; ctrlKey: boolean } }) =>
onSelect(element.id, isAdditiveKonvaPointerEvent(event)),
onDragMove: (event: { target: Konva.Node }) => onDragMove?.(element.id, event.target),
onDragEnd: (event: { target: Konva.Node }) => {
onChange(element.id, {
Expand Down
3 changes: 2 additions & 1 deletion src/components/canvas/InactiveScreenArtboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
requestScreenSnapshot,
} from '@/lib/canvas/perf/screen-snapshot-cache'
import { screenContentSignature } from '@/lib/canvas/perf/content-signature'
import { isAdditiveKonvaPointerEvent } from '@/lib/selection/is-additive-selection'
import type { BackgroundConfig, Screen } from '@/lib/types'

function SnapshotImage({
Expand Down Expand Up @@ -111,7 +112,7 @@ export function InactiveScreenArtboard({
listening
onMouseDown={(event) => {
if (event.target !== event.currentTarget) return
onArtboardBackgroundClick(event.evt.shiftKey)
onArtboardBackgroundClick(isAdditiveKonvaPointerEvent(event))
}}
/>
</Group>
Expand Down
6 changes: 4 additions & 2 deletions src/components/canvas/ScreenArtboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { ElementNode } from '@/components/canvas/ElementNode'
import { ElementGroupNode } from '@/components/canvas/ElementGroupNode'
import { selectionStrokeWidth } from '@/lib/canvas/selection-style'
import { SELECTION_BLUE, SELECTION_BLUE_SOFT } from '@/lib/canvas/selection-style'
import { sortElementsByZIndex } from '@/lib/factories'
import { isAdditiveKonvaPointerEvent } from '@/lib/selection/is-additive-selection'
import { getCachedBackgroundCanvas } from '@/lib/canvas/perf/background-cache'
import { buildGridCanvas } from '@/lib/canvas/perf/grid-canvas'
import type { BackgroundConfig, Element } from '@/lib/types'
Expand Down Expand Up @@ -92,7 +94,7 @@ function ScreenArtboardInner({
onArtboardBackgroundClick,
}: ScreenArtboardProps) {
const sortedElements = useMemo(
() => [...elements].sort((a, b) => a.zIndex - b.zIndex),
() => sortElementsByZIndex(elements),
[elements],
)
const strokeScale = selectionStrokeWidth(workspaceZoom, 1)
Expand Down Expand Up @@ -165,7 +167,7 @@ function ScreenArtboardInner({
fill="transparent"
onMouseDown={(event) => {
if (event.target !== event.currentTarget) return
onArtboardBackgroundClick(event.evt.shiftKey)
onArtboardBackgroundClick(isAdditiveKonvaPointerEvent(event))
}}
/>

Expand Down
5 changes: 3 additions & 2 deletions src/components/canvas/ScreensOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { renderScreenToDataUrl } from '@/lib/export/renderer'
import { screenContentSignature } from '@/lib/canvas/perf/content-signature'
import { LruMap } from '@/lib/canvas/perf/lru-map'
import { enqueueThumbnailTask } from '@/lib/canvas/perf/thumbnail-queue'
import { SCREEN_OVERVIEW_ACTIVE, SCREEN_OVERVIEW_HOVER } from '@/lib/canvas/selection-style'
import { cn } from '@/lib/utils'
import type { Screen } from '@/lib/types'

Expand Down Expand Up @@ -89,8 +90,8 @@ function ScreenThumbnail({
style={style}
className={cn(
'group relative flex flex-col gap-2 rounded-lg border bg-card p-2 transition',
isActive ? 'border-[#18A0FB] ring-2 ring-[#18A0FB]/30' : 'border-border',
isDragging ? 'z-10 opacity-80 shadow-lg' : 'hover:border-[#18A0FB]/50',
isActive ? SCREEN_OVERVIEW_ACTIVE : 'border-border',
isDragging ? 'z-10 opacity-80 shadow-lg' : SCREEN_OVERVIEW_HOVER,
)}
>
<div className="flex items-center justify-between gap-2">
Expand Down
Loading
Loading