Spaces:
Sleeping
Sleeping
File size: 6,122 Bytes
e1753d8 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 | import { useEffect } from 'react'
import { useEditor, react } from 'tldraw'
import { toolBrushStylesAtom } from '../utils/brushUtils'
export function BrushManager() {
const editor = useEditor()
// Tools that should have completely independent styles
const DRAWING_TOOLS = ['draw', 'highlight']
// 1. Capture tools' brushes ONLY when user actively changes them (not on tool switch)
useEffect(() => {
let previousStyles: Record<string, any> = {}
let previousTool = editor.getCurrentToolId()
const unreact = react('capture-user-brush', () => {
const selection = editor.getSelectedShapeIds()
const toolId = editor.getCurrentToolId()
const activeStyles = editor.getInstanceState().stylesForNextShape
// Skip select tool
if (toolId === 'select') return
// Detect if tool changed
const toolChanged = toolId !== previousTool
previousTool = toolId
// For drawing tools: ONLY capture if user changes style while already on the tool
// Do NOT capture when switching TO the tool (prevents contamination)
if (DRAWING_TOOLS.includes(toolId)) {
if (toolChanged) {
// Just switched to this tool - don't capture, just record current state
previousStyles[toolId] = JSON.parse(JSON.stringify(activeStyles))
return
}
// Already on this tool - check if user changed something
const prevForThisTool = previousStyles[toolId]
if (prevForThisTool && JSON.stringify(prevForThisTool) !== JSON.stringify(activeStyles)) {
// User actively changed the style! Save it.
const currentStored = toolBrushStylesAtom.get()
toolBrushStylesAtom.set({
...currentStored,
[toolId]: JSON.parse(JSON.stringify(activeStyles))
})
previousStyles[toolId] = JSON.parse(JSON.stringify(activeStyles))
}
return
}
// For non-drawing tools: capture when no selection
if (selection.length === 0) {
const currentStored = toolBrushStylesAtom.get()
if (JSON.stringify(currentStored[toolId]) !== JSON.stringify(activeStyles)) {
toolBrushStylesAtom.set({
...currentStored,
[toolId]: JSON.parse(JSON.stringify(activeStyles))
})
}
}
})
return () => {
unreact()
}
}, [editor])
// 2. Restore the saved brush for ALL tools (including drawing tools)
useEffect(() => {
const restoreBrush = () => {
const toolId = editor.getCurrentToolId()
// Skip select tool only
if (toolId === 'select') return
const storedStyles = toolBrushStylesAtom.get()[toolId]
if (storedStyles && Object.keys(storedStyles).length > 0) {
editor.run(() => {
for (const [id, value] of Object.entries(storedStyles)) {
// Use the internal style ID to set the next style
editor.setStyleForNextShapes({ id } as any, value, { history: 'ignore' })
}
}, { history: 'ignore' })
}
}
// Restore when:
// - Tool changes
// - Selection is cleared
// - A pointer operation finishes (to overwrite tldraw's auto-sync-to-selection)
const handleEvent = (e: any) => {
if (e.name === 'tool_change') {
restoreBrush()
}
if (e.name === 'pointer_up') {
// Wait for tldraw to finish its own sync-to-selection
setTimeout(restoreBrush, 20)
}
}
editor.on('event', handleEvent)
// Also watch for selection becoming empty via react
const unreact = react('restore-on-deselect', () => {
const selection = editor.getSelectedShapeIds()
if (selection.length === 0) {
restoreBrush()
}
})
return () => {
editor.off('event', handleEvent)
unreact()
}
}, [editor])
// 3. EXPLICIT PROTECTION: Prevent drawing tools from syncing styles from selections
useEffect(() => {
let lastToolId = editor.getCurrentToolId()
return react('block-drawing-tool-sync', () => {
const toolId = editor.getCurrentToolId()
const selection = editor.getSelectedShapeIds()
// Detect tool change
if (toolId !== lastToolId) {
lastToolId = toolId
// If we switched TO a drawing tool AND there's a selection
// Force restore the drawing tool's saved styles immediately
if (DRAWING_TOOLS.includes(toolId) && selection.length > 0) {
const storedStyles = toolBrushStylesAtom.get()[toolId]
if (storedStyles && Object.keys(storedStyles).length > 0) {
// Multiple aggressive restores to override tldraw's sync
for (let i = 0; i < 3; i++) {
setTimeout(() => {
editor.run(() => {
for (const [id, value] of Object.entries(storedStyles)) {
editor.setStyleForNextShapes({ id } as any, value, { history: 'ignore' })
}
}, { history: 'ignore' })
}, i * 10)
}
}
}
}
})
}, [editor])
return null
} |