mirror of https://github.com/logseq/logseq
feat: snap to grip
parent
17d9e645c1
commit
4f00e326ff
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import type {
|
|||
TLCallback,
|
||||
TLEventMap,
|
||||
TLEvents,
|
||||
TLShortcut,
|
||||
TLStateEvents,
|
||||
TLSubscription,
|
||||
TLSubscriptionEventInfo,
|
||||
|
|
|
@ -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 = ''
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 })
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue