Переглянути джерело

feat (wip): lock elements

Konstantinos Kaloutas 2 роки тому
батько
коміт
865f0ac4e5
28 змінених файлів з 94 додано та 31 видалено
  1. 23 4
      tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx
  2. 1 0
      tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts
  3. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx
  4. 2 2
      tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx
  5. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx
  6. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx
  7. 2 2
      tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx
  8. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx
  9. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx
  10. 2 0
      tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx
  11. 1 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/LogseqPortalShape.tsx
  12. 1 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/PenShape.tsx
  13. 1 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/PencilShape.tsx
  14. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx
  15. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx
  16. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx
  17. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/VideoShape.tsx
  18. 2 1
      tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx
  19. 4 0
      tldraw/apps/tldraw-logseq/src/styles.css
  20. 4 2
      tldraw/packages/core/src/lib/TLApi/TLApi.ts
  21. 19 2
      tldraw/packages/core/src/lib/TLApp/TLApp.ts
  22. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/HoveringSelectionHandleState.ts
  23. 1 1
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/IdleState.ts
  24. 1 0
      tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingSelectedShapeState.ts
  25. 2 1
      tldraw/packages/react/src/components/Canvas/Canvas.tsx
  26. 4 1
      tldraw/packages/react/src/components/Indicator/Indicator.tsx
  27. 4 2
      tldraw/packages/react/src/components/ui/SelectionForeground/SelectionForeground.tsx
  28. 1 0
      tldraw/packages/react/src/lib/TLReactShape.tsx

+ 23 - 4
tldraw/apps/tldraw-logseq/src/components/ContextMenu/ContextMenu.tsx

@@ -56,7 +56,7 @@ export const ContextMenu = observer(function ContextMenu({
         tabIndex={-1}
       >
         <div>
-          {app.selectedShapes?.size > 1 && !app.readOnly && (
+          {app.selectedShapes?.size > 1 && !app.readOnly && app.selectedShapesArray?.some(s => !s.props.isLocked) && (
             <>
               <ReactContextMenu.Item>
                 <div className="tl-menu-button-row pb-0">
@@ -145,6 +145,7 @@ export const ContextMenu = observer(function ContextMenu({
           )}
           {(app.selectedShapesArray.some(s => s.type === 'group' || app.getParentGroup(s)) ||
             app.selectedShapesArray.length > 1) &&
+            app.selectedShapesArray?.some(s => !s.props.isLocked) &&
             !app.readOnly && (
               <>
                 {app.selectedShapesArray.some(s => s.type === 'group' || app.getParentGroup(s)) && (
@@ -161,7 +162,7 @@ export const ContextMenu = observer(function ContextMenu({
                     </div>
                   </ReactContextMenu.Item>
                 )}
-                {app.selectedShapesArray.length > 1 && (
+                {app.selectedShapesArray.length > 1 && app.selectedShapesArray?.some(s => !s.props.isLocked) && (
                   <ReactContextMenu.Item
                     className="tl-menu-item"
                     onClick={() => runAndTransition(app.api.doGroup)}
@@ -178,7 +179,7 @@ export const ContextMenu = observer(function ContextMenu({
                 <ReactContextMenu.Separator className="menu-separator" />
               </>
             )}
-          {app.selectedShapes?.size > 0 && (
+          {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => !s.props.isLocked) && (
             <>
               {!app.readOnly && (
                 <ReactContextMenu.Item
@@ -276,7 +277,25 @@ export const ContextMenu = observer(function ContextMenu({
               Deselect all
             </ReactContextMenu.Item>
           )}
-          {app.selectedShapes?.size > 0 && !app.readOnly && (
+          {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => !s.props.isLocked) && (
+            <ReactContextMenu.Item
+              className="tl-menu-item"
+              onClick={() => runAndTransition(() => app.setLock(true))}
+            >
+              <TablerIcon className="tl-menu-icon" name="lock" />
+              Lock
+            </ReactContextMenu.Item>
+          )}
+          {app.selectedShapes?.size > 0 && app.selectedShapesArray?.some(s => s.props.isLocked) && (
+            <ReactContextMenu.Item
+              className="tl-menu-item"
+              onClick={() => runAndTransition(() => app.setLock(false))}
+            >
+              <TablerIcon className="tl-menu-icon" name="lock-open" />
+              Unlock
+            </ReactContextMenu.Item>
+          )}
+          {app.selectedShapes?.size > 0 && !app.readOnly && app.selectedShapesArray?.some(s => !s.props.isLocked) && (
             <>
               <ReactContextMenu.Item
                 className="tl-menu-item"

+ 1 - 0
tldraw/apps/tldraw-logseq/src/hooks/usePaste.ts

@@ -399,6 +399,7 @@ const handleCreatingShapes = async (
     return {
       ...shape,
       parentId: app.currentPageId,
+      isLocked: false,
       id: validUUID(shape.id) ? shape.id : uniqueId(),
     }
   })

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/BoxShape.tsx

@@ -166,12 +166,13 @@ export class BoxShape extends TLBoxShape<BoxShapeProps> {
       props: {
         size: [w, h],
         borderRadius,
+        isLocked,
       },
     } = this
 
     return (
       <g>
-        <rect width={w} height={h} rx={borderRadius} ry={borderRadius} fill="transparent" />
+        <rect width={w} height={h} rx={borderRadius} ry={borderRadius} fill="transparent" strokeDasharray={isLocked ? '8 2' : undefined} />
       </g>
     )
   })

+ 2 - 2
tldraw/apps/tldraw-logseq/src/lib/shapes/DotShape.tsx

@@ -43,8 +43,8 @@ export class DotShape extends TLDotShape<DotShapeProps> {
   })
 
   ReactIndicator = observer(() => {
-    const { radius } = this.props
-    return <circle cx={radius} cy={radius} r={radius} pointerEvents="all" />
+    const { radius, isLocked } = this.props
+    return <circle cx={radius} cy={radius} r={radius} pointerEvents="all" strokeDasharray={isLocked ? "8 2" : "undefined"} />
   })
 
   validateProps = (props: Partial<DotShapeProps>) => {

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/EllipseShape.tsx

@@ -154,11 +154,12 @@ export class EllipseShape extends TLEllipseShape<EllipseShapeProps> {
   ReactIndicator = observer(() => {
     const {
       size: [w, h],
+      isLocked,
     } = this.props
 
     return (
       <g>
-        <ellipse cx={w / 2} cy={h / 2} rx={w / 2} ry={h / 2} strokeWidth={2} fill="transparent" />
+        <ellipse cx={w / 2} cy={h / 2} rx={w / 2} ry={h / 2} strokeWidth={2} fill="transparent" strokeDasharray={isLocked ? "8 2" : "undefined"}/>
       </g>
     )
   })

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/HTMLShape.tsx

@@ -136,9 +136,10 @@ export class HTMLShape extends TLBoxShape<HTMLShapeProps> {
     const {
       props: {
         size: [w, h],
+        isLocked,
       },
     } = this
-    return <rect width={w} height={h} fill="transparent" />
+    return <rect width={w} height={h} fill="transparent"  strokeDasharray={isLocked ? "8 2" : "undefined"}/>
   })
 
   validateProps = (props: Partial<HTMLShapeProps>) => {

+ 2 - 2
tldraw/apps/tldraw-logseq/src/lib/shapes/HighlighterShape.tsx

@@ -83,8 +83,8 @@ export class HighlighterShape extends TLDrawShape<HighlighterShapeProps> {
   }
 
   ReactIndicator = observer(() => {
-    const { pointsPath } = this
-    return <path d={pointsPath} fill="none" />
+    const { pointsPath, props } = this
+    return <path d={pointsPath} fill="none"  strokeDasharray={props.isLocked ? "8 2" : "undefined"}/>
   })
 
   validateProps = (props: Partial<HighlighterShapeProps>) => {

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/IFrameShape.tsx

@@ -82,8 +82,9 @@ export class IFrameShape extends TLBoxShape<IFrameShapeProps> {
     const {
       props: {
         size: [w, h],
+        isLocked,
       },
     } = this
-    return <rect width={w} height={h} fill="transparent" rx={8} ry={8} />
+    return <rect width={w} height={h} fill="transparent" rx={8} ry={8}  strokeDasharray={isLocked ? "8 2" : "undefined"}/>
   })
 }

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/ImageShape.tsx

@@ -74,9 +74,10 @@ export class ImageShape extends TLImageShape<ImageShapeProps> {
     const {
       props: {
         size: [w, h],
+        isLocked,
       },
     } = this
-    return <rect width={w} height={h} fill="transparent" />
+    return <rect width={w} height={h} fill="transparent" strokeDasharray={isLocked ? "8 2" : "undefined"}/>
   })
 
   getShapeSVGJsx({ assets }: { assets: TLAsset[] }) {

+ 2 - 0
tldraw/apps/tldraw-logseq/src/lib/shapes/LineShape.tsx

@@ -146,6 +146,7 @@ export class LineShape extends TLLineShape<LineShapeProps> {
       fontSize,
       fontWeight,
       handles: { start, end },
+      isLocked,
     } = this.props
     const bounds = this.getBounds()
     const labelSize =
@@ -176,6 +177,7 @@ export class LineShape extends TLLineShape<LineShapeProps> {
             decorations?.start,
             decorations?.end
           )}
+          strokeDasharray={isLocked ? "8 2" : "undefined"}
         />
         {label && !isEditing && (
           <rect

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

@@ -536,7 +536,7 @@ export class LogseqPortalShape extends TLBoxShape<LogseqPortalShapeProps> {
 
   ReactIndicator = observer(() => {
     const bounds = this.getBounds()
-    return <rect width={bounds.width} height={bounds.height} fill="transparent" rx={8} ry={8} />
+    return <rect width={bounds.width} height={bounds.height} fill="transparent" rx={8} ry={8}  strokeDasharray={this.props.isLocked ? "8 2" : "undefined"}/>
   })
 
   validateProps = (props: Partial<LogseqPortalShapeProps>) => {

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

@@ -66,7 +66,7 @@ export class PenShape extends TLDrawShape<PenShapeProps> {
 
   ReactIndicator = observer(() => {
     const { pointsPath } = this
-    return <path d={pointsPath} />
+    return <path d={pointsPath} strokeDasharray={this.props.isLocked ? "8 2" : "undefined"}/>
   })
 
   validateProps = (props: Partial<PenShapeProps>) => {

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

@@ -133,7 +133,7 @@ export class PencilShape extends TLDrawShape<PencilShapeProps> {
 
   ReactIndicator = observer(() => {
     const { pointsPath } = this
-    return <path d={pointsPath} />
+    return <path d={pointsPath} strokeDasharray={this.props.isLocked ? "8 2" : "undefined"}/>
   })
 
   validateProps = (props: Partial<PencilShapeProps>) => {

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/PolygonShape.tsx

@@ -168,7 +168,7 @@ export class PolygonShape extends TLPolygonShape<PolygonShapeProps> {
   ReactIndicator = observer(() => {
     const {
       offset: [x, y],
-      props: { strokeWidth },
+      props: { strokeWidth, isLocked },
     } = this
 
     return (
@@ -176,6 +176,7 @@ export class PolygonShape extends TLPolygonShape<PolygonShapeProps> {
         <polygon
           transform={`translate(${x}, ${y})`}
           points={this.getVertices(strokeWidth / 2).join()}
+          strokeDasharray={isLocked ? "8 2" : "undefined"}
         />
       </g>
     )

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/TextShape.tsx

@@ -240,7 +240,7 @@ export class TextShape extends TLTextShape<TextShapeProps> {
 
   ReactIndicator = observer(({ isEditing }: TLComponentProps) => {
     const {
-      props: { borderRadius },
+      props: { borderRadius, isLocked },
       bounds,
     } = this
     return isEditing ? null : (
@@ -250,6 +250,7 @@ export class TextShape extends TLTextShape<TextShapeProps> {
         rx={borderRadius}
         ry={borderRadius}
         fill="transparent"
+        strokeDasharray={isLocked ? "8 2" : "undefined"}
       />
     )
   })

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/TweetShape.tsx

@@ -103,9 +103,10 @@ export class TweetShape extends TLBoxShape<TweetShapeProps> {
     const {
       props: {
         size: [w, h],
+        isLocked,
       },
     } = this
-    return <rect width={w} height={h} fill="transparent" rx={8} ry={8} />
+    return <rect width={w} height={h} fill="transparent" rx={8} ry={8} strokeDasharray={isLocked ? "8 2" : "undefined"}/>
   })
 
   useComponentSize<T extends HTMLElement>(ref: React.RefObject<T> | null, selector = '') {

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/VideoShape.tsx

@@ -90,8 +90,9 @@ export class VideoShape extends TLBoxShape<VideoShapeProps> {
     const {
       props: {
         size: [w, h],
+        isLocked,
       },
     } = this
-    return <rect width={w} height={h} fill="transparent" />
+    return <rect width={w} height={h} fill="transparent" strokeDasharray={isLocked ? "8 2" : "undefined"}/>
   })
 }

+ 2 - 1
tldraw/apps/tldraw-logseq/src/lib/shapes/YouTubeShape.tsx

@@ -119,9 +119,10 @@ export class YouTubeShape extends TLBoxShape<YouTubeShapeProps> {
     const {
       props: {
         size: [w, h],
+        isLocked,
       },
     } = this
-    return <rect width={w} height={h} fill="transparent" rx={8} ry={8} />
+    return <rect width={w} height={h} fill="transparent" rx={8} ry={8} strokeDasharray={isLocked ? "8 2" : "undefined"} />
   })
 
   validateProps = (props: Partial<YouTubeShapeProps>) => {

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

@@ -1214,6 +1214,10 @@ button.tl-shape-links-panel-item-remove-button {
   fill: var(--ls-secondary-background-color);
 }
 
+.tl-locked {
+  stroke-dasharray: calc(3px * var(--tl-scale)) calc(3px * var(--tl-scale));
+}
+
 @keyframes fadeIn {
   from {
     opacity: 0;

+ 4 - 2
tldraw/packages/core/src/lib/TLApi/TLApi.ts

@@ -11,8 +11,10 @@ export class TLApi<S extends TLShape = TLShape, K extends TLEventMap = TLEventMa
     this.app = app
   }
 
-  editShape = (shape: string | S | undefined): this => {
-    this.app.transition('select').selectedTool.transition('editingShape', { shape })
+  editShape = (shape: S | undefined): this => {
+    if (!shape?.props.isLocked)
+      this.app.transition('select').selectedTool.transition('editingShape', { shape })
+
     return this
   }
 

+ 19 - 2
tldraw/packages/core/src/lib/TLApp/TLApp.ts

@@ -336,9 +336,13 @@ export class TLApp<
   }
 
   @action updateShapes = <T extends S>(shapes: ({ id: string } & Partial<T['props']>)[]): this => {
-    if (this.readOnly) return this
+    if (this.readOnly ) return this
+
+    const shapesToUpdate = shapes.map(shape => this.getShapeById(shape.id)).filter(shape => shape?.props.isLocked)
+
+    if (shapesToUpdate.length === 0) return this
 
-    shapes.forEach(shape => this.getShapeById(shape.id)?.update(shape))
+    shapesToUpdate.forEach(shape => shape?.update(shape))
     this.persist()
     return this
   }
@@ -348,6 +352,7 @@ export class TLApp<
     const normalizedShapes: S[] = shapes
       .map(shape => (typeof shape === 'string' ? this.getShapeById(shape) : shape))
       .filter(isNonNullable)
+      .filter(s => !s.props.isLocked)
 
     // delete a group shape should also delete its children
     const shapesInGroups = this.shapesInGroups(normalizedShapes)
@@ -381,6 +386,7 @@ export class TLApp<
     }
 
     this.currentPage.shapes
+      .filter(s => !s.props.isLocked)
       .flatMap(s => Object.values(s.props.handles ?? {}))
       .flatMap(h => h.bindingId)
       .filter(isNonNullable)
@@ -519,6 +525,17 @@ export class TLApp<
     return this
   }
 
+  setLock = (isLocked: boolean): this => {
+    if (this.selectedShapesArray.length === 0 || this.readOnly) return this
+
+    this.selectedShapesArray.forEach(shape => {
+      shape.update({ isLocked })
+    })
+
+    this.persist()
+    return this
+  }
+
   /* --------------------- Assets --------------------- */
 
   @observable assets: Record<string, TLAsset> = {}

+ 1 - 1
tldraw/packages/core/src/lib/tools/TLSelectTool/states/HoveringSelectionHandleState.ts

@@ -74,7 +74,7 @@ export class HoveringSelectionHandleState<
     if (!isSingle) return
     const selectedShape = getFirstFromSet(this.app.selectedShapes)
 
-    if (selectedShape.canEdit && !this.app.readOnly) {
+    if (selectedShape.canEdit && !this.app.readOnly && !selectedShape.props.isLocked) {
       switch (info.type) {
         case TLTargetType.Shape: {
           this.tool.transition('editingShape', info)

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

@@ -124,7 +124,7 @@ export class IdleState<
     if (info.order || this.app.selectedShapesArray.length !== 1 || this.app.readOnly) return
 
     const selectedShape = this.app.selectedShapesArray[0]
-    if (!selectedShape.canEdit) return
+    if (!selectedShape.canEdit || selectedShape.props.isLocked) return
 
     switch (info.type) {
       case TLTargetType.Shape: {

+ 1 - 0
tldraw/packages/core/src/lib/tools/TLSelectTool/states/PointingSelectedShapeState.ts

@@ -49,6 +49,7 @@ export class PointingSelectedShapeState<
       selectedShapesArray.length === 1 &&
       this.pointedSelectedShape.canEdit &&
       !this.app.readOnly &&
+      !this.pointedSelectedShape.props.isLocked &&
       this.pointedSelectedShape instanceof TLBoxShape &&
       PointUtils.pointInBounds(currentPoint, this.pointedSelectedShape.bounds)
     ) {

+ 2 - 1
tldraw/packages/react/src/components/Canvas/Canvas.tsx

@@ -168,6 +168,7 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
                 isHovered={false}
                 isBinding={false}
                 isSelected={true}
+                isLocked={shape.props.isLocked}
               />
             ))}
           {hoveredShapes.map(
@@ -194,7 +195,7 @@ export const Canvas = observer(function Renderer<S extends TLReactShape>({
                   zIndex={editingShape && selectedShapes.includes(editingShape) ? 1002 : 10002}
                 >
                   <components.SelectionForeground
-                    shapes={selectedShapes}
+                    shapes={selectedShapes.filter(shape => !shape.props.isLocked)}
                     bounds={selectionBounds}
                     showResizeHandles={showResizeHandles}
                     showRotateHandles={showRotateHandles}

+ 4 - 1
tldraw/packages/react/src/components/Indicator/Indicator.tsx

@@ -10,6 +10,7 @@ interface IndicatorProps {
   isSelected?: boolean
   isBinding?: boolean
   isEditing?: boolean
+  isLocked?: boolean
   meta?: any
 }
 
@@ -19,6 +20,7 @@ export const Indicator = observer(function Shape({
   isSelected = false,
   isBinding = false,
   isEditing = false,
+  isLocked = false,
   meta,
 }: IndicatorProps) {
   const {
@@ -38,11 +40,12 @@ export const Indicator = observer(function Shape({
       zIndex={isEditing ? 1000 : 10000}
     >
       <SVGContainer>
-        <g className={`tl-indicator-container ${isSelected ? 'tl-selected' : 'tl-hovered'}`}>
+        <g className={`tl-indicator-container ${isSelected ? 'tl-selected' : 'tl-hovered'} ${isLocked ? 'tl-locked' : ''}`}>
           <ReactIndicator
             isEditing={isEditing}
             isBinding={isBinding}
             isHovered={isHovered}
+            isLocked={isLocked}
             isSelected={isSelected}
             isErasing={false}
             meta={meta}

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

@@ -26,7 +26,8 @@ export const SelectionForeground = observer(function SelectionForeground<S exten
   const borderRadius = app.editingShape?.props['borderRadius'] ?? 0
 
   return (
-    <SVGContainer>
+    <>
+    {shapes.length > 0 && <SVGContainer>
       {!app.editingShape && (<rect
         className="tl-bounds-fg"
         width={Math.max(width, 1)}
@@ -139,6 +140,7 @@ export const SelectionForeground = observer(function SelectionForeground<S exten
           />
         </>
       )}
-    </SVGContainer>
+    </SVGContainer>}
+    </>
   )
 })

+ 1 - 0
tldraw/packages/react/src/lib/TLReactShape.tsx

@@ -8,6 +8,7 @@ export interface TLCommonShapeProps<M = unknown> {
   isHovered: boolean
   isSelected: boolean
   isErasing: boolean
+  isLocked: boolean
   asset?: TLAsset
 }