mirror of
https://github.com/logseq/logseq.git
synced 2026-06-01 19:01:22 +00:00
Merge pull request #6489 from logseq/enhance/whiteboards-ui
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
export * from './ContextMenu'
|
||||
@@ -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>
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user