Browse Source

feat: show shape links on hovering

Peng Xiao 2 years ago
parent
commit
e5ccec389b

+ 1 - 2
src/main/frontend/extensions/tldraw.cljs

@@ -34,8 +34,7 @@
   (block/breadcrumb {:preview? true}
                     (state/get-current-repo)
                     (uuid (gobj/get props "blockId"))
-                    {:end-separator? true
-                     :level-limit (gobj/get props "levelLimit" 3)}))
+                    {:level-limit (gobj/get props "levelLimit" 3)}))
 
 (rum/defc page-name-link
   [props]

+ 3 - 3
tldraw/apps/tldraw-logseq/src/app.tsx

@@ -5,7 +5,6 @@ import {
   AppCanvas,
   AppProvider,
   TLReactCallbacks,
-  TLReactComponents,
   TLReactToolConstructor,
   useApp,
 } from '@tldraw/react'
@@ -13,6 +12,7 @@ import * as React from 'react'
 import { AppUI } from './components/AppUI'
 import { ContextBar } from './components/ContextBar'
 import { ContextMenu } from './components/ContextMenu'
+import { QuickLinks } from './components/QuickLinks'
 import { useDrop } from './hooks/useDrop'
 import { usePaste } from './hooks/usePaste'
 import { useQuickAdd } from './hooks/useQuickAdd'
@@ -21,6 +21,7 @@ import {
   EllipseTool,
   HighlighterTool,
   HTMLTool,
+  IFrameTool,
   LineTool,
   LogseqPortalTool,
   NuEraseTool,
@@ -29,14 +30,12 @@ import {
   shapes,
   TextTool,
   YouTubeTool,
-  IFrameTool,
   type Shape,
 } from './lib'
 import { LogseqContext, type LogseqContextValue } from './lib/logseq-context'
 
 const tools: TLReactToolConstructor<Shape>[] = [
   BoxTool,
-  // DotTool,
   EllipseTool,
   PolygonTool,
   NuEraseTool,
@@ -73,6 +72,7 @@ const AppImpl = () => {
     () => ({
       ContextBar,
       ReferencesCount,
+      QuickLinks,
     }),
     []
   )

+ 49 - 0
tldraw/apps/tldraw-logseq/src/components/QuickLinks/QuickLinks.tsx

@@ -0,0 +1,49 @@
+import { validUUID } from '@tldraw/core'
+import type { TLQuickLinksComponent } from '@tldraw/react'
+import { observer } from 'mobx-react-lite'
+import * as React from 'react'
+import type { Shape } from '../../lib'
+import { LogseqContext } from '../../lib/logseq-context'
+import { TablerIcon } from '../icons'
+
+const BlockLink = ({ type, id }: { type?: 'P' | 'B'; id: string }) => {
+  const {
+    handlers,
+    renderers: { Breadcrumb, PageNameLink },
+  } = React.useContext(LogseqContext)
+
+  type = type ?? (validUUID(id) ? 'B' : 'P')
+
+  return (
+    <>
+      <TablerIcon name={type === 'P' ? 'page' : 'block'} />
+      {type === 'P' ? <PageNameLink pageName={id} /> : <Breadcrumb levelLimit={1} blockId={id} />}
+    </>
+  )
+}
+
+export const QuickLinks: TLQuickLinksComponent<Shape> = observer(({ id, shape }) => {
+  const refs = shape.props.refs ?? []
+  const portalType = shape.props.type === 'logseq-portal' && shape.props.blockType
+
+  if (refs.length === 0 && !portalType) return null
+
+  return (
+    <div className="tl-quick-links">
+      {portalType && shape.props.type === 'logseq-portal' && (
+        <>
+          <div className="tl-quick-links-row tl-quick-links-row-primary">
+            <BlockLink id={shape.props.pageId} type={portalType} />
+          </div>
+        </>
+      )}
+      {refs.map(ref => {
+        return (
+          <div key={ref} className="tl-quick-links-row">
+            <BlockLink id={ref} />
+          </div>
+        )
+      })}
+    </div>
+  )
+})

+ 1 - 0
tldraw/apps/tldraw-logseq/src/components/QuickLinks/index.ts

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

+ 64 - 64
tldraw/apps/tldraw-logseq/src/components/inputs/ShapeLinksInput.tsx

@@ -69,7 +69,9 @@ export function ShapeLinksInput({
   ...rest
 }: ShapeLinksInputProps) {
   const noOfLinks = refs.length + (pageId ? 1 : 0)
-  const [showQuickSearch, setShowQuickSearch] = React.useState(false)
+  const canAddLink = refs.length === 0
+
+  const [showQuickSearch, setShowQuickSearch] = React.useState(canAddLink)
 
   const addNewRef = (value?: string) => {
     if (value && !refs.includes(value)) {
@@ -79,43 +81,39 @@ export function ShapeLinksInput({
   }
 
   React.useEffect(() => {
-    if (!showQuickSearch) {
-      const callback = (keyboardEvent: Mousetrap.ExtendedKeyboardEvent, combo: string) => {
-        keyboardEvent.preventDefault()
-        keyboardEvent.stopPropagation()
-        ;(async () => {
-          // TODO: thinking about how to make this more generic with usePaste hook
-          // TODO: handle whiteboard shapes?
-          const items = await navigator.clipboard.read()
-          if (items.length > 0) {
-            const blob = await items[0].getType('text/plain')
-            const rawText = (await blob.text()).trim()
-
-            if (rawText) {
-              const text = rawText.trim()
-              let newValue: string | undefined
-              if (/^\(\(.*\)\)$/.test(rawText) && rawText.length === NIL_UUID.length + 4) {
-                const blockRef = rawText.slice(2, -2)
-                if (validUUID(blockRef)) {
-                  newValue = blockRef
-                }
-              } else if (/^\[\[.*\]\]$/.test(rawText)) {
-                newValue = rawText.slice(2, -2)
+    const callback = (keyboardEvent: Mousetrap.ExtendedKeyboardEvent, combo: string) => {
+      keyboardEvent.preventDefault()
+      keyboardEvent.stopPropagation()
+      ;(async () => {
+        // TODO: thinking about how to make this more generic with usePaste hook
+        // TODO: handle whiteboard shapes?
+        const items = await navigator.clipboard.read()
+        if (items.length > 0) {
+          const blob = await items[0].getType('text/plain')
+          const rawText = (await blob.text()).trim()
+
+          if (rawText) {
+            let newValue: string | undefined
+            if (/^\(\(.*\)\)$/.test(rawText) && rawText.length === NIL_UUID.length + 4) {
+              const blockRef = rawText.slice(2, -2)
+              if (validUUID(blockRef)) {
+                newValue = blockRef
               }
-              addNewRef(newValue)
+            } else if (/^\[\[.*\]\]$/.test(rawText)) {
+              newValue = rawText.slice(2, -2)
             }
+            addNewRef(newValue)
           }
-        })()
-      }
+        }
+      })()
+    }
 
-      Mousetrap.bind(`mod+shift+v`, callback, 'keydown')
+    Mousetrap.bind(`mod+shift+v`, callback, 'keydown')
 
-      return () => {
-        Mousetrap.unbind(`mod+shift+v`, 'keydown')
-      }
+    return () => {
+      Mousetrap.unbind(`mod+shift+v`, 'keydown')
     }
-    return () => {}
-  }, [showQuickSearch])
+  }, [])
 
   const showReferencePanel = !!(pageId && portalType)
 
@@ -127,7 +125,7 @@ export function ShapeLinksInput({
       alignOffset={-6}
       label={
         <div className="flex gap-1 relative items-center justify-center px-1">
-          <TablerIcon name={noOfLinks > 0 ? "link" : "add-link"} />
+          <TablerIcon name={noOfLinks > 0 ? 'link' : 'add-link'} />
           {noOfLinks > 0 && <div className="tl-shape-links-count">{noOfLinks}</div>}
         </div>
       }
@@ -146,7 +144,7 @@ export function ShapeLinksInput({
         <div className="tl-shape-links-panel color-level">
           <div className="text-base font-bold inline-flex gap-1 items-center">
             <TablerIcon className="opacity-50" name="add-link" />
-            Your links
+            Your link
           </div>
           <div className="h-2" />
           <div className="whitespace-pre-wrap">
@@ -156,37 +154,39 @@ export function ShapeLinksInput({
 
           <div className="h-2" />
 
-          {showQuickSearch ? (
-            <LogseqQuickSearch
-              style={{
-                width: 'calc(100% - 46px)',
-                marginLeft: '46px',
-              }}
-              onBlur={() => setShowQuickSearch(false)}
-              placeholder="Start typing to search..."
-              onChange={addNewRef}
-            />
-          ) : (
-            <div>
-              <Button
-                className="tl-shape-links-panel-add-button"
-                onClick={() => setShowQuickSearch(true)}
-              >
-                <TablerIcon name="plus" />
-                Add a new link
-              </Button>
-            </div>
+          {canAddLink && (
+            <>
+              {showQuickSearch ? (
+                <LogseqQuickSearch
+                  style={{
+                    width: 'calc(100% - 46px)',
+                    marginLeft: '46px',
+                  }}
+                  onBlur={() => setShowQuickSearch(false)}
+                  placeholder="Start typing to search..."
+                  onChange={addNewRef}
+                />
+              ) : (
+                <div>
+                  <Button
+                    className="tl-shape-links-panel-add-button"
+                    onClick={() => setShowQuickSearch(true)}
+                  >
+                    <TablerIcon name="plus" />
+                    Add a new link
+                  </Button>
+                </div>
+              )}
+              <div className="h-2" />
+              <div className="text-center">
+                <span className="opacity-50 mr-1">Paste from clipboard with</span>
+                <span className="keyboard-shortcut">
+                  <code>{MOD_KEY}</code> <code>⇧</code> <code>V</code>
+                </span>
+              </div>
+            </>
           )}
-          <div className="h-2" />
-          <div
-            className="text-center"
-            style={{ visibility: !showQuickSearch ? 'visible' : 'hidden' }}
-          >
-            <span className="opacity-50 mr-1">Paste from clipboard with</span>
-            <span className="keyboard-shortcut">
-              <code>{MOD_KEY}</code> <code>⇧</code> <code>V</code>
-            </span>
-          </div>
+
           {refs.length > 0 && (
             <>
               <div className="h-2" />

+ 38 - 3
tldraw/apps/tldraw-logseq/src/styles.css

@@ -991,11 +991,11 @@ html[data-theme='dark'] {
   }
 }
 
-.tl-references-count-container {
+.tl-backlinks-count-container {
   z-index: 10004;
 }
 
-.tl-references-count {
+.tl-backlinks-count {
   @apply inline-flex items-center justify-center p-1 transition-all;
   background-color: var(--color-selectedFill);
 
@@ -1008,7 +1008,7 @@ html[data-theme='dark'] {
   }
 }
 
-.tl-references-count-rounded {
+.tl-backlinks-count-rounded {
   border-radius: 50%;
   width: 32px;
   height: 32px;
@@ -1077,3 +1077,38 @@ html[data-theme='dark'] {
     opacity: 1;
   }
 }
+
+.tl-quick-links-container {
+  z-index: 10005;
+}
+
+.tl-quick-links {
+  @apply rounded text-sm w-fit;
+  background-color: var(--ls-secondary-background-color);
+}
+
+.tl-quick-links-row {
+  @apply flex px-2 items-center gap-1;
+  min-height: 26px;
+
+  > .breadcrumb {
+    margin: 0;
+  }
+
+  .page-ref {
+    @apply flex-1;
+  }
+
+  * {
+    color: inherit !important;
+  }
+}
+
+.tl-quick-links-row-primary {
+  @apply rounded;
+  background-color: var(--color-selectedFill);
+  color: #fff;
+}
+.tl-quick-links-row-secondary {
+  color: var(--ls-secondary-text-color);
+}

+ 5 - 4
tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts

@@ -19,13 +19,10 @@ export class IdleState<
     }
   }
 
-  onExit = () => {
-    this.app.setHoveredShape(undefined)
-  }
+  onExit = () => {}
 
   onPointerEnter: TLEvents<S>['pointer'] = info => {
     if (info.order) return
-
     switch (info.type) {
       case TLTargetType.Shape: {
         this.app.setHoveredShape(info.shape.id)
@@ -37,6 +34,10 @@ export class IdleState<
         }
         break
       }
+      case TLTargetType.Canvas: {
+        this.app.setHoveredShape(undefined)
+        break
+      }
     }
   }
 

+ 7 - 3
tldraw/packages/react/src/components/Canvas/Canvas.tsx

@@ -22,7 +22,8 @@ import { Container } from '../Container'
 import { ContextBarContainer } from '../ContextBarContainer'
 import { HTMLLayer } from '../HTMLLayer'
 import { Indicator } from '../Indicator'
-import { ReferencesContainer } from '../ReferencesCountContainer'
+import { QuickLinksContainer } from '../ReferencesCountContainer copy'
+import { BacklinksCountContainer } from '../ReferencesCountContainer'
 import { SelectionDetailContainer } from '../SelectionDetailContainer'
 import { Shape } from '../Shape'
 import { SVGContainer } from '../SVGContainer'
@@ -157,13 +158,16 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
           {hoveredShape && (
             <Indicator key={'hovered_indicator_' + hoveredShape.id} shape={hoveredShape} />
           )}
-          {selectedOrHooveredShape && components.ReferencesCount && (
-            <ReferencesContainer
+          {selectedOrHooveredShape && components.BacklinksCount && (
+            <BacklinksCountContainer
               hidden={false}
               bounds={selectedOrHooveredShape.bounds}
               shape={selectedOrHooveredShape}
             />
           )}
+          {hoveredShape && hoveredShape !== singleSelectedShape && components.QuickLinks && (
+            <QuickLinksContainer hidden={false} bounds={hoveredShape.bounds} shape={hoveredShape} />
+          )}
           {brush && components.Brush && <components.Brush bounds={brush} />}
           {selectedShapes && selectionBounds && (
             <>

+ 66 - 0
tldraw/packages/react/src/components/ReferencesCountContainer copy/QuickLinksContainer.tsx

@@ -0,0 +1,66 @@
+import type { TLBounds } from '@tldraw/core'
+import { observer } from 'mobx-react-lite'
+import { useApp, useRendererContext } from '../../hooks'
+import { useShapeEvents } from '../../hooks/useShapeEvents'
+import type { TLReactShape } from '../../lib'
+import { Container } from '../Container'
+import { HTMLContainer } from '../HTMLContainer'
+
+export interface TLQuickLinksContainerProps<S extends TLReactShape> {
+  hidden: boolean
+  bounds: TLBounds
+  shape: S
+}
+
+// backlinks
+export const QuickLinksContainer = observer(function QuickLinksContainer<S extends TLReactShape>({
+  bounds,
+  shape,
+}: TLQuickLinksContainerProps<S>) {
+  const {
+    viewport: {
+      camera: { zoom },
+    },
+    components: { QuickLinks },
+  } = useRendererContext()
+
+  const app = useApp<S>()
+
+  const events = useShapeEvents(shape)
+
+  if (!QuickLinks) throw Error('Expected a QuickLinks component.')
+
+  const stop: React.EventHandler<any> = e => e.stopPropagation()
+
+  const rounded = bounds.height * zoom < 50 || !app.selectedShapesArray.includes(shape)
+
+  return (
+    <Container bounds={bounds} className="tl-quick-links-container">
+      <HTMLContainer>
+        <span
+          style={{
+            position: 'absolute',
+            top: '100%',
+            pointerEvents: 'all',
+            transformOrigin: 'left top',
+            paddingTop: '8px',
+            // anti-scale the container so that it always show in 100% for the user
+            transform: 'scale(var(--tl-scale))',
+            // Make it a little bit easier to click
+            minWidth: '320px',
+          }}
+          {...events}
+          onPointerDown={stop}
+          onWheelCapture={stop}
+          title="Shape Quick Links"
+        >
+          <QuickLinks
+            className={'tl-backlinks-count ' + (rounded ? 'tl-backlinks-count-rounded' : '')}
+            id={shape.id}
+            shape={shape}
+          />
+        </span>
+      </HTMLContainer>
+    </Container>
+  )
+})

+ 1 - 0
tldraw/packages/react/src/components/ReferencesCountContainer copy/index.ts

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

+ 8 - 8
tldraw/packages/react/src/components/ReferencesCountContainer/ReferencesCountContainer.tsx

@@ -5,33 +5,33 @@ import type { TLReactShape } from '../../lib'
 import { Container } from '../Container'
 import { HTMLContainer } from '../HTMLContainer'
 
-export interface TLReferencesCountContainerProps<S extends TLReactShape> {
+export interface TLBacklinksCountContainerProps<S extends TLReactShape> {
   hidden: boolean
   bounds: TLBounds
   shape: S
 }
 
 // backlinks
-export const ReferencesContainer = observer(function ReferencesCountContainer<
+export const BacklinksCountContainer = observer(function BacklinksCountContainer<
   S extends TLReactShape
->({ bounds, hidden, shape }: TLReferencesCountContainerProps<S>) {
+>({ bounds, shape }: TLBacklinksCountContainerProps<S>) {
   const {
     viewport: {
       camera: { zoom },
     },
-    components: { ReferencesCount },
+    components: { BacklinksCount },
   } = useRendererContext()
 
   const app = useApp<S>()
 
-  if (!ReferencesCount) throw Error('Expected a ReferencesCount component.')
+  if (!BacklinksCount) throw Error('Expected a ReferencesCount component.')
 
   const stop: React.EventHandler<any> = e => e.stopPropagation()
 
   const rounded = bounds.height * zoom < 50 || !app.selectedShapesArray.includes(shape)
 
   return (
-    <Container bounds={bounds} className="tl-references-count-container">
+    <Container bounds={bounds} className="tl-backlinks-count-container">
       <HTMLContainer>
         <span
           style={{
@@ -45,8 +45,8 @@ export const ReferencesContainer = observer(function ReferencesCountContainer<
           onWheelCapture={stop}
           title="Shape Backlinks"
         >
-          <ReferencesCount
-            className={'tl-references-count ' + (rounded ? 'tl-references-count-rounded' : '')}
+          <BacklinksCount
+            className={'tl-backlinks-count ' + (rounded ? 'tl-backlinks-count-rounded' : '')}
             id={shape.id}
             shape={shape}
           />

+ 12 - 1
tldraw/packages/react/src/types/component-props.ts

@@ -82,6 +82,16 @@ export type TLReferencesCountComponent<S extends TLReactShape = TLReactShape> =
   props: TLReferencesCountComponentProps<S>
 ) => JSX.Element | null
 
+export interface TLQuickLinksComponentProps<S extends TLReactShape = TLReactShape> {
+  shape: S
+  id: string
+  className?: string
+}
+
+export type TLQuickLinksComponent<S extends TLReactShape = TLReactShape> = (
+  props: TLQuickLinksComponentProps<S>
+) => JSX.Element | null
+
 export interface TLGridProps {
   size: number
 }
@@ -92,7 +102,8 @@ export type TLReactComponents<S extends TLReactShape = TLReactShape> = {
   SelectionBackground?: TLBoundsComponent<S> | null
   SelectionForeground?: TLBoundsComponent<S> | null
   SelectionDetail?: TLSelectionDetailComponent<S> | null
-  ReferencesCount?: TLReferencesCountComponent<S> | null
+  BacklinksCount?: TLReferencesCountComponent<S> | null
+  QuickLinks?: TLQuickLinksComponent<S> | null
   DirectionIndicator?: TLDirectionIndicatorComponent<S> | null
   Handle?: TLHandleComponent<S> | null
   ContextBar?: TLContextBarComponent<S> | null