Commit Graph

30 Commits

Author SHA1 Message Date
Saul-Mirone 76dc55f328 refactor(editor): remove legacy block service spec slots (#11013) 2025-03-20 01:04:18 +09:00
doouding 1c8d25bc29 feat: add ElementTransformManager for edgeless element basic manipulation (#10824)
### Overview:
We've been working with some legacy code in the default-tool and edgeless-selected-rect modules, which are responsible for fundamental operations like moving, resizing, and rotating elements. Currently, these operations are hardcoded, making it challenging to extend functionalities without diving deep into the code.

### What's Changing:
Introducing `ElementTransformManager` to streamline the handling of basic transformations (move, resize, rotate) while allowing the business logic to dictate when these actions occur.

Providing two ways to extend the transformations behaviour:
- Extends inside element view definition: Elements can decide how to handle move/resize events, such as enforcing size constraints.
- Extension mechanism provided by this manager: Adjust or completely override default drag behaviors, like snapping elements into alignment.

### Code Examples:
Delegate element movement to TransformManager:
```typescript
class DefaultTool {
  override dragStart(event) {
    if(this.dragType === DragType.ContentMoving) {
      const transformManager = this.std.get(TransformManagerIdentifier);
      transformManager.startDrag({ selectedElements, event });
    }
  }
}
```

 Enforce minimum width inside view definition:
```typescript
class EdgelessNoteBlock extends GfxBlockComponent {
  onResizeDelta({ dw, dh }) {
    const bound = this.model.elementBound;
    bound.w = Math.min(MAX_WIDTH, bound.w + dw);
    bound.h = Math.min(MAX_HEIGHT, bound.h + dh);
    this.model.xywh = bound.serialize();
  }
}
```

Use extension to implement element snapping:
```typescript
import { TransformerExtension } from '@blocksuite/std/gfx';

// Just extends the TransformerExtension
class SnapManager extends TransformerExtension {
  static override key = 'snap-manager';
  onDragInitialize() {
    return {
      onDragMove(context) {
        const { dx, dy } = this.getAlignmentMoveDistance(context.elements);
        context.dx = dx;
        context.dy = dy;
      }
    }
  }
}
```

### Others

The migration will be divided into several PRs. This PR mostly focus on refactoring elements movement part of `default-tool`.
- Delegate elements movement to `TransformManager`
- Rewrite the default tool extension into `TransformManager` extension
- Add drag handler interface to gfx view (both `GfxBlockComponent` and `GfxElementModelView`) to allow element to define how it gonna react on drag
2025-03-19 15:30:06 +00:00
Saul-Mirone a9b53839a6 refactor(editor): improve std structure (#10993) 2025-03-19 11:37:55 +00:00
Saul-Mirone 321e3449ec fix(editor): block can be null in widget (#10959)
Closes: [BS-2826](https://linear.app/affine-design/issue/BS-2826/typeerror-thisblock-is-null)
2025-03-18 10:58:19 +00:00
Saul-Mirone 3de7d85eea feat(editor): improve api for store, and add docs (#10941) 2025-03-17 16:30:59 +00:00
Mirone cd63e0ed8b feat(editor): replace slot with rxjs subject (#10768) 2025-03-12 11:29:24 +09:00
doodlewind 334912e85b perf(editor): lazy DOM update with idle state in gfx viewport (#10624)
Currently, `GfxViewportElement` hides DOM blocks outside the viewport using `display: none` to optimize performance. However, this approach presents two issues:

1. Even when hidden, all top-level blocks still undergo frequent CSS transform updates during viewport panning and zooming.
2. Hidden blocks cannot access DOM layout information, preventing `TurboRenderer` from updating the complete canvas bitmap.

To address this, this PR introduces a refactoring that divides all top-level edgeless blocks into two states: `idle` and `active`. The improvements are as follows:

1. Blocks outside the viewport are set to the `idle` state, meaning they no longer update their DOM during viewport panning or zooming. Only `active` blocks within the viewport are updated frame by frame.
2. For `idle` blocks, the hiding method switches from `display: none` to `visibility: hidden`, ensuring their layout information remains accessible to `TurboRenderer`.

[Screen Recording 2025-03-07 at 3.23.56 PM.mov <span class="graphite__hidden">(uploaded via Graphite)</span> <img class="graphite__hidden" src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/lEGcysB4lFTEbCwZ8jMv/4bac640b-f5b6-4b0b-904d-5899f96cf375.mov" />](https://app.graphite.dev/media/video/lEGcysB4lFTEbCwZ8jMv/4bac640b-f5b6-4b0b-904d-5899f96cf375.mov)

While this minimizes DOM updates, it introduces a trade-off: `idle` blocks retain an outdated layout state. Since their positions are updated using a lazy update strategy, their layout state remains frozen at the moment they were last moved out of the viewport:

![idle-issue.jpg](https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/9c8c2150-69d4-416b-b46e-8473a7fdf339.jpg)

To resolve this, the PR serializes and stores the viewport field of the block at that moment on the `idle` block itself. This allows the correct layout, positioned in the model coordinate system, to be restored from the stored data.
2025-03-08 01:38:02 +00:00
Saul-Mirone 2e86bfffae feat(editor): life cycle ext (#10668) 2025-03-06 12:28:55 +00:00
Saul-Mirone 84e2dda3f8 refactor(editor): separate lit and slot in global (#10666) 2025-03-06 10:24:59 +00:00
Saul-Mirone 66d9d576e0 refactor(editor): add gfx entry in bs global package (#10612) 2025-03-04 12:46:50 +00:00
doodlewind 22191caa82 fix(editor): safari compat for lit host (#10514)
Fixed this white screen crash on mobile safari:

<img src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/lEGcysB4lFTEbCwZ8jMv/00bffd51-8e06-4d37-88d4-6f5020cd503d.png" width="500">
2025-02-28 10:26:59 +00:00
Saul-Mirone 1c5e360d7e feat(editor): add widget in viewUpdated slot (#10452) 2025-02-26 13:24:32 +00:00
Saul-Mirone d111f8ac88 feat(editor): add WidgetViewExtension (#10180)
Closes: [BS-2282](https://linear.app/affine-design/issue/BS-2282/replace-widgetviewmapextension-with-widgetextension)
2025-02-14 11:00:01 +00:00
Saul-Mirone e7cc710f8e refactor(editor): remove service global type (#10129)
Closes: [BS-2566](https://linear.app/affine-design/issue/BS-2566/remove-global-types-in-service)
2025-02-12 11:46:10 +00:00
donteatfriedrice bd041cbfcf fix(editor): shadowless element should remove style element correctly (#10128)
[BS-2565](https://linear.app/affine-design/issue/BS-2565/关闭-chat-block-center-peek-后新生成的-chat-block-最后一个-message-样式不正确)
2025-02-12 10:38:07 +00:00
Saul-Mirone 39eb8625d6 refactor(editor): remove block models global type (#10086) 2025-02-11 11:00:57 +00:00
fundon ffd54c6620 refactor(editor): use selected signal in block component (#9849) 2025-01-27 02:56:10 +00:00
Saul-Mirone 092f9c84fa refactor(editor): remove deprecated dangerouslyRenderModel method (#9803) 2025-01-20 14:48:09 +00:00
doouding 99717196c5 refactor: rewrite blocksuite dnd (#9595)
### Changed

- Refactored BlockSuite drag-and-drop using @atlaskit/pragmatic-drag-and-drop/element/adapter.
- Updated block dragging to use the new drag-and-drop infrastructure.

### BlockSuite DND API

Access the BlockSuite drag-and-drop API via `std.dnd`. This is a lightweight wrapper around pragmatic-drag-and-drop, offering convenient generic types and more intuitive option names.

#### Drag payload structure
There's some constrain about drag payload. The whole drag payload looks like this:

```typescript
type DragPayload = {
  entity: {
    type: string
  },
  from: {
    at: 'blocksuite',
    docId: string
  }
}
```
- The `from` field is auto-generated—no need for manual handling.
- The `entity` field is customizable, but it must include a `type`.

All drag-and-drop methods accept a generic type for entity, ensuring more accurate payloads in event handlers.

```typescript
type BlockEntity = {
  type: 'blocks',
  blockIds: string[]
}

dnd.draggable<BlockEntity>({
  element: someElement,
  setDragData: () => {
    // the return type must satisfy the generic type
    // in this case, it's BlockEntity
    return {
      type: 'blocks',
      blockIds: []
    }
  }
});

dnd.monitor<BlockEntity>({
  // the arguments is same for other event handler
  onDrag({ source }) {
    // the type of this is BlockEntity
    source.data.entity
  }
})
```

#### Drop payload
When hover on droppable target. You can set drop payload as well. All drag-and-drop methods accept a second generic type for drop payload.

The drop payload is customizable. Additionally, the DND system will add an `edge` field to the final payload object, indicating the nearest edge of the drop target relative to the current drag position.

```typescript
type DropPayload = {
  blockId: string;
}

dnd.dropTarget<BlockEntity, DropPayload>({
  getData() {
    // the type should be DropPayload
    return {
      blockId: 'someId'
    }
  }
});

dnd.monitor<BlockEntity, DropPayload>({
  // drag over on drop target
  onDrag({ location }) {
    const target = location.current.dropTargets[0];

    // the type is DropPayload
    target.data;
    // retrieve the nearest edge of the drop target relative to the current drop position.
    target.data.edge;
  }
})
```
2025-01-16 12:36:58 +00:00
Saul-Mirone 126ab18967 feat(editor): selection as store extension (#9605) 2025-01-09 11:49:23 +00:00
Saul-Mirone d21ef47ae8 chore(editor): rename std.doc to std.store (#9596) 2025-01-09 04:16:28 +00:00
Saul-Mirone 5842d45ab1 feat(editor): merge store and blocks (#9591) 2025-01-08 13:01:19 +00:00
Saul-Mirone 3d168ba2d2 refactor(editor): reorg code structure of store package (#9525) 2025-01-05 12:49:02 +00:00
Saul-Mirone 4457cb7266 refactor(editor): rename doc to blocks (#9510) 2025-01-03 12:49:33 +00:00
doodlewind cbc84ff672 fix(editor): blur in edgeless content zooming (#9496)
Fix [BS-2294](https://linear.app/affine-design/issue/BS-2294/edgeless-%E7%BC%A9%E6%94%BE%E5%AD%97%E5%8F%B7%E6%A8%A1%E7%B3%8A)
2025-01-03 02:36:57 +00:00
doodlewind d4053a345e perf(editor): reduce per frame viewport dom ops (#9431)
Currently when panning / zooming on whiteboard, both position and transform of each block component are updated per frame. The positioning part lead to heavy layout costs, which can be reduced.

Before (~35fps):

![image](https://github.com/user-attachments/assets/3f6d8a76-26a5-4ed6-a64c-b519a453cbc0)

After (~50fps):

![image](https://github.com/user-attachments/assets/43bc4b0a-db01-4526-8400-2ec95c3bdd0b)

Tested environment: TibetTravel templet, Windows 11, i5-1130G7 1.1GHz
2024-12-30 10:50:33 +00:00
Saul-Mirone 2b27d62b0e refactor(editor): cleanup ts-expect-error (#9369) 2024-12-27 05:14:23 +00:00
Saul-Mirone 7c84545647 refactor(editor): cleanup dead code (#9300) 2024-12-25 07:48:00 +00:00
Saul-Mirone 3a82da0e5b chore: fix eslint in blocksuite (#9232) 2024-12-20 16:48:10 +00:00
Mirone 30200ff86d chore: merge blocksuite source code (#9213) 2024-12-20 15:38:06 +08:00