From f7fd54203fc76ca8ec1328b39ac659fcfa42012a Mon Sep 17 00:00:00 2001 From: Konstantinos Kaloutas Date: Fri, 26 Aug 2022 17:20:32 +0300 Subject: [PATCH] Add context menu and pan with middle click --- tldraw/apps/tldraw-logseq/src/app.tsx | 4 + .../components/ContextMenu/ContextMenu.tsx | 111 ++++++++++++++++++ .../src/components/ContextMenu/index.ts | 1 + tldraw/apps/tldraw-logseq/src/styles.css | 46 +++++++- tldraw/packages/core/src/lib/TLApp/TLApp.ts | 39 +++++- .../lib/tools/TLSelectTool/TLSelectTool.tsx | 2 + .../TLSelectTool/states/ContextMenuState.ts | 39 ++++++ .../tools/TLSelectTool/states/IdleState.ts | 5 + .../lib/tools/TLSelectTool/states/index.ts | 1 + .../react/src/components/AppCanvas.tsx | 1 + .../src/components/Canvas/Canvas.test.tsx | 1 + .../react/src/components/Canvas/Canvas.tsx | 2 + .../src/components/Renderer/Renderer.test.tsx | 1 + 13 files changed, 247 insertions(+), 6 deletions(-) create mode 100644 tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx create mode 100644 tldraw/apps/tldraw-logseq/src/components/ContextMenu/index.ts create mode 100644 tldraw/packages/core/src/lib/tools/TLSelectTool/states/ContextMenuState.ts diff --git a/tldraw/apps/tldraw-logseq/src/app.tsx b/tldraw/apps/tldraw-logseq/src/app.tsx index 4271736f11..3d7c35f946 100644 --- a/tldraw/apps/tldraw-logseq/src/app.tsx +++ b/tldraw/apps/tldraw-logseq/src/app.tsx @@ -11,6 +11,7 @@ import { import * as React from 'react' import { AppUI } from '~components/AppUI' import { ContextBar } from '~components/ContextBar/ContextBar' +import { ContextMenu } from '~components/ContextMenu/ContextMenu' import { useFileDrop } from '~hooks/useFileDrop' import { usePaste } from '~hooks/usePaste' import { useQuickAdd } from '~hooks/useQuickAdd' @@ -102,11 +103,14 @@ export const App = function App({ model={model} {...rest} > +
+
+ ) diff --git a/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx new file mode 100644 index 0000000000..bfeefcb9f3 --- /dev/null +++ b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx @@ -0,0 +1,111 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { useApp } from '@tldraw/react' +import { observer } from 'mobx-react-lite' +import * as React from 'react' + +import * as ReactContextMenu from '@radix-ui/react-context-menu' + +const preventDefault = (e: Event) => e.stopPropagation() + +interface ContextMenuProps { + children: React.ReactNode +} + +export const ContextMenu = observer(function ContextMenu({ children }: ContextMenuProps) { + const app = useApp() + const rContent = React.useRef(null) + + return ( + + {children} + +
+ + Copy +
⌘+C
+
+ + Paste +
⌘+V
+
+ + Select All +
⌘+A
+
+ {/*TODO: Add paste to this menu*/} + {app.selectedShapes && app.selectedShapes.size > 0 && ( + <> + { + app.api.deleteShapes() + }}> + Delete +
Delete
+
+ + Duplicate +
⌘+D
+
+ { + app.flipHorizontal(app.selectedShapesArray) + }}> + Flip Horizontally + + { + app.flipVertical(app.selectedShapesArray) + }}> + Flip Vertically + + { + app.bringToFront(app.selectedShapesArray) + }} + > + Move to Front +
⇧+]
+
+ { + app.bringForward(app.selectedShapesArray) + }} + > + Move forwards +
]
+
+ { + app.sendToBack(app.selectedShapesArray) + }} + > + Move to back +
⇧+[
+
+ { + app.sendBackward(app.selectedShapesArray) + }} + > + Move backwards +
[
+
+ + )} +
+
+
+ ) +}) diff --git a/tldraw/apps/tldraw-logseq/src/components/ContextMenu/index.ts b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/index.ts new file mode 100644 index 0000000000..47f4873934 --- /dev/null +++ b/tldraw/apps/tldraw-logseq/src/components/ContextMenu/index.ts @@ -0,0 +1 @@ +export * from './ContextMenu' diff --git a/tldraw/apps/tldraw-logseq/src/styles.css b/tldraw/apps/tldraw-logseq/src/styles.css index 4ab5a7d743..c68e72a7d1 100644 --- a/tldraw/apps/tldraw-logseq/src/styles.css +++ b/tldraw/apps/tldraw-logseq/src/styles.css @@ -32,6 +32,51 @@ font-size: inherit; } +.tl-context-menu-button { + min-width: 220px; + all: unset; + font-size: 13px; + line-height: 1; + border-radius: 3px; + display: flex; + align-items: center; + height: 25px; + padding: 0 5px; + position: relative; + user-select: none; + color: var(--color-text); + + &:hover, + &:focus { + background-color: var(--ls-menu-hover-color); + } +} + +.tl-context-menu { + @apply relative flex bottom-0 flex border-0; + + opacity: 100%; + border-radius: 6px; + padding: 5px; + overflow: hidden; + user-select: none; + flex-direction: column; + z-index: 180; + min-width: 180; + pointer-events: 'all'; + background: var(--ls-primary-background-color); + box-shadow: var(--shadow-medium); +} + +.tl-context-menu-right-slot { + margin-left: auto; + padding-left: 20px; +} + +.tl-context-menu-right-slot:focus { + color: whites; +} + .tl-action-bar { @apply absolute bottom-0 flex border-0; @@ -79,7 +124,6 @@ @apply flex items-center px-4 py-1 text-sm !important; min-width: 220px; all: unset; - color: black; height: 25px; user-select: none; color: var(--color-text); diff --git a/tldraw/packages/core/src/lib/TLApp/TLApp.ts b/tldraw/packages/core/src/lib/TLApp/TLApp.ts index 30fb68a47f..172ab50049 100644 --- a/tldraw/packages/core/src/lib/TLApp/TLApp.ts +++ b/tldraw/packages/core/src/lib/TLApp/TLApp.ts @@ -719,6 +719,12 @@ export class TLApp< ) } + @computed get showContextMenu() { + return ( + this.isIn('select.contextMenu') + ) + } + @computed get showRotateHandles() { const { selectedShapesArray } = this return ( @@ -812,6 +818,14 @@ export class TLApp< /* ----------------- Event Handlers ----------------- */ + temporaryTransitionToMove(event: any) { + event.stopPropagation() + event.preventDefault() + const prevTool = this.selectedTool + this.transition('move', { prevTool }) + this.selectedTool.transition('idleHold') + } + readonly onTransition: TLStateEvents['onTransition'] = () => { this.settings.update({ isToolLocked: false }) } @@ -822,6 +836,18 @@ export class TLApp< } readonly onPointerDown: TLEvents['pointer'] = (info, e) => { + // Switch to select on right click to enable contextMenu state + if (e.button === 2) { + this.transition('select', info) + return false + } + + // Pan canvas when holding middle click + if (!this.editingShape && e.button === 1 && !this.isIn('move')) { + this.temporaryTransitionToMove(e) + return + } + if ('clientX' in e) { this.inputs.onPointerDown( [...this.viewport.getPagePoint([e.clientX, e.clientY]), 0.5], @@ -831,6 +857,13 @@ export class TLApp< } readonly onPointerUp: TLEvents['pointer'] = (info, e) => { + if (!this.editingShape && e.button === 1 && this.isIn('move')) { + this.selectedTool.transition('idle', { exit: true }) + e.stopPropagation() + e.preventDefault() + return + } + if ('clientX' in e) { this.inputs.onPointerUp( [...this.viewport.getPagePoint([e.clientX, e.clientY]), 0.5], @@ -847,11 +880,7 @@ export class TLApp< readonly onKeyDown: TLEvents['keyboard'] = (info, e) => { if (!this.editingShape && e['key'] === ' ' && !this.isIn('move')) { - e.stopPropagation() - e.preventDefault() - const prevTool = this.selectedTool - this.transition('move', { prevTool }) - this.selectedTool.transition('idleHold') + this.temporaryTransitionToMove(e) return } this.inputs.onKeyDown(e) diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/TLSelectTool.tsx b/tldraw/packages/core/src/lib/tools/TLSelectTool/TLSelectTool.tsx index 7cce836304..bd2cc761e2 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/TLSelectTool.tsx +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/TLSelectTool.tsx @@ -3,6 +3,7 @@ import type { TLEvents, TLEventMap } from '~types' import { IdleState, BrushingState, + ContextMenuState, PointingCanvasState, PointingShapeState, PointingShapeBehindBoundsState, @@ -35,6 +36,7 @@ export class TLSelectTool< static states = [ IdleState, BrushingState, + ContextMenuState, PointingCanvasState, PointingShapeState, PointingShapeBehindBoundsState, diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/ContextMenuState.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/ContextMenuState.ts new file mode 100644 index 0000000000..35013b4bca --- /dev/null +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/ContextMenuState.ts @@ -0,0 +1,39 @@ +/* eslint-disable @typescript-eslint/no-non-null-assertion */ +import { TLApp, TLSelectTool, TLShape, TLToolState } from '~lib' +import { + TLEvents, + TLSelectionHandle, + TLEventMap, + TLEventSelectionInfo, +} from '~types' + +export class ContextMenuState< + S extends TLShape, + K extends TLEventMap, + R extends TLApp, + P extends TLSelectTool +> extends TLToolState { + static id = 'contextMenu' + + handle?: TLSelectionHandle + + onEnter = (info: TLEventSelectionInfo) => { + this.handle = info.handle + } + + onPointerDown: TLEvents['pointer'] = () => { + this.tool.transition('idle') + } + + onPinch: TLEvents['pinch'] = info => { + this.tool.transition('idle') + } + + onPinchEnd: TLEvents['pinch'] = () => { + this.tool.transition('idle') + } + + onWheel: TLEvents['wheel'] = (info, e) => { + this.tool.transition('idle') + } +} diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts index 41cc6eb47b..5c61c1be9f 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts @@ -43,6 +43,11 @@ export class IdleState< inputs: { ctrlKey }, } = this.app + if (event.button === 2) { + this.tool.transition('contextMenu') + return + } + // Holding ctrlKey should ignore shapes if (ctrlKey) { this.tool.transition('pointingCanvas') diff --git a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/index.ts b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/index.ts index 55b05175c5..94911f6947 100644 --- a/tldraw/packages/core/src/lib/tools/TLSelectTool/states/index.ts +++ b/tldraw/packages/core/src/lib/tools/TLSelectTool/states/index.ts @@ -1,4 +1,5 @@ export * from './BrushingState' +export * from './ContextMenuState' export * from './IdleState' export * from './PointingShapeState' export * from './PointingBoundsBackgroundState' diff --git a/tldraw/packages/react/src/components/AppCanvas.tsx b/tldraw/packages/react/src/components/AppCanvas.tsx index bb90688471..fafb883578 100644 --- a/tldraw/packages/react/src/components/AppCanvas.tsx +++ b/tldraw/packages/react/src/components/AppCanvas.tsx @@ -33,6 +33,7 @@ export const AppCanvas = observer(function InnerApp( showRotateHandles={app.showRotateHandles} showSelectionDetail={app.showSelectionDetail} showContextBar={app.showContextBar} + showContextMenu={app.showContextMenu} cursor={app.cursors.cursor} cursorRotation={app.cursors.rotation} selectionRotation={app.selectionRotation} diff --git a/tldraw/packages/react/src/components/Canvas/Canvas.test.tsx b/tldraw/packages/react/src/components/Canvas/Canvas.test.tsx index c4781106b3..22b46dfe6e 100644 --- a/tldraw/packages/react/src/components/Canvas/Canvas.test.tsx +++ b/tldraw/packages/react/src/components/Canvas/Canvas.test.tsx @@ -22,6 +22,7 @@ describe('Canvas', () => { showRotateHandles={app.showRotateHandles} showSelectionDetail={app.showSelectionDetail} showContextBar={app.showContextBar} + showContextMenu={app.showContextMenu} /> ) } diff --git a/tldraw/packages/react/src/components/Canvas/Canvas.tsx b/tldraw/packages/react/src/components/Canvas/Canvas.tsx index 1c836db9f2..4d5a72f796 100644 --- a/tldraw/packages/react/src/components/Canvas/Canvas.tsx +++ b/tldraw/packages/react/src/components/Canvas/Canvas.tsx @@ -54,6 +54,7 @@ export interface TLCanvasProps { showResizeHandles: boolean showRotateHandles: boolean showContextBar: boolean + showContextMenu: boolean showSelectionDetail: boolean showSelectionRotation: boolean children: React.ReactNode @@ -82,6 +83,7 @@ export const Canvas = observer(function Renderer({ showRotateHandles = true, showSelectionDetail = true, showContextBar = true, + showContextMenu = true, showGrid = true, gridSize = 8, onEditingEnd = NOOP, diff --git a/tldraw/packages/react/src/components/Renderer/Renderer.test.tsx b/tldraw/packages/react/src/components/Renderer/Renderer.test.tsx index 245140a2c8..a3a6d88d52 100644 --- a/tldraw/packages/react/src/components/Renderer/Renderer.test.tsx +++ b/tldraw/packages/react/src/components/Renderer/Renderer.test.tsx @@ -25,6 +25,7 @@ describe('HTMLLayer', () => { showRotateHandles={app.showRotateHandles} showSelectionDetail={app.showSelectionDetail} showContextBar={app.showContextBar} + showContextMenu={app.showContextBar} /> ) }