Merge pull request #6489 from logseq/enhance/whiteboards-ui

This commit is contained in:
Peng Xiao
2022-08-29 17:34:26 +08:00
committed by GitHub
19 changed files with 305 additions and 49 deletions

View File

@@ -10,6 +10,7 @@
"dev:vite": "tsup --watch --sourcemap inline"
},
"devDependencies": {
"@radix-ui/react-context-menu": "^1.0.0",
"@radix-ui/react-dropdown-menu": "^1.0.0",
"@radix-ui/react-select": "^1.0.0",
"@radix-ui/react-separator": "^1.0.0",

View File

@@ -11,6 +11,7 @@ import {
import * as React from 'react'
import { AppUI } from './components/AppUI'
import { ContextBar } from './components/ContextBar'
import { ContextMenu } from './components/ContextMenu'
import { useFileDrop } from './hooks/useFileDrop'
import { usePaste } from './hooks/usePaste'
import { useQuickAdd } from './hooks/useQuickAdd'
@@ -80,6 +81,7 @@ export const App = function App({
const onFileDrop = useFileDrop(contextValue)
const onPaste = usePaste(contextValue)
const onQuickAdd = useQuickAdd()
const ref = React.useRef<HTMLDivElement>(null);
const onPersistOnDiff: TLReactCallbacks<Shape>['onPersist'] = React.useCallback(
(app, info) => {
@@ -102,11 +104,13 @@ export const App = function App({
model={model}
{...rest}
>
<div className="logseq-tldraw logseq-tldraw-wrapper">
<AppCanvas components={components}>
<AppUI />
</AppCanvas>
</div>
<ContextMenu collisionRef={ref}>
<div ref={ref} className="logseq-tldraw logseq-tldraw-wrapper">
<AppCanvas components={components}>
<AppUI />
</AppCanvas>
</div>
</ContextMenu>
</AppProvider>
</LogseqContext.Provider>
)

View File

@@ -0,0 +1,129 @@
import { useApp } from '@tldraw/react'
import { MOD_KEY} from '@tldraw/core'
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
collisionRef: React.RefObject<HTMLDivElement>
}
export const ContextMenu = observer(function ContextMenu({ children, collisionRef }: ContextMenuProps) {
const app = useApp()
const rContent = React.useRef<HTMLDivElement>(null)
return (
<ReactContextMenu.Root onOpenChange={state => {if (!state) app.transition('select')}}>
<ReactContextMenu.Trigger>{children}</ReactContextMenu.Trigger>
<ReactContextMenu.Content className="tl-context-menu"
ref={rContent}
onEscapeKeyDown={preventDefault}
collisionBoundary={collisionRef.current}
asChild
tabIndex={-1}
>
<div>
{app.selectedShapes?.size > 0 && (
<>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.copy()}>
Copy
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code>{MOD_KEY}</code> <code>C</code></span>
</div>
</ReactContextMenu.Item>
</>
)}
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.paste()}>
Paste
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code>{MOD_KEY}</code> <code>V</code></span>
</div>
</ReactContextMenu.Item>
<ReactContextMenu.Separator className="menu-separator"/>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.api.selectAll()}>
Select All
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code>{MOD_KEY}</code> <code>A</code></span>
</div>
</ReactContextMenu.Item>
{app.selectedShapes?.size > 1 && (
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.api.deselectAll()}>
Deselect All
</ReactContextMenu.Item>
)}
{app.selectedShapes?.size > 0 && (
<>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.api.deleteShapes()}>
Delete
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code>Del</code></span>
</div>
</ReactContextMenu.Item>
{app.selectedShapes?.size > 1 && (
<>
<ReactContextMenu.Separator className="menu-separator"/>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.flipHorizontal()}>
Flip Horizontally
</ReactContextMenu.Item>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.flipVertical()}>
Flip Vertically
</ReactContextMenu.Item>
</>
)}
<ReactContextMenu.Separator className="menu-separator"/>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.bringToFront()}>
Move to Front
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code></code> <code>]</code></span>
</div>
</ReactContextMenu.Item>
<ReactContextMenu.Item
className="tl-context-menu-button">
Move forwards
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code>]</code></span>
</div>
</ReactContextMenu.Item>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.sendToBack()}>
Move to back
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code></code> <code>[</code></span>
</div>
</ReactContextMenu.Item>
<ReactContextMenu.Item
className="tl-context-menu-button"
onClick={() => app.sendBackward()}>
Move backwards
<div className="tl-context-menu-right-slot">
<span className="keyboard-shortcut"><code>[</code></span>
</div>
</ReactContextMenu.Item>
</>
)}
</div>
</ReactContextMenu.Content>
</ReactContextMenu.Root>
)
})

View File

@@ -0,0 +1 @@
export * from './ContextMenu'

View File

@@ -1,5 +1,6 @@
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import { useApp } from '@tldraw/react'
import { MOD_KEY} from '@tldraw/core'
import { observer } from 'mobx-react-lite'
export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
@@ -19,39 +20,39 @@ export const ZoomMenu = observer(function ZoomMenu(): JSX.Element {
>
<DropdownMenuPrimitive.Arrow style={{ fill: 'white' }}></DropdownMenuPrimitive.Arrow>
<DropdownMenuPrimitive.Item
className="tl-zoom-menu-dropdown-item"
className="menu-link tl-zoom-menu-dropdown-item"
onSelect={preventEvent}
onClick={app.api.zoomToFit}
>
Zoom to Fit <div className="tl-zoom-menu-right-slot"></div>
</DropdownMenuPrimitive.Item>
<DropdownMenuPrimitive.Item
className="tl-zoom-menu-dropdown-item"
className="menu-link tl-zoom-menu-dropdown-item"
onSelect={preventEvent}
onClick={app.api.zoomToSelection}
>
Zoom to Selection <div className="tl-zoom-menu-right-slot">+Minus</div>
Zoom to Selection <div className="tl-zoom-menu-right-slot"><span className="keyboard-shortcut"><code>{MOD_KEY}</code> <code>-</code></span></div>
</DropdownMenuPrimitive.Item>
<DropdownMenuPrimitive.Item
className="tl-zoom-menu-dropdown-item"
className="menu-link tl-zoom-menu-dropdown-item"
onSelect={preventEvent}
onClick={app.api.zoomIn}
>
Zoom In <div className="tl-zoom-menu-right-slot">+Plus</div>
Zoom In <div className="tl-zoom-menu-right-slot"><span className="keyboard-shortcut"><code>{MOD_KEY}</code> <code>+</code></span></div>
</DropdownMenuPrimitive.Item>
<DropdownMenuPrimitive.Item
className="tl-zoom-menu-dropdown-item"
className="menu-link tl-zoom-menu-dropdown-item"
onSelect={preventEvent}
onClick={app.api.zoomOut}
>
Zoom Out <div className="tl-zoom-menu-right-slot">+Minus</div>
Zoom Out <div className="tl-zoom-menu-right-slot"><span className="keyboard-shortcut"><code>{MOD_KEY}</code> <code>-</code></span></div>
</DropdownMenuPrimitive.Item>
<DropdownMenuPrimitive.Item
className="tl-zoom-menu-dropdown-item"
className="menu-link tl-zoom-menu-dropdown-item"
onSelect={preventEvent}
onClick={app.api.resetZoom}
>
Reset Zoom <div className="tl-zoom-menu-right-slot">+0</div>
Reset Zoom <div className="tl-zoom-menu-right-slot"><span className="keyboard-shortcut"><code></code> <code>0</code></span></div>
</DropdownMenuPrimitive.Item>
</DropdownMenuPrimitive.Content>
</DropdownMenuPrimitive.Root>

View File

@@ -36,6 +36,45 @@
font-size: inherit;
}
.tl-context-menu-button {
@apply flex items-center px-4 py-1 text-sm !important;
min-width: 220px;
all: unset;
line-height: 1;
height: 25px;
padding: 0 5px;
position: relative;
user-select: none;
color: var(--ls-primary-text-color);
&:hover,
&:focus {
background-color: var(--ls-secondary-background-color) !important;
}
}
.tl-context-menu {
@apply relative py-2 flex bottom-0 flex border-0 rounded shadow-lg;
opacity: 100%;
user-select: none;
flex-direction: column;
z-index: 180;
min-width: 220px;
pointer-events: 'all';
background: var(--ls-primary-background-color);
}
.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;
@@ -69,33 +108,26 @@
}
.tl-zoom-menu-dropdown-menu-button {
@apply py-2 rounded shadow-lg;
opacity: 100%;
background-color: var(--color-panel);
border-radius: 6px;
padding: 5px;
box-shadow: 0 10px 38px -10px rgba(22, 23, 24, 0.35), 0 10px 20px -15px rgba(22, 23, 24, 0.2);
background-color: var(--ls-primary-background-color);
> span svg {
fill: var(--color-panel) !important;
display: none !important;
}
}
.tl-zoom-menu-dropdown-item {
@apply flex items-center px-4 py-1 text-sm !important;
min-width: 220px;
all: unset;
font-size: 13px;
line-height: 1;
color: black;
border-radius: 3px;
display: flex;
align-items: center;
height: 25px;
padding: 0 5px;
position: relative;
user-select: none;
color: var(--color-text);
&:hover {
cursor: pointer;
background-color: var(--color-hover);
}
}
@@ -103,7 +135,6 @@
.tl-zoom-menu-right-slot {
margin-left: auto;
padding-left: 20px;
color: var(--color-text);
}
.tl-contextbar {