浏览代码

feat: add sizing controls to logseq portal shape

Peng Xiao 3 年之前
父节点
当前提交
d1eab6b4b0

+ 2 - 1
tldraw/apps/tldraw-logseq/package.json

@@ -31,7 +31,8 @@
     "typescript": "^4.7.3",
     "zx": "^6.2.4",
     "@radix-ui/react-switch": "^1.0.0",
-    "@radix-ui/react-dropdown-menu": "^1.0.0"
+    "@radix-ui/react-dropdown-menu": "^1.0.0",
+    "@radix-ui/react-select": "^1.0.0"
   },
   "peerDependencies": {
     "react": "^16.8.0 || ^17.0.0 || ^18.0.0",

+ 49 - 8
tldraw/apps/tldraw-logseq/src/components/inputs/SelectInput.tsx

@@ -1,16 +1,57 @@
 import * as React from 'react'
+import * as Select from '@radix-ui/react-select'
+import { TablerIcon } from '~components/icons'
 
-interface ColorInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
-  label: string
+export interface SelectOption {
+  value: string
+  label: React.ReactNode
 }
 
-export function ColorInput({ label, ...rest }: ColorInputProps) {
+interface SelectInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
+  options: SelectOption[]
+  value: string
+  onValueChange: (value: string) => void
+}
+
+export function SelectInput({ options, value, onValueChange, ...rest }: SelectInputProps) {
+  const [isOpen, setIsOpen] = React.useState(false)
   return (
-    <div className="input">
-      <label htmlFor={`color-${label}`}>{label}</label>
-      <div className="color-input-wrapper">
-        <input className="color-input" name={`color-${label}`} type="color" {...rest} />
-      </div>
+    <div {...rest} className="tl-select-input">
+      <Select.Root
+        open={isOpen}
+        onOpenChange={setIsOpen}
+        value={value}
+        onValueChange={onValueChange}
+      >
+        <Select.Trigger className="tl-select-input-trigger">
+          <div className="tl-select-input-trigger-value">
+            <Select.Value />
+          </div>
+          <Select.Icon>
+            <TablerIcon name={isOpen ? 'chevron-up' : 'chevron-down'} />
+          </Select.Icon>
+        </Select.Trigger>
+
+        <Select.Portal className="tl-select-input-portal">
+          <Select.Content className="tl-select-input-content">
+            <Select.ScrollUpButton />
+            <Select.Viewport className="tl-select-input-viewport">
+              {options.map(option => {
+                return (
+                  <Select.Item
+                    className="tl-select-input-select-item"
+                    key={option.value}
+                    value={option.value}
+                  >
+                    <Select.ItemText>{option.label}</Select.ItemText>
+                  </Select.Item>
+                )
+              })}
+            </Select.Viewport>
+            <Select.ScrollDownButton />
+          </Select.Content>
+        </Select.Portal>
+      </Select.Root>
     </div>
   )
 }

+ 4 - 5
tldraw/apps/tldraw-logseq/src/components/inputs/SwitchInput.tsx

@@ -1,18 +1,17 @@
-import * as React from 'react'
 import * as Switch from '@radix-ui/react-switch'
 interface SwitchInputProps extends React.InputHTMLAttributes<HTMLInputElement> {
   label: string
   onCheckedChange: (checked: boolean) => void
 }
 
-export function SwitchInput({ label, ...rest }: SwitchInputProps) {
+export function SwitchInput({ label, onCheckedChange, checked, ...rest }: SwitchInputProps) {
   return (
-    <div className="input">
+    <div {...rest} className="input">
       <label htmlFor={`switch-${label}`}>{label}</label>
       <Switch.Root
         className="switch-input-root"
-        checked={rest.checked}
-        onCheckedChange={rest.onCheckedChange}
+        checked={checked}
+        onCheckedChange={onCheckedChange}
       >
         <Switch.Thumb className="switch-input-thumb" />
       </Switch.Root>

+ 58 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx

@@ -6,6 +6,8 @@ import { makeObservable, runInAction } from 'mobx'
 import { observer } from 'mobx-react-lite'
 import * as React from 'react'
 import { TablerIcon } from '~components/icons'
+import { NumberInput } from '~components/inputs/NumberInput'
+import { SelectInput, SelectOption } from '~components/inputs/SelectInput'
 import { SwitchInput } from '~components/inputs/SwitchInput'
 import { useCameraMovingRef } from '~hooks/useCameraMoving'
 import type { Shape } from '~lib'
@@ -21,12 +23,49 @@ export interface LogseqPortalShapeProps extends TLBoxShapeProps, CustomStyleProp
   collapsed?: boolean
   compact?: boolean
   collapsedHeight?: number
+  scaleLevel?: 'sm' | 'md' | 'lg'
 }
 
 interface LogseqQuickSearchProps {
   onChange: (id: string) => void
 }
 
+const sizeOptions: SelectOption[] = [
+  {
+    label: 'Extra Small',
+    value: 'xs',
+  },
+  {
+    label: 'Small',
+    value: 'sm',
+  },
+  {
+    label: 'Medium',
+    value: 'md',
+  },
+  {
+    label: 'Large',
+    value: 'lg',
+  },
+  {
+    label: 'Extra Large',
+    value: 'xl',
+  },
+  {
+    label: '2 Extra Large',
+    value: 'xxl',
+  },
+]
+
+const sizeToScale = {
+  xs: 0.5,
+  sm: 0.8,
+  md: 1,
+  lg: 1.5,
+  xl: 2,
+  xxl: 3,
+}
+
 const LogseqTypeTag = ({
   type,
   active,
@@ -123,6 +162,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     pageId: '',
     collapsed: false,
     compact: false,
+    scaleLevel: 'md',
   }
 
   hideRotateHandle = true
@@ -188,6 +228,16 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
     const app = useApp<Shape>()
     return (
       <>
+        <SelectInput
+          options={sizeOptions}
+          value={this.props.scaleLevel ?? 'md'}
+          onValueChange={v => {
+            this.update({
+              scaleLevel: v,
+            })
+            app.persist()
+          }}
+        />
         {this.props.blockType !== 'B' && (
           <SwitchInput
             label="Collapsed"
@@ -618,6 +668,9 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
         ref={cpRefContainer}
         className="tl-logseq-cp-container"
         style={{
+          width: 'calc(100% / var(--ls-portal-scale))',
+          height: 'calc((100% - var(--ls-header-height)) / var(--ls-portal-scale))',
+          transform: `scale(var(--ls-portal-scale))`,
           overflow: this.props.compact ? 'visible' : 'auto',
         }}
       >
@@ -633,7 +686,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
   ReactComponent = observer((componentProps: TLComponentProps) => {
     const { events, isErasing, isEditing, isBinding } = componentProps
     const {
-      props: { opacity, pageId, stroke, fill },
+      props: { opacity, pageId, stroke, fill, scaleLevel },
     } = this
 
     const app = useApp<Shape>()
@@ -656,6 +709,8 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
       [tlEventsEnabled]
     )
 
+    const scaleRatio = sizeToScale[scaleLevel ?? 'md']
+
     // It is a bit weird to update shapes here. Is there a better place?
     React.useEffect(() => {
       if (this.props.collapsed && isEditing) {
@@ -740,6 +795,8 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
                 '--ls-primary-background-color': !fill?.startsWith('var') ? fill : undefined,
                 '--ls-primary-text-color': !stroke?.startsWith('var') ? stroke : undefined,
                 '--ls-title-text-color': !stroke?.startsWith('var') ? stroke : undefined,
+                '--ls-portal-scale': scaleRatio,
+                '--ls-header-height': this.getHeaderHeight() + 'px'
               }}
             >
               {!this.props.compact && !targetNotFound && (

+ 43 - 0
tldraw/apps/tldraw-logseq/src/styles.css

@@ -248,6 +248,45 @@
   pointer-events: all;
 }
 
+.tl-select-input-trigger {
+  @apply flex items-center;
+  border: 1px solid var(--ls-secondary-background-color);
+  background-color: var(--ls-tertiary-background-color);
+  min-width: 160px;
+  border-radius: 8px;
+  padding: 0 12px;
+  font-size: 16px;
+  height: 100%;
+}
+
+.tl-select-input-trigger-value {
+  @apply flex items-center items-start flex-1;
+}
+
+.tl-select-input-viewport {
+  border-radius: 8px;
+  background-color: var(--ls-secondary-background-color);
+}
+
+.tl-select-input-select-item {
+  cursor: default;
+  padding: 4px 12px;
+
+  &[data-state='unchecked']:hover {
+    background-color: var(--ls-tertiary-background-color);
+    color: var(--ls-primary-text-color);
+  }
+
+  &[data-state='checked'] {
+    background-color: #4285f4;
+    color: #fff;
+  }
+}
+
+.tl-select-input-content {
+  z-index: 100;
+}
+
 .tl-geometry-tools-pane-anchor {
   @apply relative;
 }
@@ -601,9 +640,13 @@
 .tl-logseq-cp-container {
   @apply h-full w-full rounded-lg;
 
+  top: var(--ls-header-height);
+  left: 0;
   overscroll-behavior: none;
   flex: 1 1 0%;
   cursor: default;
+  transform-origin: top left;
+  position: absolute;
 }
 
 .tl-highlighted {

+ 0 - 1
tldraw/packages/core/src/lib/shapes/TLShape/TLShape.tsx

@@ -41,7 +41,6 @@ export interface TLShapeProps {
   isGenerated?: boolean
   isSizeLocked?: boolean
   isAspectRatioLocked?: boolean
-  logseqLink?: string
 }
 
 export interface TLResizeStartInfo {

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

@@ -90,7 +90,6 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
 }: Partial<TLCanvasProps<S>>) {
   const rContainer = React.useRef<HTMLDivElement>(null)
   const { viewport, components, meta } = useRendererContext()
-  const { zoom } = viewport.camera
   const app = useApp()
   const onBoundsChange = React.useCallback((bounds: TLBounds) => {
     app.inputs.updateContainerOffset([bounds.minX, bounds.minY])
@@ -117,7 +116,6 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
           {components.SelectionBackground && selectedShapes && selectionBounds && showSelection && (
             <Container data-type="SelectionBackground" bounds={selectionBounds} zIndex={2}>
               <components.SelectionBackground
-                zoom={zoom}
                 shapes={selectedShapes}
                 bounds={selectionBounds}
                 showResizeHandles={showResizeHandles}
@@ -164,7 +162,6 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
                   zIndex={editingShape && selectedShapes.includes(editingShape) ? 1002 : 10002}
                 >
                   <components.SelectionForeground
-                    zoom={zoom}
                     shapes={selectedShapes}
                     bounds={selectionBounds}
                     showResizeHandles={showResizeHandles}

+ 0 - 1
tldraw/packages/react/src/components/ui/SelectionBackground/SelectionBackground.tsx

@@ -1,4 +1,3 @@
-import * as React from 'react'
 import { observer } from 'mobx-react-lite'
 import { useBoundsEvents } from '~hooks/useBoundsEvents'
 import { SVGContainer } from '~components'

+ 4 - 5
tldraw/packages/react/src/components/ui/SelectionForeground/SelectionForeground.tsx

@@ -1,19 +1,21 @@
 import { TLResizeCorner, TLResizeEdge, TLRotateCorner } from '@tldraw/core'
 import { observer } from 'mobx-react-lite'
 import { SVGContainer } from '~components'
+import { useApp } from '~hooks'
 import type { TLReactShape } from '~lib'
 import type { TLSelectionComponentProps } from '~types'
-import { CornerHandle, EdgeHandle, RotateHandle } from './handles'
+import { CornerHandle, EdgeHandle } from './handles'
 import { RotateCornerHandle } from './handles/RotateCornerHandle'
 
 export const SelectionForeground = observer(function SelectionForeground<S extends TLReactShape>({
   bounds,
-  zoom,
   showResizeHandles,
   showRotateHandles,
   shapes,
 }: TLSelectionComponentProps<S>) {
+  const app = useApp()
   const { width, height } = bounds
+  const zoom = app.viewport.camera.zoom
 
   const size = 8 / zoom
   const targetSize = 6 / zoom
@@ -132,9 +134,6 @@ export const SelectionForeground = observer(function SelectionForeground<S exten
           />
         </>
       )}
-      {/* {showRotateHandles && (
-        <RotateHandle cx={width / 2} cy={0 - targetSize * 2} size={size} targetSize={targetSize} />
-      )} */}
     </SVGContainer>
   )
 })

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

@@ -6,7 +6,6 @@ import type { TLReactShape } from '~lib'
 /* ------------------- Components ------------------- */
 
 export type TLSelectionComponentProps<S extends TLReactShape = TLReactShape> = {
-  zoom: number
   shapes: S[]
   bounds: TLBounds
   showResizeHandles?: boolean

+ 43 - 0
tldraw/yarn.lock

@@ -1782,6 +1782,13 @@
     node-addon-api "^3.2.1"
     node-gyp-build "^4.3.0"
 
+"@radix-ui/[email protected]":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.0.tgz#4c536161d0de750b3f5d55860fc3de46264f897b"
+  integrity sha512-Ofwh/1HX69ZfJRiRBMTy7rgjAzHmwe4kW9C9Y99HTRUcYLUuVT0KESFj15rPjRgKJs20GPq8Bm5aEDJ8DuA3vA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+
 "@radix-ui/[email protected]":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.0.tgz#e1d8ef30b10ea10e69c76e896f608d9276352253"
@@ -1973,6 +1980,34 @@
     "@radix-ui/react-use-callback-ref" "1.0.0"
     "@radix-ui/react-use-controllable-state" "1.0.0"
 
+"@radix-ui/react-select@^1.0.0":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.0.0.tgz#16d91f493a18e471d40fc06b1faff516b5686784"
+  integrity sha512-GfbVBxx7JPvytNT+0XYCFCOImEMmpl0Vg7hhifLaLV7p5zRottFMgQ7qrrxw8v6B8buKXz41kWYMrdRrWqsZaQ==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@radix-ui/number" "1.0.0"
+    "@radix-ui/primitive" "1.0.0"
+    "@radix-ui/react-collection" "1.0.0"
+    "@radix-ui/react-compose-refs" "1.0.0"
+    "@radix-ui/react-context" "1.0.0"
+    "@radix-ui/react-direction" "1.0.0"
+    "@radix-ui/react-dismissable-layer" "1.0.0"
+    "@radix-ui/react-focus-guards" "1.0.0"
+    "@radix-ui/react-focus-scope" "1.0.0"
+    "@radix-ui/react-id" "1.0.0"
+    "@radix-ui/react-label" "1.0.0"
+    "@radix-ui/react-portal" "1.0.0"
+    "@radix-ui/react-primitive" "1.0.0"
+    "@radix-ui/react-slot" "1.0.0"
+    "@radix-ui/react-use-callback-ref" "1.0.0"
+    "@radix-ui/react-use-controllable-state" "1.0.0"
+    "@radix-ui/react-use-layout-effect" "1.0.0"
+    "@radix-ui/react-use-previous" "1.0.0"
+    "@radix-ui/react-visually-hidden" "1.0.0"
+    aria-hidden "^1.1.1"
+    react-remove-scroll "2.5.4"
+
 "@radix-ui/[email protected]":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.0.tgz#7fa805b99891dea1e862d8f8fbe07f4d6d0fd698"
@@ -2049,6 +2084,14 @@
     "@babel/runtime" "^7.13.10"
     "@radix-ui/react-use-layout-effect" "1.0.0"
 
+"@radix-ui/[email protected]":
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.0.tgz#4d69d7e3b6d21ee4678ed6de5215dcd068394401"
+  integrity sha512-MwAhMdX+n6S4InwRKSnpUsp+lLkYG6izQF56ul6guSX2mBBLOMV9Frx7xJlkEe2GjKLzbNuHhaCS6e5gopmZNA==
+  dependencies:
+    "@babel/runtime" "^7.13.10"
+    "@radix-ui/react-primitive" "1.0.0"
+
 "@radix-ui/[email protected]":
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.0.tgz#0dc8e6a829ea2828d53cbc94b81793ba6383bf3c"