Tldraw/packages/tldraw/src/lib/ui/hooks/menu-hooks.ts

216 wiersze
4.9 KiB
TypeScript

import {
Editor,
TLArrowShape,
TLDrawShape,
TLGroupShape,
TLLineShape,
TLTextShape,
useEditor,
useValue,
} from '@tldraw/editor'
function shapesWithUnboundArrows(editor: Editor) {
const selectedShapeIds = editor.getSelectedShapeIds()
const selectedShapes = selectedShapeIds.map((id) => {
return editor.getShape(id)
})
return selectedShapes.filter((shape) => {
if (!shape) return false
if (
editor.isShapeOfType<TLArrowShape>(shape, 'arrow') &&
shape.props.start.type === 'binding'
) {
return false
}
if (editor.isShapeOfType<TLArrowShape>(shape, 'arrow') && shape.props.end.type === 'binding') {
return false
}
return true
})
}
/** @internal */
export const useThreeStackableItems = () => {
const editor = useEditor()
return useValue('threeStackableItems', () => shapesWithUnboundArrows(editor).length > 2, [editor])
}
/** @internal */
export const useAllowGroup = () => {
const editor = useEditor()
return useValue(
'allow group',
() => {
// We can't group arrows that are bound to shapes that aren't selected
// if more than one shape has an arrow bound to it, allow group
const selectedShapes = editor.getSelectedShapes()
if (selectedShapes.length < 2) return false
for (const shape of selectedShapes) {
if (editor.isShapeOfType<TLArrowShape>(shape, 'arrow')) {
const { start, end } = shape.props
if (start.type === 'binding') {
// if the other shape is not among the selected shapes...
if (!selectedShapes.some((s) => s.id === start.boundShapeId)) {
return false
}
}
if (end.type === 'binding') {
// if the other shape is not among the selected shapes...
if (!selectedShapes.some((s) => s.id === end.boundShapeId)) {
return false
}
}
}
}
return true
},
[editor]
)
}
/** @internal */
export const useAllowUngroup = () => {
const editor = useEditor()
return useValue(
'allowUngroup',
() => editor.getSelectedShapeIds().some((id) => editor.getShape(id)?.type === 'group'),
[editor]
)
}
export const showMenuPaste =
typeof window !== 'undefined' &&
'navigator' in window &&
Boolean(navigator.clipboard) &&
Boolean(navigator.clipboard.read)
/**
* Returns true if the number of LOCKED OR UNLOCKED selected shapes is at least min or at most max.
*/
export function useAnySelectedShapesCount(min?: number, max?: number) {
const editor = useEditor()
return useValue(
'selectedShapes',
() => {
const len = editor.getSelectedShapes().length
if (min === undefined) {
if (max === undefined) {
// just length
return len
} else {
// max but no min
return len <= max
}
} else {
if (max === undefined) {
// min but no max
return len >= min
} else {
// max and min
return len >= min && len <= max
}
}
},
[editor, min, max]
)
}
/**
* Returns true if the number of UNLOCKED selected shapes is at least min or at most max.
*/
export function useUnlockedSelectedShapesCount(min?: number, max?: number) {
const editor = useEditor()
return useValue(
'selectedShapes',
() => {
const len = editor
.getSelectedShapes()
.filter((s) => !editor.isShapeOrAncestorLocked(s)).length
if (min === undefined) {
if (max === undefined) {
// just length
return len
} else {
// max but no min
return len <= max
}
} else {
if (max === undefined) {
// min but no max
return len >= min
} else {
// max and min
return len >= min && len <= max
}
}
},
[editor]
)
}
export function useShowAutoSizeToggle() {
const editor = useEditor()
return useValue(
'showAutoSizeToggle',
() => {
const selectedShapes = editor.getSelectedShapes()
return (
selectedShapes.length === 1 &&
editor.isShapeOfType<TLTextShape>(selectedShapes[0], 'text') &&
selectedShapes[0].props.autoSize === false
)
},
[editor]
)
}
export function useHasLinkShapeSelected() {
const editor = useEditor()
return useValue(
'hasLinkShapeSelected',
() => {
const onlySelectedShape = editor.getOnlySelectedShape()
return !!(
onlySelectedShape &&
onlySelectedShape.type !== 'embed' &&
'url' in onlySelectedShape.props &&
!onlySelectedShape.isLocked
)
},
[editor]
)
}
export function useOnlyFlippableShape() {
const editor = useEditor()
return useValue(
'onlyFlippableShape',
() => {
const shape = editor.getOnlySelectedShape()
return (
shape &&
(editor.isShapeOfType<TLGroupShape>(shape, 'group') ||
editor.isShapeOfType<TLArrowShape>(shape, 'arrow') ||
editor.isShapeOfType<TLLineShape>(shape, 'line') ||
editor.isShapeOfType<TLDrawShape>(shape, 'draw'))
)
},
[editor]
)
}
/** @public */
export function useCanRedo() {
const editor = useEditor()
return useValue('useCanRedo', () => editor.getCanRedo(), [editor])
}
/** @public */
export function useCanUndo() {
const editor = useEditor()
return useValue('useCanUndo', () => editor.getCanUndo(), [editor])
}