Просмотр исходного кода

Fix (Whiteboards): Portal height calculation bug (#9161)

* fix: portal height

* fix: remove circle button from portals

* enhance: remove unnecessary dev tools

* enhance: convert collapse btn-group to toggle

* enhance: introduce collapse shortcut
Konstantinos 2 лет назад
Родитель
Сommit
1aca903449

+ 1 - 17
tldraw/apps/tldraw-logseq/src/components/Button/CircleButton.tsx

@@ -1,11 +1,8 @@
-import React from 'react'
 import { TablerIcon } from '../icons'
 
 export const CircleButton = ({
-  active,
   style,
   icon,
-  otherIcon,
   onClick,
 }: {
   active?: boolean
@@ -14,27 +11,14 @@ export const CircleButton = ({
   otherIcon?: string
   onClick: () => void
 }) => {
-  const [recentlyChanged, setRecentlyChanged] = React.useState(false)
-
-  React.useEffect(() => {
-    setRecentlyChanged(true)
-    const timer = setTimeout(() => {
-      setRecentlyChanged(false)
-    }, 500)
-    return () => clearTimeout(timer)
-  }, [active])
-
   return (
     <button
-      data-active={active}
-      data-recently-changed={recentlyChanged}
       data-html2canvas-ignore="true"
       style={style}
       className="tl-circle-button"
       onPointerDown={onClick}
     >
-      <div className="tl-circle-button-icons-wrapper" data-icons-count={otherIcon ? 2 : 1}>
-        {otherIcon && <TablerIcon name={otherIcon} />}
+      <div className="tl-circle-button-icons-wrapper">
         <TablerIcon name={icon} />
       </div>
     </button>

+ 19 - 81
tldraw/apps/tldraw-logseq/src/components/ContextBar/contextBarActionFactory.tsx

@@ -33,7 +33,7 @@ import { LogseqContext } from '../../lib/logseq-context'
 
 export const contextBarActionTypes = [
   // Order matters
-  'Edit',
+  'LogseqPortalViewMode',
   'Geometry',
   'AutoResizing',
   'Swatch',
@@ -44,14 +44,12 @@ export const contextBarActionTypes = [
   'YoutubeLink',
   'TwitterLink',
   'IFrameSource',
-  'LogseqPortalViewMode',
   'ArrowMode',
   'Links',
 ] as const
 
 type ContextBarActionType = typeof contextBarActionTypes[number]
 const singleShapeActions: ContextBarActionType[] = [
-  'Edit',
   'YoutubeLink',
   'TwitterLink',
   'IFrameSource',
@@ -65,7 +63,6 @@ type ShapeType = Shape['props']['type']
 export const shapeMapping: Record<ShapeType, ContextBarActionType[]> = {
   'logseq-portal': [
     'Swatch',
-    'Edit',
     'LogseqPortalViewMode',
     'ScaleLevel',
     'AutoResizing',
@@ -74,13 +71,13 @@ export const shapeMapping: Record<ShapeType, ContextBarActionType[]> = {
   youtube: ['YoutubeLink', 'Links'],
   tweet: ['TwitterLink', 'Links'],
   iframe: ['IFrameSource', 'Links'],
-  box: ['Edit', 'Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'],
-  ellipse: ['Edit', 'Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'],
-  polygon: ['Edit', 'Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'],
-  line: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'ArrowMode', 'Links'],
+  box: ['Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'],
+  ellipse: ['Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'],
+  polygon: ['Geometry', 'TextStyle', 'Swatch', 'ScaleLevel', 'NoFill', 'StrokeType', 'Links'],
+  line: ['TextStyle', 'Swatch', 'ScaleLevel', 'ArrowMode', 'Links'],
   pencil: ['Swatch', 'Links', 'ScaleLevel'],
   highlighter: ['Swatch', 'Links', 'ScaleLevel'],
-  text: ['Edit', 'TextStyle', 'Swatch', 'ScaleLevel', 'AutoResizing', 'Links'],
+  text: ['TextStyle', 'Swatch', 'ScaleLevel', 'AutoResizing', 'Links'],
   html: ['ScaleLevel', 'AutoResizing', 'Links'],
   image: ['Links'],
   video: ['Links'],
@@ -98,54 +95,6 @@ function filterShapeByAction<S extends Shape>(type: ContextBarActionType) {
   return unlockedSelectedShapes.filter(shape => shapeMapping[shape.props.type]?.includes(type))
 }
 
-const EditAction = observer(() => {
-  const {
-    handlers: { isWhiteboardPage, redirectToPage, getRedirectPageName, insertFirstPageBlock },
-  } = React.useContext(LogseqContext)
-
-  const app = useApp<Shape>()
-  const shape = filterShapeByAction('Edit')[0]
-
-  const iconName =
-    ('label' in shape.props && shape.props.label) || ('text' in shape.props && shape.props.text)
-      ? 'forms'
-      : 'text'
-
-  return (
-    <Button
-      type="button"
-      tooltip="Edit"
-      onClick={() => {
-        app.api.editShape(shape)
-        if (shape.props.type === 'logseq-portal') {
-          let uuid = shape.props.pageId
-          if (shape.props.blockType === 'P') {
-            if (isWhiteboardPage(uuid)) {
-              redirectToPage(uuid)
-            }
-
-            const pageId = getRedirectPageName(shape.props.pageId)
-            let pageBlocksTree = window.logseq?.api?.get_page_blocks_tree?.(pageId)
-
-            if (pageBlocksTree?.length === 0) {
-              insertFirstPageBlock(pageId)
-              pageBlocksTree = window.logseq?.api?.get_page_blocks_tree?.(pageId)
-            }
-
-            const firstNonePropertyBlock =
-              pageBlocksTree?.find(b => !('propertiesOrder' in b)) || pageBlocksTree[0]
-
-            uuid = firstNonePropertyBlock?.uuid
-          }
-          window.logseq?.api?.edit_block?.(uuid)
-        }
-      }}
-    >
-      <TablerIcon name={iconName} />
-    </Button>
-  )
-})
-
 const AutoResizingAction = observer(() => {
   const app = useApp<Shape>()
   const shapes = filterShapeByAction<LogseqPortalShape | TextShape | HTMLShape>('AutoResizing')
@@ -181,30 +130,20 @@ const LogseqPortalViewModeAction = observer(() => {
   const shapes = filterShapeByAction<LogseqPortalShape>('LogseqPortalViewMode')
 
   const collapsed = shapes.every(s => s.collapsed)
-  const ViewModeOptions: ToggleGroupInputOption[] = [
-    {
-      value: '1',
-      icon: 'object-compact',
-      tooltip: 'Collapse',
-    },
-    {
-      value: '0',
-      icon: 'object-expanded',
-      tooltip: 'Expand',
-    },
-  ]
+  if (!collapsed && !shapes.every(s => !s.collapsed)) {
+    return null
+  }
+
   return (
-    <ToggleGroupInput
-      title="View Mode"
-      options={ViewModeOptions}
-      value={collapsed ? '1' : '0'}
-      onValueChange={v => {
-        shapes.forEach(shape => {
-          shape.toggleCollapsed()
-        })
-        app.persist()
-      }}
-    />
+    <ToggleInput
+      tooltip={collapsed ? 'Expand' : 'Collapse'}
+      toggle={shapes.every(s => s.props.type === 'logseq-portal')}
+      className="tl-button"
+      pressed={collapsed}
+      onPressedChange={() => app.api.setCollapsed(!collapsed) }
+    >
+      <TablerIcon name={collapsed ? 'object-expanded' : 'object-compact'} />
+    </ToggleInput>
   )
 })
 
@@ -527,7 +466,6 @@ const LinksAction = observer(() => {
   )
 })
 
-contextBarActionMapping.set('Edit', EditAction)
 contextBarActionMapping.set('Geometry', GeometryAction)
 contextBarActionMapping.set('AutoResizing', AutoResizingAction)
 contextBarActionMapping.set('LogseqPortalViewMode', LogseqPortalViewModeAction)

+ 1 - 62
tldraw/apps/tldraw-logseq/src/components/Devtools/Devtools.tsx

@@ -1,59 +1,12 @@
-import { useApp, useRendererContext } from '@tldraw/react'
-import { autorun } from 'mobx'
+import { useRendererContext } from '@tldraw/react'
 import { observer } from 'mobx-react-lite'
 import React from 'react'
 import ReactDOM from 'react-dom'
-import type { Shape } from '../../lib'
 
 const printPoint = (point: number[]) => {
   return `[${point.map(d => d?.toFixed(2) ?? '-').join(', ')}]`
 }
 
-const HistoryStack = observer(function HistoryStack() {
-  const app = useApp<Shape>()
-  const anchorRef = React.useRef<HTMLDivElement>()
-  const [_, setTick] = React.useState(0)
-
-  React.useEffect(() => {
-    anchorRef.current = document.createElement('div')
-    anchorRef.current.style.display = 'contents'
-    document.body.append(anchorRef.current)
-    setTick(tick => tick + 1)
-    return () => {
-      anchorRef.current?.remove()
-    }
-  }, [])
-
-  React.useEffect(() => {
-    requestAnimationFrame(() => {
-      anchorRef.current
-        ?.querySelector(`[data-item-index="${app.history.pointer}"]`)
-        ?.scrollIntoView()
-    })
-  }, [app.history.pointer])
-
-  return anchorRef.current
-    ? ReactDOM.createPortal(
-        <div className="fixed z-[1000] left-4 max-w-[400px] top-4 overflow-scroll bg-gray-200 flex gap-2 p-2">
-          {app.history.stack.map((item, i) => (
-            <div
-              data-item-index={i}
-              style={{
-                background: app.history.pointer === i ? 'pink' : 'grey',
-              }}
-              key={i}
-              onClick={() => app.history.setPointer(i)}
-              className="flex items-center rounded-lg px-2 h-[32px] whitespace-nowrap"
-            >
-              {item.pages[0].nonce}
-            </div>
-          ))}
-        </div>,
-        anchorRef.current
-      )
-    : null
-})
-
 export const DevTools = observer(() => {
   const {
     viewport: {
@@ -63,13 +16,9 @@ export const DevTools = observer(() => {
     inputs,
   } = useRendererContext()
 
-  const canvasAnchorRef = React.useRef<HTMLElement | null>()
   const statusbarAnchorRef = React.useRef<HTMLElement | null>()
 
   React.useEffect(() => {
-    const canvasAnchor = document.getElementById('tl-dev-tools-canvas-anchor')
-    canvasAnchorRef.current = canvasAnchor
-
     const statusbarAnchor = document.getElementById('tl-statusbar-anchor')
     statusbarAnchorRef.current = statusbarAnchor
   }, [])
@@ -84,15 +33,6 @@ export const DevTools = observer(() => {
     .map(p => p.join(''))
     .join('|')
 
-  const originPoint = canvasAnchorRef.current
-    ? ReactDOM.createPortal(
-        <svg className="tl-renderer-dev-tools tl-grid">
-          <circle cx={point[0] * zoom} cy={point[1] * zoom} r="4" fill="red" />
-        </svg>,
-        canvasAnchorRef.current
-      )
-    : null
-
   const rendererStatus = statusbarAnchorRef.current
     ? ReactDOM.createPortal(
         <div
@@ -110,7 +50,6 @@ export const DevTools = observer(() => {
 
   return (
     <>
-      {originPoint}
       {rendererStatus}
     </>
   )

+ 45 - 56
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -15,7 +15,6 @@ import { action, computed, makeObservable } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
 import type { Shape, SizeLevel } from '.'
-import { CircleButton } from '../../components/Button'
 import { LogseqQuickSearch } from '../../components/QuickSearch'
 import { useCameraMovingRef } from '../../hooks/useCameraMoving'
 import { LogseqContext } from '../logseq-context'
@@ -149,8 +148,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     return this.props.blockType === 'B' ? this.props.compact : this.props.collapsed
   }
 
-  @action toggleCollapsed = async () => {
-    const collapsed = !this.collapsed
+  @action setCollapsed = async (collapsed: boolean) => {
     if (this.props.blockType === 'B') {
       this.update({ compact: collapsed })
       this.canResize[1] = !collapsed
@@ -192,28 +190,30 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     const [size, setSize] = React.useState<[number, number]>([0, 0])
     const app = useApp<Shape>()
     React.useEffect(() => {
-      if (ref?.current) {
-        const el = selector ? ref.current.querySelector<HTMLElement>(selector) : ref.current
-        if (el) {
-          const updateSize = () => {
-            const { width, height } = el.getBoundingClientRect()
-            const bound = Vec.div([width, height], app.viewport.camera.zoom) as [number, number]
-            setSize(bound)
-            return bound
-          }
-          updateSize()
-          // Hacky, I know 🤨
-          this.getInnerHeight = () => updateSize()[1]
-          const resizeObserver = new ResizeObserver(() => {
+      setTimeout(() => {
+        if (ref?.current) {
+          const el = selector ? ref.current.querySelector<HTMLElement>(selector) : ref.current
+          if (el) {
+            const updateSize = () => {
+              const { width, height } = el.getBoundingClientRect()
+              const bound = Vec.div([width, height], app.viewport.camera.zoom) as [number, number]
+              setSize(bound)
+              return bound
+            }
             updateSize()
-          })
-          resizeObserver.observe(el)
-          return () => {
-            resizeObserver.disconnect()
+            // Hacky, I know 🤨
+            this.getInnerHeight = () => updateSize()[1]
+            const resizeObserver = new ResizeObserver(() => {
+              updateSize()
+            })
+            resizeObserver.observe(el)
+            return () => {
+              resizeObserver.disconnect()
+            }
           }
         }
-      }
-      return () => {}
+        return () => {}
+      }, 10);
     }, [ref, selector])
     return size
   }
@@ -494,41 +494,30 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
               placeholder="Create or search your graph..."
             />
           ) : (
-            <>
-              <div
-                className="tl-logseq-portal-container"
-                data-collapsed={this.collapsed}
-                data-page-id={pageId}
-                data-portal-selected={portalSelected}
-                data-editing={isEditing}
-                style={portalStyle}
-              >
-                {!this.props.compact && !targetNotFound && (
-                  <LogseqPortalShapeHeader
-                    type={this.props.blockType ?? 'P'}
-                    fill={fill}
-                    opacity={opacity}
-                  >
-                    {this.props.blockType === 'P' ? (
-                      <PageName pageName={pageId} />
-                    ) : (
-                      <Breadcrumb blockId={pageId} />
-                    )}
-                  </LogseqPortalShapeHeader>
-                )}
-                {targetNotFound && <div className="tl-target-not-found">Target not found</div>}
-                {showingPortal && <PortalComponent {...componentProps} />}
-              </div>
-              {!app.readOnly && !isLocked && (
-                <CircleButton
-                  active={!!this.collapsed}
-                  style={{ opacity: isSelected ? 1 : 0 }}
-                  icon={this.props.blockType === 'B' ? 'block' : 'page'}
-                  onClick={this.toggleCollapsed}
-                  otherIcon={'whiteboard-element'}
-                />
+            <div
+              className="tl-logseq-portal-container"
+              data-collapsed={this.collapsed}
+              data-page-id={pageId}
+              data-portal-selected={portalSelected}
+              data-editing={isEditing}
+              style={portalStyle}
+            >
+              {!this.props.compact && !targetNotFound && (
+                <LogseqPortalShapeHeader
+                  type={this.props.blockType ?? 'P'}
+                  fill={fill}
+                  opacity={opacity}
+                >
+                  {this.props.blockType === 'P' ? (
+                    <PageName pageName={pageId} />
+                  ) : (
+                    <Breadcrumb blockId={pageId} />
+                  )}
+                </LogseqPortalShapeHeader>
               )}
-            </>
+              {targetNotFound && <div className="tl-target-not-found">Target not found</div>}
+              {showingPortal && <PortalComponent {...componentProps} />}
+            </div>
           )}
         </div>
       </HTMLContainer>

+ 1 - 47
tldraw/apps/tldraw-logseq/src/styles.css

@@ -547,7 +547,7 @@ button.tl-select-input-trigger {
 }
 
 .tl-circle-button {
-  @apply absolute flex items-center justify-center transition-all rounded-full shadow;
+  @apply absolute flex items-center justify-center rounded-full shadow;
 
   color: var(--ls-primary-text-color);
   background-color: var(--ls-secondary-background-color);
@@ -557,60 +557,14 @@ button.tl-select-input-trigger {
   width: 34px;
   border: 2px solid var(--ls-secondary-background-color);
   top: 2px;
-  transition-delay: 0;
 
   .tie {
     transform: translateY(-100%);
   }
 
-  &[data-active='false']:hover:not([data-recently-changed='true']) {
-    .tie {
-      transform: translateY(0);
-
-      &:first-of-type {
-        opacity: 0.6;
-      }
-    }
-  }
-
-  &[data-active='true'] {
-    background-color: var(--ls-active-primary-color);
-    color: var(--ls-block-highlight-color);
-    border: 2px solid var(--ls-active-primary-color);
-
-    .tie {
-      transform: translateY(0);
-
-      &:last-of-type {
-        opacity: 0.6;
-      }
-    }
-
-    &:hover:not([data-recently-changed='true']) {
-      color: var(--ls-primary-text-color);
-      background-color: var(--ls-secondary-background-color);
-
-      .tie {
-        transform: translateY(-100%);
-      }
-    }
-  }
-
   .tl-circle-button-icons-wrapper {
     @apply flex flex-col;
   }
-
-  i.tie {
-    transition: transform 0.2s ease-in-out;
-    transition-delay: 0;
-  }
-
-  .tl-circle-button-icons-wrapper[data-icons-count='2'] {
-    position: relative;
-    width: 22px;
-    height: 22px;
-    overflow: hidden;
-  }
 }
 
 .tl-quick-search-input-container {

+ 8 - 0
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -442,4 +442,12 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     this.app.persist()
     this.app.setSelectedShapes(clones)
   }
+
+  setCollapsed = (collapsed: boolean, shapes: S[] = this.app.allSelectedShapesArray) => {
+    shapes.forEach(shape => {
+      if (shape.props.type === 'logseq-portal')
+        shape.setCollapsed(collapsed)
+    })
+    this.app.persist()
+  }
 }

+ 8 - 0
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -116,6 +116,14 @@ export class TLApp<
         keys: 'shift+2',
         fn: () => this.api.zoomToSelection(),
       },
+      {
+        keys: 'mod+up',
+        fn: () => this.api.setCollapsed(true),
+      },
+      {
+        keys: 'mod+down',
+        fn: () => this.api.setCollapsed(false),
+      },
       {
         keys: 'mod+-',
         fn: () => this.api.zoomOut(),