implemented basic board text search function, added double click to zoom
This commit is contained in:
parent
6f5ee6a673
commit
e193789546
|
|
@ -18,6 +18,7 @@ import {
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
import { saveToPdf } from "../utils/pdfUtils"
|
import { saveToPdf } from "../utils/pdfUtils"
|
||||||
import { TLFrameShape } from "tldraw"
|
import { TLFrameShape } from "tldraw"
|
||||||
|
import { searchText } from "../utils/searchUtils"
|
||||||
|
|
||||||
const getAllFrames = (editor: Editor) => {
|
const getAllFrames = (editor: Editor) => {
|
||||||
return editor
|
return editor
|
||||||
|
|
@ -217,6 +218,16 @@ export function CustomContextMenu(props: TLUiContextMenuProps) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</TldrawUiMenuGroup>
|
</TldrawUiMenuGroup>
|
||||||
|
|
||||||
|
<TldrawUiMenuGroup id="search-controls">
|
||||||
|
<TldrawUiMenuItem
|
||||||
|
id="search-text"
|
||||||
|
label="Search Text"
|
||||||
|
icon="search"
|
||||||
|
kbd="s"
|
||||||
|
onSelect={() => searchText(editor)}
|
||||||
|
/>
|
||||||
|
</TldrawUiMenuGroup>
|
||||||
</DefaultContextMenu>
|
</DefaultContextMenu>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import {
|
||||||
zoomToSelection,
|
zoomToSelection,
|
||||||
} from "./cameraUtils"
|
} from "./cameraUtils"
|
||||||
import { saveToPdf } from "../utils/pdfUtils"
|
import { saveToPdf } from "../utils/pdfUtils"
|
||||||
|
import { searchText } from "../utils/searchUtils"
|
||||||
|
|
||||||
export const overrides: TLUiOverrides = {
|
export const overrides: TLUiOverrides = {
|
||||||
tools(editor, tools) {
|
tools(editor, tools) {
|
||||||
|
|
@ -38,6 +39,22 @@ export const overrides: TLUiOverrides = {
|
||||||
// Otherwise, use default select tool behavior
|
// Otherwise, use default select tool behavior
|
||||||
;(tools.select as any).onPointerDown?.(info)
|
;(tools.select as any).onPointerDown?.(info)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
//TODO: Fix double click to zoom on selector tool later...
|
||||||
|
onDoubleClick: (info: any) => {
|
||||||
|
// Prevent default double-click behavior (which would start text editing)
|
||||||
|
info.preventDefault?.()
|
||||||
|
|
||||||
|
// Handle all pointer types (mouse, touch, pen)
|
||||||
|
const point = info.point || (info.touches && info.touches[0]) || info
|
||||||
|
|
||||||
|
// Zoom in at the clicked/touched point
|
||||||
|
editor.zoomIn(point, { animation: { duration: 200 } })
|
||||||
|
|
||||||
|
// Stop event propagation and prevent default handling
|
||||||
|
info.stopPropagation?.()
|
||||||
|
return false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
VideoChat: {
|
VideoChat: {
|
||||||
id: "VideoChat",
|
id: "VideoChat",
|
||||||
|
|
@ -81,6 +98,12 @@ export const overrides: TLUiOverrides = {
|
||||||
onSelect: () => editor.setCurrentTool("MycrozineTemplate"),
|
onSelect: () => editor.setCurrentTool("MycrozineTemplate"),
|
||||||
},
|
},
|
||||||
*/
|
*/
|
||||||
|
hand: {
|
||||||
|
...tools.hand,
|
||||||
|
onDoubleClick: (info: any) => {
|
||||||
|
editor.zoomIn(info.point, { animation: { duration: 200 } })
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions(editor, actions) {
|
actions(editor, actions) {
|
||||||
|
|
@ -317,6 +340,13 @@ export const overrides: TLUiOverrides = {
|
||||||
editor.stopFollowingUser()
|
editor.stopFollowingUser()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
searchShapes: {
|
||||||
|
id: "search-shapes",
|
||||||
|
label: "Search Shapes",
|
||||||
|
kbd: "s",
|
||||||
|
readonlyOk: true,
|
||||||
|
onSelect: () => searchText(editor),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
import { Editor } from "tldraw"
|
||||||
|
|
||||||
|
export const searchText = (editor: Editor) => {
|
||||||
|
// Switch to select tool first
|
||||||
|
editor.setCurrentTool('select')
|
||||||
|
|
||||||
|
const searchTerm = prompt("Enter search text:")
|
||||||
|
if (!searchTerm) return
|
||||||
|
|
||||||
|
const shapes = editor.getCurrentPageShapes()
|
||||||
|
const matchingShapes = shapes.filter(shape => {
|
||||||
|
if (!shape.props) return false
|
||||||
|
|
||||||
|
const textProperties = [
|
||||||
|
(shape.props as any).text,
|
||||||
|
(shape.props as any).name,
|
||||||
|
(shape.props as any).value,
|
||||||
|
(shape.props as any).url,
|
||||||
|
(shape.props as any).description,
|
||||||
|
(shape.props as any).content,
|
||||||
|
]
|
||||||
|
|
||||||
|
const termLower = searchTerm.toLowerCase()
|
||||||
|
return textProperties.some(prop =>
|
||||||
|
typeof prop === 'string' &&
|
||||||
|
prop.toLowerCase().includes(termLower)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (matchingShapes.length > 0) {
|
||||||
|
editor.selectNone()
|
||||||
|
editor.setSelectedShapes(matchingShapes)
|
||||||
|
|
||||||
|
const commonBounds = editor.getSelectionPageBounds()
|
||||||
|
if (!commonBounds) return
|
||||||
|
|
||||||
|
// Calculate viewport dimensions
|
||||||
|
const viewportPageBounds = editor.getViewportPageBounds()
|
||||||
|
|
||||||
|
// Calculate the ratio of selection size to viewport size
|
||||||
|
const widthRatio = commonBounds.width / viewportPageBounds.width
|
||||||
|
const heightRatio = commonBounds.height / viewportPageBounds.height
|
||||||
|
|
||||||
|
// Calculate target zoom based on selection size
|
||||||
|
let targetZoom
|
||||||
|
if (widthRatio < 0.1 || heightRatio < 0.1) {
|
||||||
|
targetZoom = Math.min(
|
||||||
|
(viewportPageBounds.width * 0.8) / commonBounds.width,
|
||||||
|
(viewportPageBounds.height * 0.8) / commonBounds.height,
|
||||||
|
40
|
||||||
|
)
|
||||||
|
} else if (widthRatio > 1 || heightRatio > 1) {
|
||||||
|
targetZoom = Math.min(
|
||||||
|
(viewportPageBounds.width * 0.7) / commonBounds.width,
|
||||||
|
(viewportPageBounds.height * 0.7) / commonBounds.height,
|
||||||
|
0.125
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
targetZoom = Math.min(
|
||||||
|
(viewportPageBounds.width * 0.8) / commonBounds.width,
|
||||||
|
(viewportPageBounds.height * 0.8) / commonBounds.height,
|
||||||
|
20
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zoom to the common bounds
|
||||||
|
editor.zoomToBounds(commonBounds, {
|
||||||
|
targetZoom,
|
||||||
|
inset: widthRatio > 1 || heightRatio > 1 ? 20 : 50,
|
||||||
|
animation: {
|
||||||
|
duration: 400,
|
||||||
|
easing: (t) => t * (2 - t),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// Update URL with new camera position and first selected shape ID
|
||||||
|
const newCamera = editor.getCamera()
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.searchParams.set("shapeId", matchingShapes[0].id)
|
||||||
|
url.searchParams.set("x", newCamera.x.toString())
|
||||||
|
url.searchParams.set("y", newCamera.y.toString())
|
||||||
|
url.searchParams.set("zoom", newCamera.z.toString())
|
||||||
|
window.history.replaceState(null, "", url.toString())
|
||||||
|
} else {
|
||||||
|
alert("No matches found")
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue