chore: handle tool shortcuts

pull/9269/head
Konstantinos Kaloutas 2023-04-26 12:08:33 +03:00 committed by Gabriel Horner
parent dd9eb3a4e2
commit 00be5edb4c
19 changed files with 81 additions and 77 deletions

View File

@ -78,7 +78,8 @@
(rum/defc keyboard-shortcut
[props]
[(ui/render-keyboard-shortcut (shortcut-helper/gen-shortcut-seq (keyword (gobj/get props "action"))))])
(let [shortcut (shortcut-helper/gen-shortcut-seq (keyword (gobj/get props "action")))]
(ui/render-keyboard-shortcut shortcut)))
(def tldraw-renderers {:Page page-cp
:Block block-cp

View File

@ -71,6 +71,36 @@
:pdf/find {:binding "alt+f"
:fn pdf-utils/open-finder}
:whiteboard/select {:binding ["1" "s"]
:fn #(.selectTool (state/active-tldraw-app) "select")}
:whiteboard/pan {:binding ["2" "p"]
:fn #(.selectTool (state/active-tldraw-app) "move")}
:whiteboard/portal {:binding "3"
:fn #(.selectTool (state/active-tldraw-app) "logseq-portal")}
:whiteboard/pencil {:binding ["4" "d"]
:fn #(.selectTool (state/active-tldraw-app) "pencil")}
:whiteboard/highlighter {:binding ["5" "h"]
:fn #(.selectTool (state/active-tldraw-app) "highlighter")}
:whiteboard/eraser {:binding ["6" "e"]
:fn #(.selectTool (state/active-tldraw-app) "erase")}
:whiteboard/connector {:binding ["7" "c"]
:fn #(.selectTool (state/active-tldraw-app) "line")}
:whiteboard/text {:binding ["8" "t"]
:fn #(.selectTool (state/active-tldraw-app) "text")}
:whiteboard/rectangle {:binding ["9" "r"]
:fn #(.selectTool (state/active-tldraw-app) "box")}
:whiteboard/ellipse {:binding "o"
:fn #(.selectTool (state/active-tldraw-app) "ellipse")}
:whiteboard/reset-zoom {:binding "shift+0"
:fn #(.resetZoom (.-api ^js (state/active-tldraw-app)))}
@ -550,7 +580,17 @@
(with-meta {:before m/enable-when-not-editing-mode!}))
:shortcut.handler/whiteboard
(-> (build-category-map [:whiteboard/reset-zoom
(-> (build-category-map [:whiteboard/select
:whiteboard/pan
:whiteboard/portal
:whiteboard/pencil
:whiteboard/highlighter
:whiteboard/eraser
:whiteboard/connector
:whiteboard/text
:whiteboard/rectangle
:whiteboard/ellipse
:whiteboard/reset-zoom
:whiteboard/zoom-to-fit
:whiteboard/zoom-to-selection
:whiteboard/zoom-out
@ -818,7 +858,17 @@
:ui/toggle-contents]
:shortcut.category/whiteboard
[:whiteboard/reset-zoom
[:whiteboard/select
:whiteboard/pan
:whiteboard/portal
:whiteboard/pencil
:whiteboard/highlighter
:whiteboard/eraser
:whiteboard/connector
:whiteboard/text
:whiteboard/rectangle
:whiteboard/ellipse
:whiteboard/reset-zoom
:whiteboard/zoom-to-fit
:whiteboard/zoom-to-selection
:whiteboard/zoom-out

View File

@ -79,6 +79,16 @@
:editor/zoom-in "Zoom in editing block / Forwards otherwise"
:editor/zoom-out "Zoom out editing block / Backwards otherwise"
:editor/toggle-undo-redo-mode "Toggle undo redo mode (global or page only)"
:whiteboard/select "Select tool"
:whiteboard/pan "Pan tool"
:whiteboard/portal "Portal tool"
:whiteboard/pencil "Pencil tool"
:whiteboard/highlighter "Highlighter tool"
:whiteboard/eraser "Eraser tool"
:whiteboard/connector "Connector tool"
:whiteboard/text "Text tool"
:whiteboard/rectangle "Rectangle tool"
:whiteboard/ellipse "Ellipse tool"
:whiteboard/reset-zoom "Reset zoom"
:whiteboard/zoom-to-fit "Zoom to drawing"
:whiteboard/zoom-to-selection "Zoom to fit selection"

View File

@ -40,12 +40,12 @@ export const PrimaryTools = observer(function PrimaryTools() {
<ToolButton handleClick={() =>app.selectTool("select")} tooltip="Select" id="select" icon="select-cursor" />
<ToolButton
handleClick={() =>app.selectTool("move")}
tooltip="Move"
tooltip="Pan"
id="move"
icon={app.isIn('move.panning') ? 'hand-grab' : 'hand-stop'}
/>
<Separator.Root className="tl-toolbar-separator" orientation="horizontal" />
<ToolButton handleClick={() =>app.selectTool("logseq-portal")} tooltip="Add block or page" id="logseq-portal" icon="circle-plus" />
<ToolButton handleClick={() =>app.selectTool("`logseq-portal")} tooltip="Add block or page" id="logseq-portal" icon="circle-plus" />
<ToolButton handleClick={() =>app.selectTool("pencil")} tooltip="Draw" id="pencil" icon="ballpen" />
<ToolButton handleClick={() =>app.selectTool("highlighter")} tooltip="Highlight" id="highlighter" icon="highlight" />
<ToolButton handleClick={() =>app.selectTool("erase")} tooltip="Eraser" id="erase" icon="eraser" />

View File

@ -5,6 +5,7 @@ import { observer } from 'mobx-react-lite'
import type * as React from 'react'
import { Button } from '../Button'
import { TablerIcon } from '../icons'
import { KeyboardShortcut } from '../KeyboardShortcut'
export interface ToolButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
id: string
@ -23,14 +24,10 @@ export const ToolButton = observer(({ id, icon, tooltip, tooltipSide = "left", h
const shortcuts = (Tool as any)?.['shortcut']
const tooltipContent = shortcuts && tooltip ? (
<>
<div className="flex">
{tooltip}
<span className="ml-2 keyboard-shortcut">
{shortcuts
.map((shortcut: string, idx: number) => <code key={idx}>{shortcut.toUpperCase()}</code>)
.reduce((prev: React.ReactNode, curr: React.ReactNode) => [prev, ' | ', curr])}
</span>
</>
<KeyboardShortcut action={shortcuts}/>
</div>
) : (
tooltip
)

View File

@ -4,6 +4,6 @@ import { BoxShape, type Shape } from '../shapes'
export class BoxTool extends TLBoxTool<BoxShape, Shape, TLReactEventMap> {
static id = 'box'
static shortcut = ['9', 'r']
static shortcut = 'whiteboard/rectangle'
Shape = BoxShape
}

View File

@ -4,6 +4,6 @@ import { EllipseShape, type Shape } from '../shapes'
export class EllipseTool extends TLBoxTool<EllipseShape, Shape, TLReactEventMap> {
static id = 'ellipse'
static shortcut = ['o']
static shortcut = 'whiteboard/ellipse'
Shape = EllipseShape
}

View File

@ -4,5 +4,5 @@ import type { Shape } from '../shapes'
export class NuEraseTool extends TLEraseTool<Shape, TLReactEventMap> {
static id = 'erase'
static shortcut = ['6', 'e']
static shortcut = 'whiteboard/eraser'
}

View File

@ -4,7 +4,7 @@ import { HighlighterShape, type Shape } from '../shapes'
export class HighlighterTool extends TLDrawTool<HighlighterShape, Shape, TLReactEventMap> {
static id = 'highlighter'
static shortcut = ['5', 'h']
static shortcut = 'whiteboard/highlighter'
Shape = HighlighterShape
simplify = true
simplifyTolerance = 0.618

View File

@ -5,6 +5,6 @@ import { LineShape, type Shape } from '../shapes'
// @ts-expect-error maybe later
export class LineTool extends TLLineTool<LineShape, Shape, TLReactEventMap> {
static id = 'line'
static shortcut = ['7', 'c']
static shortcut = 'whiteboard/connector'
Shape = LineShape
}

View File

@ -9,7 +9,7 @@ export class LogseqPortalTool extends TLTool<
TLApp<Shape, TLReactEventMap>
> {
static id = 'logseq-portal'
static shortcut = ['3']
static shortcut = 'whiteboard/portal'
static states = [IdleState, CreatingState]
static initial = 'idle'

View File

@ -4,7 +4,7 @@ import { PencilShape, type Shape } from '../shapes'
export class PencilTool extends TLDrawTool<PencilShape, Shape, TLReactEventMap> {
static id = 'pencil'
static shortcut = ['4', 'd']
static shortcut = 'whiteboard/pencil'
Shape = PencilShape
simplify = false
}

View File

@ -4,6 +4,6 @@ import { TextShape, type Shape } from '../shapes'
export class TextTool extends TLTextTool<TextShape, Shape, TLReactEventMap> {
static id = 'text'
static shortcut = ['8', 't']
static shortcut = 'whiteboard/text'
Shape = TextShape
}

View File

@ -147,7 +147,7 @@ html[data-theme='light'] {
.tl-menu-right-slot {
margin-left: auto;
padding-left: 40px;
padding-left: 20px;
.keyboard-shortcut > code {
padding: 4px !important;

View File

@ -9,7 +9,6 @@ import type {
TLShortcut,
TLEvents,
} from '../types'
import { KeyUtils } from '../utils'
import type { TLShape } from './shapes'
export interface TLStateClass<
@ -120,7 +119,6 @@ export abstract class TLRootState<S extends TLShape, K extends TLEventMap>
if (this.currentState) {
prevState._events.onExit({ ...data, toId: id })
prevState.dispose()
nextState.registerKeyboardShortcuts()
this.setCurrentState(nextState)
this._events.onTransition({ ...data, fromId: prevState.id, toId: id })
nextState._events.onEnter({ ...data, fromId: prevState.id })
@ -404,34 +402,15 @@ export abstract class TLState<
}
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const shortcuts = this.constructor['shortcuts'] as TLShortcut<S, K>[]
this._shortcuts = shortcuts
makeObservable(this)
}
static cursor?: TLCursor
registerKeyboardShortcuts = (): void => {
if (!this._shortcuts?.length) return
this._disposables.push(
...this._shortcuts.map(({ keys, fn }) => {
return KeyUtils.registerShortcut(keys, event => {
if (!this.isActive) return
fn(this.root, this, event)
})
})
)
}
/* --------------------- States --------------------- */
protected _root: R
protected _parent: P
private _shortcuts: TLShortcut<S, K>[] = []
get root() {
return this._root

View File

@ -10,7 +10,7 @@ export class TLMoveTool<
R extends TLApp<S, K> = TLApp<S, K>
> extends TLTool<S, K, R> {
static id = 'move'
static shortcut = ['2', 'm']
static shortcut = 'whiteboard/pan'
static states = [IdleState, IdleHoldState, PanningState, PinchingState]

View File

@ -33,7 +33,7 @@ export class TLSelectTool<
static initial = 'idle'
static shortcut = ['1', 's']
static shortcut = 'whiteboard/select'
static states = [
IdleState,

View File

@ -1,32 +0,0 @@
import Mousetrap from 'mousetrap'
type AvailableTags = 'INPUT' | 'TEXTAREA' | 'SELECT'
const tagFilter = ({ target }: KeyboardEvent, enableOnTags?: AvailableTags[]) => {
const targetTagName = target && (target as HTMLElement).tagName
return Boolean(
targetTagName && enableOnTags && enableOnTags.includes(targetTagName as AvailableTags)
)
}
export class KeyUtils {
static registerShortcut(
keys: string | string[],
callback: (keyboardEvent: Mousetrap.ExtendedKeyboardEvent, combo: string) => void
) {
const fn = (keyboardEvent: Mousetrap.ExtendedKeyboardEvent, combo: string): void => {
keyboardEvent.preventDefault()
if (
tagFilter(keyboardEvent, ['INPUT', 'TEXTAREA', 'SELECT']) ||
(keyboardEvent.target as HTMLElement)?.isContentEditable
) {
return
}
callback(keyboardEvent, combo)
}
Mousetrap.bind(keys, fn, 'keydown')
return () => {
Mousetrap.unbind(keys, 'keydown')
}
}
}

View File

@ -1,7 +1,6 @@
import * as uuid from 'uuid'
export * from './BoundsUtils'
export * from './PointUtils'
export * from './KeyUtils'
export * from './GeomUtils'
export * from './PolygonUtils'
export * from './SvgPathUtils'