mirror of https://github.com/logseq/logseq
parent
6869bdefbc
commit
1893dde335
|
@ -36,6 +36,7 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
|
||||||
className="tl-menu-item"
|
className="tl-menu-item"
|
||||||
onSelect={preventEvent}
|
onSelect={preventEvent}
|
||||||
onClick={app.api.zoomToSelection}
|
onClick={app.api.zoomToSelection}
|
||||||
|
disabled={app.selectedShapesArray.length === 0}
|
||||||
>
|
>
|
||||||
Zoom to selection
|
Zoom to selection
|
||||||
<div className="tl-menu-right-slot">
|
<div className="tl-menu-right-slot">
|
||||||
|
|
|
@ -4,6 +4,9 @@ import type { Shape } from '../lib'
|
||||||
|
|
||||||
export function useQuickAdd() {
|
export function useQuickAdd() {
|
||||||
return React.useCallback<TLReactCallbacks<Shape>['onCanvasDBClick']>(async app => {
|
return React.useCallback<TLReactCallbacks<Shape>['onCanvasDBClick']>(async app => {
|
||||||
|
// Give a timeout so that the quick add input will not be blurred too soon
|
||||||
|
setTimeout(() => {
|
||||||
app.selectTool('logseq-portal').selectedTool.transition('creating')
|
app.selectTool('logseq-portal').selectedTool.transition('creating')
|
||||||
|
}, 100)
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
|
@ -390,6 +390,12 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
|
||||||
}
|
}
|
||||||
}, [isEditing, this.props.collapsed])
|
}, [isEditing, this.props.collapsed])
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isCreating) {
|
||||||
|
app.viewport.zoomToBounds({ ...this.bounds, minY: this.bounds.maxY + 25 })
|
||||||
|
}
|
||||||
|
}, [app.viewport.currentView.height])
|
||||||
|
|
||||||
const onPageNameChanged = React.useCallback((id: string) => {
|
const onPageNameChanged = React.useCallback((id: string) => {
|
||||||
this.initialHeightCalculated = false
|
this.initialHeightCalculated = false
|
||||||
const blockType = validUUID(id) ? 'B' : 'P'
|
const blockType = validUUID(id) ? 'B' : 'P'
|
||||||
|
|
|
@ -32,9 +32,6 @@ export class CreatingState extends TLToolState<
|
||||||
this.app.currentPage.addShapes(shape)
|
this.app.currentPage.addShapes(shape)
|
||||||
this.app.setEditingShape(shape)
|
this.app.setEditingShape(shape)
|
||||||
this.app.setSelectedShapes([shape])
|
this.app.setSelectedShapes([shape])
|
||||||
if (this.app.viewport.camera.zoom < 0.8 || this.app.viewport.camera.zoom > 1.2) {
|
|
||||||
this.app.api.resetZoomToCursor()
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,10 @@ html[data-theme='light'] {
|
||||||
.tl-menu-icon {
|
.tl-menu-icon {
|
||||||
@apply absolute left-4 text-base opacity-50;
|
@apply absolute left-4 text-base opacity-50;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&[data-disabled] {
|
||||||
|
@apply opacity-50 pointer-events-none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#tl-zoom {
|
#tl-zoom {
|
||||||
|
|
|
@ -153,7 +153,7 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
|
||||||
|
|
||||||
resetZoomToCursor = (): this => {
|
resetZoomToCursor = (): this => {
|
||||||
const viewport = this.app.viewport
|
const viewport = this.app.viewport
|
||||||
viewport.update({
|
viewport.animateCamera({
|
||||||
zoom: 1,
|
zoom: 1,
|
||||||
point: Vec.sub(this.app.inputs.originScreenPoint, this.app.inputs.originPoint),
|
point: Vec.sub(this.app.inputs.originScreenPoint, this.app.inputs.originPoint),
|
||||||
})
|
})
|
||||||
|
|
|
@ -3,6 +3,14 @@ import { action, computed, makeObservable, observable } from 'mobx'
|
||||||
import { FIT_TO_SCREEN_PADDING, ZOOM_UPDATE_FACTOR } from '../constants'
|
import { FIT_TO_SCREEN_PADDING, ZOOM_UPDATE_FACTOR } from '../constants'
|
||||||
import type { TLBounds } from '../types'
|
import type { TLBounds } from '../types'
|
||||||
|
|
||||||
|
const ease = (x: number) => {
|
||||||
|
return -(Math.cos(Math.PI * x) - 1) / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const elapsedProgress = (t: number, duration = 100) => {
|
||||||
|
return ease(Vec.clamp(t / duration, 0, 1))
|
||||||
|
}
|
||||||
|
|
||||||
export class TLViewport {
|
export class TLViewport {
|
||||||
constructor() {
|
constructor() {
|
||||||
makeObservable(this)
|
makeObservable(this)
|
||||||
|
@ -73,54 +81,99 @@ export class TLViewport {
|
||||||
return Vec.mul(Vec.add(point, camera.point), camera.zoom)
|
return Vec.mul(Vec.add(point, camera.point), camera.zoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
onZoom = (point: number[], zoom: number): this => {
|
onZoom = (point: number[], zoom: number, animate = false): this => {
|
||||||
return this.pinchZoom(point, [0, 0], zoom)
|
return this.pinchZoom(point, [0, 0], zoom, animate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pinch to a new zoom level, possibly together with a pan.
|
* Pinch to a new zoom level, possibly together with a pan.
|
||||||
*
|
*
|
||||||
* @param point The current point under the cursor.
|
* @param point The current point under the cursor in the screen space. Zoom will be transformed
|
||||||
* @param delta The movement delta.
|
* around this point.
|
||||||
|
* @param delta The movement delta in the screen space
|
||||||
* @param zoom The new zoom level
|
* @param zoom The new zoom level
|
||||||
*/
|
*/
|
||||||
pinchZoom = (point: number[], delta: number[], zoom: number): this => {
|
pinchZoom = (point: number[], delta: number[], zoom: number, animate = false): this => {
|
||||||
const { camera } = this
|
const { camera } = this
|
||||||
|
|
||||||
const nextPoint = Vec.sub(camera.point, Vec.div(delta, camera.zoom))
|
const nextPoint = Vec.sub(camera.point, Vec.div(delta, camera.zoom))
|
||||||
zoom = Vec.clamp(zoom, TLViewport.minZoom, TLViewport.maxZoom)
|
zoom = Vec.clamp(zoom, TLViewport.minZoom, TLViewport.maxZoom)
|
||||||
const p0 = Vec.sub(Vec.div(point, camera.zoom), nextPoint)
|
const p0 = Vec.div(point, camera.zoom)
|
||||||
const p1 = Vec.sub(Vec.div(point, zoom), nextPoint)
|
const p1 = Vec.div(point, zoom)
|
||||||
return this.update({ point: Vec.toFixed(Vec.add(nextPoint, Vec.sub(p1, p0))), zoom })
|
|
||||||
|
const newPoint = Vec.toFixed(Vec.add(nextPoint, Vec.sub(p1, p0)))
|
||||||
|
|
||||||
|
if (animate) {
|
||||||
|
this.animateCamera({ point: newPoint, zoom })
|
||||||
|
} else {
|
||||||
|
this.update({ point: newPoint, zoom })
|
||||||
}
|
}
|
||||||
|
|
||||||
setZoom = (zoom: number) => {
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
setZoom = (zoom: number, animate = false) => {
|
||||||
const { bounds } = this
|
const { bounds } = this
|
||||||
const center = [bounds.width / 2, bounds.height / 2]
|
const center = [bounds.width / 2, bounds.height / 2]
|
||||||
this.onZoom(center, zoom)
|
this.onZoom(center, zoom, animate)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomIn = () => {
|
zoomIn = () => {
|
||||||
const { camera } = this
|
const { camera } = this
|
||||||
this.setZoom(camera.zoom / ZOOM_UPDATE_FACTOR)
|
this.setZoom(camera.zoom / ZOOM_UPDATE_FACTOR, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomOut = () => {
|
zoomOut = () => {
|
||||||
const { camera, bounds } = this
|
const { camera } = this
|
||||||
this.setZoom(camera.zoom * ZOOM_UPDATE_FACTOR)
|
this.setZoom(camera.zoom * ZOOM_UPDATE_FACTOR, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
resetZoom = (): this => {
|
resetZoom = (): this => {
|
||||||
const {
|
this.setZoom(1, true)
|
||||||
bounds,
|
return this
|
||||||
camera: { zoom, point },
|
|
||||||
} = this
|
|
||||||
const center = [bounds.width / 2, bounds.height / 2]
|
|
||||||
const p0 = Vec.sub(Vec.div(center, zoom), point)
|
|
||||||
const p1 = Vec.sub(Vec.div(center, 1), point)
|
|
||||||
return this.update({ point: Vec.toFixed(Vec.add(point, Vec.sub(p1, p0))), zoom: 1 })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
zoomToBounds = ({ width, height, minX, minY }: TLBounds): this => {
|
/**
|
||||||
|
* Animate the camera to the given position
|
||||||
|
*/
|
||||||
|
animateCamera = ({ point, zoom }: { point: number[]; zoom: number }) => {
|
||||||
|
return this.animateToViewport({
|
||||||
|
minX: -point[0],
|
||||||
|
minY: -point[1],
|
||||||
|
maxX: this.bounds.width / zoom - point[0],
|
||||||
|
maxY: this.bounds.height / zoom - point[1],
|
||||||
|
width: this.bounds.width / zoom,
|
||||||
|
height: this.bounds.height / zoom,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
animateToViewport = (view: TLBounds) => {
|
||||||
|
const startTime = performance.now()
|
||||||
|
const oldView = { ...this.currentView }
|
||||||
|
|
||||||
|
const step = () => {
|
||||||
|
const elapsed = performance.now() - startTime
|
||||||
|
const progress = elapsedProgress(elapsed) // 0 ~ 1
|
||||||
|
const next = {
|
||||||
|
minX: oldView.minX + (view.minX - oldView.minX) * progress,
|
||||||
|
minY: oldView.minY + (view.minY - oldView.minY) * progress,
|
||||||
|
maxX: oldView.maxX + (view.maxX - oldView.maxX) * progress,
|
||||||
|
maxY: oldView.maxY + (view.maxY - oldView.maxY) * progress,
|
||||||
|
}
|
||||||
|
|
||||||
|
const point = [-next.minX, -next.minY]
|
||||||
|
const zoom = this.bounds.width / (next.maxX - next.minX)
|
||||||
|
|
||||||
|
this.update({ point, zoom })
|
||||||
|
if (progress < 1) {
|
||||||
|
requestAnimationFrame(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
step()
|
||||||
|
}
|
||||||
|
|
||||||
|
zoomToBounds = ({ width, height, minX, minY }: TLBounds) => {
|
||||||
const { bounds, camera } = this
|
const { bounds, camera } = this
|
||||||
let zoom = Math.min(
|
let zoom = Math.min(
|
||||||
(bounds.width - FIT_TO_SCREEN_PADDING) / width,
|
(bounds.width - FIT_TO_SCREEN_PADDING) / width,
|
||||||
|
@ -137,6 +190,8 @@ export class TLViewport {
|
||||||
(bounds.width - width * zoom) / 2 / zoom,
|
(bounds.width - width * zoom) / 2 / zoom,
|
||||||
(bounds.height - height * zoom) / 2 / zoom,
|
(bounds.height - height * zoom) / 2 / zoom,
|
||||||
]
|
]
|
||||||
return this.update({ point: Vec.add([-minX, -minY], delta), zoom })
|
|
||||||
|
const point = Vec.add([-minX, -minY], delta)
|
||||||
|
this.animateCamera({ point, zoom })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue