feat: snap to grip

pull/9391/head^2
Konstantinos Kaloutas 2023-06-14 16:43:02 +03:00 committed by Gabriel Horner
parent 17d9e645c1
commit 4f00e326ff
10 changed files with 73 additions and 9 deletions

View File

@ -354,6 +354,8 @@
:whiteboard/search-only-pages "Search only pages"
:whiteboard/cache-outdated "Cache is outdated. Please click the 'Re-index' button in the graph's dropdown menu."
:whiteboard/shape-quick-links "Shape Quick Links"
:whiteboard/toggle-grid "Toggle grid"
:whiteboard/snap-to-grid "Snap to grid"
:page-search "Search in the current page"
:graph-search "Search graph"
:home "Home"

View File

@ -6,6 +6,7 @@ import * as React from 'react'
import type { Shape } from '../../lib'
import { TablerIcon } from '../icons'
import { Button } from '../Button'
import { ToggleInput } from '../inputs/ToggleInput'
import { ZoomMenu } from '../ZoomMenu'
import * as Separator from '@radix-ui/react-separator'
import { LogseqContext } from '../../lib/logseq-context'
@ -32,6 +33,14 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
app.api.zoomOut()
}, [app])
const toggleGrid = React.useCallback(() => {
app.api.toggleGrid()
}, [app])
const toggleSnapToGrid = React.useCallback(() => {
app.api.toggleSnapToGrid()
}, [app])
return (
<div className="tl-action-bar" data-html2canvas-ignore="true">
{!app.readOnly && (
@ -55,6 +64,28 @@ export const ActionBar = observer(function ActionBar(): JSX.Element {
<Separator.Root className="tl-toolbar-separator" orientation="vertical" />
<ZoomMenu />
</div>
<div className={'tl-toolbar tl-grid-bar ml-4'}>
<ToggleInput
tooltip={t('whiteboard/toggle-grid')}
className="tl-button"
pressed={app.settings.showGrid}
id="tl-show-grid"
onPressedChange={toggleGrid}
>
<TablerIcon name="grid-dots" />
</ToggleInput>
<ToggleInput
tooltip={t('whiteboard/snap-to-grid')}
className="tl-button"
pressed={app.settings.snapToGrid}
id="tl-snap-to-grid"
onPressedChange={toggleSnapToGrid}
>
<TablerIcon name={app.settings.snapToGrid ? "magnet" : "magnet-off"} />
</ToggleInput>
</div>
</div>
)
})

View File

@ -1,4 +1,4 @@
import { TLApp, TLTargetType, TLToolState, uniqueId } from '@tldraw/core'
import { TLApp, TLTargetType, TLToolState, uniqueId, GRID_SIZE } from '@tldraw/core'
import type { TLReactEventMap, TLReactEvents } from '@tldraw/react'
import Vec from '@tldraw/vec'
import { transaction } from 'mobx'
@ -20,10 +20,16 @@ export class CreatingState extends TLToolState<
onEnter = () => {
this.app.history.pause()
transaction(() => {
let point = Vec.sub(this.app.inputs.originPoint, this.offset)
if (this.app.settings.snapToGrid) {
point = Vec.snap(point, GRID_SIZE)
}
const shape = new LogseqPortalShape({
id: uniqueId(),
parentId: this.app.currentPage.id,
point: Vec.sub(this.app.inputs.originPoint, this.offset),
point: point,
size: LogseqPortalShape.defaultProps.size,
fill: this.app.settings.color,
stroke: this.app.settings.color,

View File

@ -172,6 +172,12 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
return this
}
toggleSnapToGrid = (): this => {
const { settings } = this.app
settings.update({ snapToGrid: !settings.snapToGrid })
return this
}
setColor = (color: string): this => {
const { settings } = this.app

View File

@ -10,7 +10,6 @@ import type {
TLCallback,
TLEventMap,
TLEvents,
TLShortcut,
TLStateEvents,
TLSubscription,
TLSubscriptionEventInfo,

View File

@ -4,6 +4,7 @@ import { observable, makeObservable, action } from 'mobx'
export interface TLSettingsProps {
mode: 'light' | 'dark'
showGrid: boolean
snapToGrid: boolean
color: string
scaleLevel: string
}
@ -15,6 +16,7 @@ export class TLSettings implements TLSettingsProps {
@observable mode: 'dark' | 'light' = 'light'
@observable showGrid = true
@observable snapToGrid = true
@observable scaleLevel = 'md'
@observable color = ''

View File

@ -51,7 +51,7 @@ export class TLViewport {
}
panToPointWhenNearBounds = (point: number[]) => {
const threshold = [TLViewport.panThreshold, TLViewport.panThreshold]
const threshold = Vec.div([TLViewport.panThreshold, TLViewport.panThreshold], this.camera.zoom)
const deltaMax = Vec.sub([this.currentView.maxX, this.currentView.maxY], Vec.add(point, threshold))
const deltaMin = Vec.sub([this.currentView.minX, this.currentView.minY], Vec.sub(point, threshold))

View File

@ -1,5 +1,6 @@
import type { TLBoxTool } from '../TLBoxTool'
import Vec from '@tldraw/vec'
import { GRID_SIZE } from '@tldraw/core'
import type { TLBounds } from '@tldraw/intersect'
import { type TLEventMap, TLCursor, type TLStateEvents, TLResizeCorner } from '../../../../types'
import { uniqueId, BoundsUtils } from '../../../../utils'
@ -68,7 +69,7 @@ export class CreatingState<
if (!this.creatingShape) throw Error('Expected a creating shape.')
const { initialBounds } = this
const { currentPoint, originPoint, shiftKey } = this.app.inputs
const bounds = BoundsUtils.getTransformedBoundingBox(
let bounds = BoundsUtils.getTransformedBoundingBox(
initialBounds,
TLResizeCorner.BottomRight,
Vec.sub(currentPoint, originPoint),
@ -78,6 +79,10 @@ export class CreatingState<
!this.creatingShape.canChangeAspectRatio
)
if (this.app.settings.snapToGrid) {
bounds = BoundsUtils.snapBoundsToGrid(bounds, GRID_SIZE)
}
this.creatingShape.update({
point: [bounds.minX, bounds.minY],
size: [bounds.width, bounds.height],

View File

@ -1,4 +1,5 @@
import type { TLBounds } from '@tldraw/intersect'
import { GRID_SIZE } from '@tldraw/core'
import { Vec } from '@tldraw/vec'
import {
type TLEventMap,
@ -216,6 +217,11 @@ export class ResizingState<
// // Position the bounds at the center
// relativeBounds = BoundsUtils.centerBounds(relativeBounds, center)
// }
if (this.app.settings.snapToGrid) {
relativeBounds = BoundsUtils.snapBoundsToGrid(relativeBounds, GRID_SIZE)
}
shape.onResize(initialShapeProps, {
center,
rotation,

View File

@ -1,11 +1,12 @@
import { Vec } from '@tldraw/vec'
import { transaction } from 'mobx'
import { type TLEventMap, TLCursor, type TLEvents } from '../../../../types'
import { dedupe, uniqueId } from '../../../../utils'
import { uniqueId } from '../../../../utils'
import type { TLShape } from '../../../shapes'
import type { TLApp } from '../../../TLApp'
import { TLToolState } from '../../../TLToolState'
import type { TLSelectTool } from '../TLSelectTool'
import { GRID_SIZE } from '../../../../constants'
export class TranslatingState<
S extends TLShape,
@ -46,9 +47,15 @@ export class TranslatingState<
}
transaction(() => {
this.app.allSelectedShapesArray.forEach(shape => {
if (!shape.props.isLocked) shape.update({ point: Vec.add(initialPoints[shape.id], delta) })
})
this.app.allSelectedShapesArray
.filter(s => !s.props.isLocked)
.forEach(shape => {
let position = Vec.add(initialPoints[shape.id], delta)
if (this.app.settings.snapToGrid) {
position = Vec.snap(position, GRID_SIZE)
}
shape.update({ point: position })
})
})
}