my-multiplayer-app-2 / client /components /SelectLockedComponents.tsx
Jaimodiji's picture
Fix color_rm.html UI bugs
8ab98be verified
import {
track,
useEditor,
useValue,
TldrawUiButton,
TldrawUiButtonIcon,
atom,
Box,
react,
TldrawUiButtonCheck
} from 'tldraw'
import { useEffect } from 'react'
import { LockIcon } from './Icons'
export const getSelectLockedSignal = (editor: any) => {
if (!editor._selectLockedSignal) {
editor._selectLockedSignal = atom('selectLocked', !!editor._selectLocked)
}
return editor._selectLockedSignal
}
export const SelectOptionsPanel = track(() => {
const editor = useEditor()
const isSelect = useValue('isSelect', () => editor.getCurrentToolId() === 'select', [editor])
const isSelectLocked = useValue('isSelectLocked', () => getSelectLockedSignal(editor).get(), [editor])
if (!isSelect) return null
return (
<div className="tlui-menu" style={{
position: 'absolute',
bottom: 60,
left: 12,
zIndex: 1000,
borderRadius: 'var(--radius-2)',
boxShadow: 'var(--shadow-2)',
backgroundColor: 'var(--color-panel)',
border: '1px solid var(--color-panel-contrast)',
padding: '4px',
pointerEvents: 'all',
display: 'flex',
flexDirection: 'column',
gap: '4px'
}}>
<TldrawUiButton
type="normal"
onClick={() => {
const signal = getSelectLockedSignal(editor)
const next = !signal.get()
signal.set(next)
;(editor as any)._selectLocked = next
editor.emit('select-locked-changed' as any)
}}
style={{
justifyContent: 'flex-start',
padding: '8px 12px',
height: '32px',
background: isSelectLocked ? 'var(--color-selected-primary)' : 'transparent',
color: isSelectLocked ? 'var(--color-selected-contrast)' : 'var(--color-text)',
borderRadius: 'var(--radius-1)'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<LockIcon size={14} />
<span style={{ fontSize: '12px', fontWeight: 500 }}>Select Locked</span>
<TldrawUiButtonCheck checked={isSelectLocked} />
</div>
</TldrawUiButton>
</div>
)
})
export const SelectLockedToggle = track(() => {
const editor = useEditor()
const isSelectLocked = useValue('isSelectLocked', () => getSelectLockedSignal(editor).get(), [editor])
return (
<TldrawUiButton
type="icon"
title="Select Locked Objects"
isActive={isSelectLocked}
onClick={() => {
const signal = getSelectLockedSignal(editor)
const next = !signal.get()
signal.set(next)
;(editor as any)._selectLocked = next
editor.emit('select-locked-changed' as any)
}}
>
<TldrawUiButtonIcon icon={isSelectLocked ? 'lock' : 'unlock'} />
</TldrawUiButton>
)
})
export function SelectLockedLogic() {
const editor = useEditor()
// Handle click-to-select locked shapes
// We use a capturing listener on the window to intercept the event BEFORE Tldraw processes it.
// This prevents Tldraw from treating the click on a locked shape as a background click (which deselects everything).
useEffect(() => {
const handlePointerDown = (e: PointerEvent) => {
const isSelectLocked = getSelectLockedSignal(editor).get()
if (!isSelectLocked) return
// Only intervene if we are using the select tool
if (editor.getCurrentToolId() !== 'select') return
// Basic check: is this a click on the canvas?
const target = e.target as HTMLElement
// Allow clicks on UI elements (context menus, panels) to pass through
if (target.closest('.tl-ui-layout') || target.closest('.tl-context-menu')) return
// Allow clicks on interactive elements inside shapes
if (target.tagName === 'BUTTON' || target.tagName === 'INPUT' || target.closest('button')) return
if (!target || !target.closest('.tl-canvas')) return
// Convert screen point to page point
const point = editor.screenToPage({ x: e.clientX, y: e.clientY })
// Find if we hit a locked shape
// We check hitInside: true because we want to select filled shapes by clicking inside
// Using a slightly larger margin for easier selection
const zoom = editor.getZoomLevel()
const margin = 5 / zoom
const shape = editor.getShapeAtPoint(point, { hitInside: true, margin })
if (shape && shape.isLocked) {
// We found a locked shape!
// Stop Tldraw from processing this event (which would clear selection)
e.stopPropagation()
e.preventDefault()
// Select the shape
editor.setSelectedShapes([shape.id])
return
}
}
// Use capture: true to intercept before Tldraw
window.addEventListener('pointerdown', handlePointerDown, { capture: true })
return () => {
window.removeEventListener('pointerdown', handlePointerDown, { capture: true })
}
}, [editor])
// Handle drag-to-select (brush) locked shapes
useEffect(() => {
return react('check-brush-locked', () => {
const isSelectLocked = getSelectLockedSignal(editor).get()
if (!isSelectLocked) return
if (editor.getCurrentToolId() !== 'select') return
const brushModel = editor.getInstanceState().brush
if (!brushModel) return
// Standard brush logic for locked shapes
const brush = Box.From(brushModel)
const shapes = editor.getCurrentPageShapesSorted()
// Find all locked shapes that collide with the brush
const lockedInBrush = new Set(shapes.filter(shape => {
if (!shape.isLocked) return false
const bounds = editor.getShapePageBounds(shape)
if (!bounds) return false
return brush.collides(bounds)
}).map(s => s.id))
// Get current selection
const currentSelected = new Set(editor.getSelectedShapeIds())
// Add any locked shapes that are in the brush but not selected
let needsUpdate = false
const newSelection = new Set(currentSelected)
for (const id of lockedInBrush) {
if (!currentSelected.has(id)) {
newSelection.add(id)
needsUpdate = true
}
}
if (needsUpdate) {
editor.setSelectedShapes(Array.from(newSelection))
}
})
}, [editor])
return null
}