diff --git a/blocksuite/affine/all/package.json b/blocksuite/affine/all/package.json index 46dbd62c4..25b00bfe9 100644 --- a/blocksuite/affine/all/package.json +++ b/blocksuite/affine/all/package.json @@ -58,6 +58,7 @@ "@blocksuite/affine-shared": "workspace:*", "@blocksuite/affine-widget-drag-handle": "workspace:*", "@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*", + "@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*", "@blocksuite/affine-widget-edgeless-toolbar": "workspace:*", "@blocksuite/affine-widget-edgeless-zoom-toolbar": "workspace:*", "@blocksuite/affine-widget-frame-title": "workspace:*", diff --git a/blocksuite/affine/all/src/extensions/view.ts b/blocksuite/affine/all/src/extensions/view.ts index d13c67f06..7b7764b58 100644 --- a/blocksuite/affine/all/src/extensions/view.ts +++ b/blocksuite/affine/all/src/extensions/view.ts @@ -40,6 +40,7 @@ import { InlinePresetViewExtension } from '@blocksuite/affine-inline-preset/view import { ReferenceViewExtension } from '@blocksuite/affine-inline-reference/view'; import { DragHandleViewExtension } from '@blocksuite/affine-widget-drag-handle/view'; import { EdgelessAutoConnectViewExtension } from '@blocksuite/affine-widget-edgeless-auto-connect/view'; +import { EdgelessSelectedRectViewExtension } from '@blocksuite/affine-widget-edgeless-selected-rect/view'; import { EdgelessToolbarViewExtension } from '@blocksuite/affine-widget-edgeless-toolbar/view'; import { EdgelessZoomToolbarViewExtension } from '@blocksuite/affine-widget-edgeless-zoom-toolbar/view'; import { FrameTitleViewExtension } from '@blocksuite/affine-widget-frame-title/view'; @@ -112,6 +113,7 @@ export function getInternalViewExtensions() { ViewportOverlayViewExtension, EdgelessZoomToolbarViewExtension, PageDraggingAreaViewExtension, + EdgelessSelectedRectViewExtension, // Fragment DocTitleViewExtension, diff --git a/blocksuite/affine/all/src/widgets/edgeless-selected-rect/index.ts b/blocksuite/affine/all/src/widgets/edgeless-selected-rect/index.ts new file mode 100644 index 000000000..bcbd42094 --- /dev/null +++ b/blocksuite/affine/all/src/widgets/edgeless-selected-rect/index.ts @@ -0,0 +1 @@ +export * from '@blocksuite/affine-widget-edgeless-selected-rect'; diff --git a/blocksuite/affine/all/src/widgets/edgeless-selected-rect/view.ts b/blocksuite/affine/all/src/widgets/edgeless-selected-rect/view.ts new file mode 100644 index 000000000..0c0d767db --- /dev/null +++ b/blocksuite/affine/all/src/widgets/edgeless-selected-rect/view.ts @@ -0,0 +1 @@ +export * from '@blocksuite/affine-widget-edgeless-selected-rect/view'; diff --git a/blocksuite/affine/all/tsconfig.json b/blocksuite/affine/all/tsconfig.json index b3891bd89..26fb35298 100644 --- a/blocksuite/affine/all/tsconfig.json +++ b/blocksuite/affine/all/tsconfig.json @@ -55,6 +55,7 @@ { "path": "../shared" }, { "path": "../widgets/drag-handle" }, { "path": "../widgets/edgeless-auto-connect" }, + { "path": "../widgets/edgeless-selected-rect" }, { "path": "../widgets/edgeless-toolbar" }, { "path": "../widgets/edgeless-zoom-toolbar" }, { "path": "../widgets/frame-title" }, diff --git a/blocksuite/affine/blocks/root/package.json b/blocksuite/affine/blocks/root/package.json index b8d4935e4..c8801bd66 100644 --- a/blocksuite/affine/blocks/root/package.json +++ b/blocksuite/affine/blocks/root/package.json @@ -35,6 +35,7 @@ "@blocksuite/affine-model": "workspace:*", "@blocksuite/affine-rich-text": "workspace:*", "@blocksuite/affine-shared": "workspace:*", + "@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*", "@blocksuite/affine-widget-edgeless-toolbar": "workspace:*", "@blocksuite/data-view": "workspace:*", "@blocksuite/global": "workspace:*", diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/note-slicer/index.ts b/blocksuite/affine/blocks/root/src/edgeless/components/note-slicer/index.ts index 92c4a3058..afc028767 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/components/note-slicer/index.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/components/note-slicer/index.ts @@ -12,6 +12,7 @@ import { import { EDGELESS_BLOCK_CHILD_PADDING } from '@blocksuite/affine-shared/consts'; import { TelemetryProvider } from '@blocksuite/affine-shared/services'; import { getRectByBlockComponent } from '@blocksuite/affine-shared/utils'; +import type { EdgelessSelectedRectWidget } from '@blocksuite/affine-widget-edgeless-selected-rect'; import { DisposableGroup } from '@blocksuite/global/disposable'; import { deserializeXYWH, Point, serializeXYWH } from '@blocksuite/global/gfx'; import { ScissorsIcon } from '@blocksuite/icons/lit'; @@ -22,8 +23,6 @@ import { state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { styleMap } from 'lit/directives/style-map.js'; -import type { EdgelessSelectedRectWidget } from '../rects/edgeless-selected-rect'; - const DIVIDING_LINE_OFFSET = 4; const NEW_NOTE_GAP = 40; diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/utils.ts b/blocksuite/affine/blocks/root/src/edgeless/components/utils.ts deleted file mode 100644 index 972b9432f..000000000 --- a/blocksuite/affine/blocks/root/src/edgeless/components/utils.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { - CursorType, - ResizeHandle, - StandardCursor, -} from '@blocksuite/std/gfx'; - -const rotateCursorMap: { - [key in ResizeHandle]: number; -} = { - 'top-right': 0, - 'bottom-right': 90, - 'bottom-left': 180, - 'top-left': 270, - - // not used - left: 0, - right: 0, - top: 0, - bottom: 0, -}; - -export function generateCursorUrl( - angle = 0, - handle: ResizeHandle, - fallback: StandardCursor = 'default' -): CursorType { - angle = ((angle % 360) + 360) % 360; - return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg transform='rotate(${rotateCursorMap[handle] + angle} 16 16)'%3E%3Cpath fill='white' d='M13.7,18.5h3.9l0-1.5c0-1.4-1.2-2.6-2.6-2.6h-1.5v3.9l-5.8-5.8l5.8-5.8v3.9h2.3c3.1,0,5.6,2.5,5.6,5.6v2.3h3.9l-5.8,5.8L13.7,18.5z'/%3E%3Cpath d='M20.4,19.4v-3.2c0-2.6-2.1-4.7-4.7-4.7h-3.2l0,0V9L9,12.6l3.6,3.6v-2.6l0,0H15c1.9,0,3.5,1.6,3.5,3.5v2.4l0,0h-2.6l3.6,3.6l3.6-3.6L20.4,19.4L20.4,19.4z'/%3E%3C/g%3E%3C/svg%3E") 16 16, ${fallback}`; -} - -const handleToRotateMap: { - [key in ResizeHandle]: number; -} = { - 'top-left': 45, - 'top-right': 135, - 'bottom-right': 45, - 'bottom-left': 135, - left: 0, - right: 0, - top: 90, - bottom: 90, -}; - -const rotateToHandleMap: { - [key: number]: StandardCursor; -} = { - 0: 'ew-resize', - 45: 'nwse-resize', - 90: 'ns-resize', - 135: 'nesw-resize', -}; - -export function getRotatedResizeCursor(option: { - handle: ResizeHandle; - angle: number; -}) { - const angle = - (Math.round( - (handleToRotateMap[option.handle] + ((option.angle + 360) % 360)) / 45 - ) % - 4) * - 45; - - return rotateToHandleMap[angle] || 'default'; -} diff --git a/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-block.ts b/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-block.ts index d5771149c..60294ba27 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-block.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-block.ts @@ -45,7 +45,6 @@ import { css, html } from 'lit'; import { query } from 'lit/decorators.js'; import { repeat } from 'lit/directives/repeat.js'; -import type { EdgelessSelectedRectWidget } from './components/rects/edgeless-selected-rect.js'; import { EdgelessPageKeyboardManager } from './edgeless-keyboard.js'; import type { EdgelessRootService } from './edgeless-root-service.js'; import { isCanvasElement } from './utils/query.js'; @@ -133,13 +132,6 @@ export class EdgelessRootBlockComponent extends BlockComponent< return this.std.get(GfxControllerIdentifier); } - get selectedRectWidget() { - return this.host.view.getWidget( - 'edgeless-selected-rect', - this.host.id - ) as EdgelessSelectedRectWidget; - } - get slots() { return this.std.get(EdgelessLegacySlotIdentifier); } diff --git a/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-spec.ts b/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-spec.ts index deff1312b..524f7f85f 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-spec.ts +++ b/blocksuite/affine/blocks/root/src/edgeless/edgeless-root-spec.ts @@ -4,7 +4,6 @@ import { literal, unsafeStatic } from 'lit/static-html.js'; import { NOTE_SLICER_WIDGET } from './components/note-slicer/index.js'; import { EDGELESS_DRAGGING_AREA_WIDGET } from './components/rects/edgeless-dragging-area-rect.js'; -import { EDGELESS_SELECTED_RECT_WIDGET } from './components/rects/edgeless-selected-rect.js'; export const edgelessDraggingAreaWidget = WidgetViewExtension( 'affine:page', @@ -16,11 +15,6 @@ export const noteSlicerWidget = WidgetViewExtension( NOTE_SLICER_WIDGET, literal`${unsafeStatic(NOTE_SLICER_WIDGET)}` ); -export const edgelessSelectedRectWidget = WidgetViewExtension( - 'affine:page', - EDGELESS_SELECTED_RECT_WIDGET, - literal`${unsafeStatic(EDGELESS_SELECTED_RECT_WIDGET)}` -); export class EdgelessLocker extends LifeCycleWatcher { static override key = 'edgeless-locker'; diff --git a/blocksuite/affine/blocks/root/src/effects.ts b/blocksuite/affine/blocks/root/src/effects.ts index cdee8ebe4..1d0588865 100644 --- a/blocksuite/affine/blocks/root/src/effects.ts +++ b/blocksuite/affine/blocks/root/src/effects.ts @@ -1,5 +1,3 @@ -import { EdgelessAutoCompletePanel } from './edgeless/components/auto-complete/auto-complete-panel.js'; -import { EdgelessAutoComplete } from './edgeless/components/auto-complete/edgeless-auto-complete.js'; import { NOTE_SLICER_WIDGET, NoteSlicer, @@ -8,10 +6,6 @@ import { EDGELESS_DRAGGING_AREA_WIDGET, EdgelessDraggingAreaRectWidget, } from './edgeless/components/rects/edgeless-dragging-area-rect.js'; -import { - EDGELESS_SELECTED_RECT_WIDGET, - EdgelessSelectedRectWidget, -} from './edgeless/components/rects/edgeless-selected-rect.js'; import { EdgelessRootBlockComponent, EdgelessRootPreviewBlockComponent, @@ -36,13 +30,6 @@ function registerRootComponents() { } function registerMiscComponents() { - // Auto-complete components - customElements.define( - 'edgeless-auto-complete-panel', - EdgelessAutoCompletePanel - ); - customElements.define('edgeless-auto-complete', EdgelessAutoComplete); - // Note and template components customElements.define(NOTE_SLICER_WIDGET, NoteSlicer); @@ -51,21 +38,14 @@ function registerMiscComponents() { EDGELESS_DRAGGING_AREA_WIDGET, EdgelessDraggingAreaRectWidget ); - customElements.define( - EDGELESS_SELECTED_RECT_WIDGET, - EdgelessSelectedRectWidget - ); } declare global { interface HTMLElementTagNameMap { 'affine-edgeless-root': EdgelessRootBlockComponent; 'affine-edgeless-root-preview': EdgelessRootPreviewBlockComponent; - 'edgeless-auto-complete-panel': EdgelessAutoCompletePanel; - 'edgeless-auto-complete': EdgelessAutoComplete; 'note-slicer': NoteSlicer; 'edgeless-dragging-area-rect': EdgelessDraggingAreaRectWidget; - 'edgeless-selected-rect': EdgelessSelectedRectWidget; 'affine-page-root': PageRootBlockComponent; } } diff --git a/blocksuite/affine/blocks/root/src/view.ts b/blocksuite/affine/blocks/root/src/view.ts index 0119623ef..a5788381b 100644 --- a/blocksuite/affine/blocks/root/src/view.ts +++ b/blocksuite/affine/blocks/root/src/view.ts @@ -21,7 +21,6 @@ import { EdgelessElementToolbarExtension } from './edgeless/configs/toolbar'; import { edgelessDraggingAreaWidget, EdgelessLocker, - edgelessSelectedRectWidget, noteSlicerWidget, } from './edgeless/edgeless-root-spec'; import { AltCloneExtension } from './edgeless/interact-extensions/clone-ext'; @@ -92,7 +91,6 @@ export class RootViewExtension extends ViewExtensionProvider { BlockViewExtension('affine:page', literal`affine-edgeless-root`), edgelessDraggingAreaWidget, noteSlicerWidget, - edgelessSelectedRectWidget, EdgelessClipboardController, AltCloneExtension, ]); diff --git a/blocksuite/affine/blocks/root/tsconfig.json b/blocksuite/affine/blocks/root/tsconfig.json index f2df58a39..664cb8836 100644 --- a/blocksuite/affine/blocks/root/tsconfig.json +++ b/blocksuite/affine/blocks/root/tsconfig.json @@ -32,6 +32,7 @@ { "path": "../../model" }, { "path": "../../rich-text" }, { "path": "../../shared" }, + { "path": "../../widgets/edgeless-selected-rect" }, { "path": "../../widgets/edgeless-toolbar" }, { "path": "../../data-view" }, { "path": "../../../framework/global" }, diff --git a/blocksuite/affine/widgets/edgeless-selected-rect/package.json b/blocksuite/affine/widgets/edgeless-selected-rect/package.json new file mode 100644 index 000000000..46010e418 --- /dev/null +++ b/blocksuite/affine/widgets/edgeless-selected-rect/package.json @@ -0,0 +1,45 @@ +{ + "name": "@blocksuite/affine-widget-edgeless-selected-rect", + "description": "Affine edgeless selected-rect widget.", + "type": "module", + "scripts": { + "build": "tsc" + }, + "sideEffects": false, + "keywords": [], + "author": "toeverything", + "license": "MIT", + "dependencies": { + "@blocksuite/affine-block-frame": "workspace:*", + "@blocksuite/affine-block-note": "workspace:*", + "@blocksuite/affine-block-surface": "workspace:*", + "@blocksuite/affine-components": "workspace:*", + "@blocksuite/affine-ext-loader": "workspace:*", + "@blocksuite/affine-gfx-connector": "workspace:*", + "@blocksuite/affine-gfx-shape": "workspace:*", + "@blocksuite/affine-gfx-text": "workspace:*", + "@blocksuite/affine-model": "workspace:*", + "@blocksuite/affine-shared": "workspace:*", + "@blocksuite/global": "workspace:*", + "@blocksuite/icons": "^2.2.12", + "@blocksuite/std": "workspace:*", + "@lit/context": "^1.1.2", + "@preact/signals-core": "^1.8.0", + "@toeverything/theme": "^1.1.14", + "lit": "^3.2.0", + "rxjs": "^7.8.1", + "yjs": "^13.6.21" + }, + "exports": { + ".": "./src/index.ts", + "./effects": "./src/effects.ts", + "./view": "./src/view.ts" + }, + "files": [ + "src", + "dist", + "!src/__tests__", + "!dist/__tests__" + ], + "version": "0.21.0" +} diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/auto-complete/auto-complete-panel.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/auto-complete-panel.ts similarity index 100% rename from blocksuite/affine/blocks/root/src/edgeless/components/auto-complete/auto-complete-panel.ts rename to blocksuite/affine/widgets/edgeless-selected-rect/src/auto-complete-panel.ts diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/auto-complete/edgeless-auto-complete.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-auto-complete.ts similarity index 100% rename from blocksuite/affine/blocks/root/src/edgeless/components/auto-complete/edgeless-auto-complete.ts rename to blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-auto-complete.ts diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/rects/edgeless-selected-rect.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-selected-rect.ts similarity index 96% rename from blocksuite/affine/blocks/root/src/edgeless/components/rects/edgeless-selected-rect.ts rename to blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-selected-rect.ts index 9ba13f212..d039b4746 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/components/rects/edgeless-selected-rect.ts +++ b/blocksuite/affine/widgets/edgeless-selected-rect/src/edgeless-selected-rect.ts @@ -1,5 +1,8 @@ import { type FrameOverlay } from '@blocksuite/affine-block-frame'; -import { OverlayIdentifier } from '@blocksuite/affine-block-surface'; +import { + EdgelessLegacySlotIdentifier, + OverlayIdentifier, +} from '@blocksuite/affine-block-surface'; import { ConnectorElementModel, type RootBlockModel, @@ -27,16 +30,12 @@ import { repeat } from 'lit/directives/repeat.js'; import { styleMap } from 'lit/directives/style-map.js'; import { type Subscription } from 'rxjs'; -import type { EdgelessRootBlockComponent } from '../../edgeless-root-block.js'; -import { RenderResizeHandles } from '../resize/resize-handles.js'; -import { generateCursorUrl, getRotatedResizeCursor } from '../utils.js'; +import { RenderResizeHandles } from './resize-handles.js'; +import { generateCursorUrl, getRotatedResizeCursor } from './utils.js'; export const EDGELESS_SELECTED_RECT_WIDGET = 'edgeless-selected-rect'; -export class EdgelessSelectedRectWidget extends WidgetComponent< - RootBlockModel, - EdgelessRootBlockComponent -> { +export class EdgelessSelectedRectWidget extends WidgetComponent { // disable change-in-update warning static override enabledWarnings = []; @@ -469,10 +468,6 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< }; }, this); - get edgelessSlots() { - return this.block?.slots; - } - get frameOverlay() { return this.std.get(OverlayIdentifier('frame')) as FrameOverlay; } @@ -504,7 +499,7 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< } override firstUpdated() { - const { _disposables, block, selection, gfx } = this; + const { _disposables, selection, gfx } = this; _disposables.add( // viewport zooming / scrolling @@ -531,15 +526,13 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< selection.slots.updated.subscribe(this._updateOnSelectionChange) ); - if (block) { - _disposables.add( - block.slots.readonlyUpdated.subscribe(() => this.requestUpdate()) - ); + _disposables.add( + this._slots.readonlyUpdated.subscribe(() => this.requestUpdate()) + ); - _disposables.add( - block.slots.elementResizeEnd.subscribe(() => (this._isResizing = false)) - ); - } + _disposables.add( + this._slots.elementResizeEnd.subscribe(() => (this._isResizing = false)) + ); if (this._interaction) { _disposables.add( @@ -554,9 +547,9 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< this._isResizing = newVal; if (newVal) { - block?.slots.elementResizeStart.next(); + this._slots.elementResizeStart.next(); } else { - block?.slots.elementResizeEnd.next(); + this._slots.elementResizeEnd.next(); } }) ); @@ -571,6 +564,10 @@ export class EdgelessSelectedRectWidget extends WidgetComponent< return this.std.getOptional(InteractivityIdentifier); } + private get _slots() { + return this.std.get(EdgelessLegacySlotIdentifier); + } + private _renderHandles() { const { selection, gfx, block, store } = this; const elements = selection.selectedElements; diff --git a/blocksuite/affine/widgets/edgeless-selected-rect/src/effects.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/effects.ts new file mode 100644 index 000000000..753cdcb0e --- /dev/null +++ b/blocksuite/affine/widgets/edgeless-selected-rect/src/effects.ts @@ -0,0 +1,18 @@ +import { EdgelessAutoCompletePanel } from './auto-complete-panel'; +import { EdgelessAutoComplete } from './edgeless-auto-complete'; +import { + EDGELESS_SELECTED_RECT_WIDGET, + EdgelessSelectedRectWidget, +} from './edgeless-selected-rect'; + +export function effects() { + customElements.define( + 'edgeless-auto-complete-panel', + EdgelessAutoCompletePanel + ); + customElements.define('edgeless-auto-complete', EdgelessAutoComplete); + customElements.define( + EDGELESS_SELECTED_RECT_WIDGET, + EdgelessSelectedRectWidget + ); +} diff --git a/blocksuite/affine/widgets/edgeless-selected-rect/src/index.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/index.ts new file mode 100644 index 000000000..e7327ca40 --- /dev/null +++ b/blocksuite/affine/widgets/edgeless-selected-rect/src/index.ts @@ -0,0 +1 @@ +export * from './edgeless-selected-rect'; diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/resize/resize-handles.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/resize-handles.ts similarity index 100% rename from blocksuite/affine/blocks/root/src/edgeless/components/resize/resize-handles.ts rename to blocksuite/affine/widgets/edgeless-selected-rect/src/resize-handles.ts diff --git a/blocksuite/affine/widgets/edgeless-selected-rect/src/spec.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/spec.ts new file mode 100644 index 000000000..462f03b38 --- /dev/null +++ b/blocksuite/affine/widgets/edgeless-selected-rect/src/spec.ts @@ -0,0 +1,10 @@ +import { WidgetViewExtension } from '@blocksuite/std'; +import { literal, unsafeStatic } from 'lit/static-html.js'; + +import { EDGELESS_SELECTED_RECT_WIDGET } from './edgeless-selected-rect'; + +export const edgelessSelectedRectWidget = WidgetViewExtension( + 'affine:page', + EDGELESS_SELECTED_RECT_WIDGET, + literal`${unsafeStatic(EDGELESS_SELECTED_RECT_WIDGET)}` +); diff --git a/blocksuite/affine/blocks/root/src/edgeless/components/auto-complete/utils.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/utils.ts similarity index 84% rename from blocksuite/affine/blocks/root/src/edgeless/components/auto-complete/utils.ts rename to blocksuite/affine/widgets/edgeless-selected-rect/src/utils.ts index c0178d2c9..1d999d1f6 100644 --- a/blocksuite/affine/blocks/root/src/edgeless/components/auto-complete/utils.ts +++ b/blocksuite/affine/widgets/edgeless-selected-rect/src/utils.ts @@ -19,7 +19,13 @@ import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions'; import { Bound, normalizeDegAngle, type XYWH } from '@blocksuite/global/gfx'; import { assertType } from '@blocksuite/global/utils'; import type { BlockComponent } from '@blocksuite/std'; -import type { GfxController, GfxModel } from '@blocksuite/std/gfx'; +import type { + CursorType, + GfxController, + GfxModel, + ResizeHandle, + StandardCursor, +} from '@blocksuite/std/gfx'; import * as Y from 'yjs'; export enum Direction { @@ -348,3 +354,63 @@ export function createShapeElement( } return id; } + +const rotateCursorMap: { + [key in ResizeHandle]: number; +} = { + 'top-right': 0, + 'bottom-right': 90, + 'bottom-left': 180, + 'top-left': 270, + + // not used + left: 0, + right: 0, + top: 0, + bottom: 0, +}; + +export function generateCursorUrl( + angle = 0, + handle: ResizeHandle, + fallback: StandardCursor = 'default' +): CursorType { + angle = ((angle % 360) + 360) % 360; + return `url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cg transform='rotate(${rotateCursorMap[handle] + angle} 16 16)'%3E%3Cpath fill='white' d='M13.7,18.5h3.9l0-1.5c0-1.4-1.2-2.6-2.6-2.6h-1.5v3.9l-5.8-5.8l5.8-5.8v3.9h2.3c3.1,0,5.6,2.5,5.6,5.6v2.3h3.9l-5.8,5.8L13.7,18.5z'/%3E%3Cpath d='M20.4,19.4v-3.2c0-2.6-2.1-4.7-4.7-4.7h-3.2l0,0V9L9,12.6l3.6,3.6v-2.6l0,0H15c1.9,0,3.5,1.6,3.5,3.5v2.4l0,0h-2.6l3.6,3.6l3.6-3.6L20.4,19.4L20.4,19.4z'/%3E%3C/g%3E%3C/svg%3E") 16 16, ${fallback}`; +} + +const handleToRotateMap: { + [key in ResizeHandle]: number; +} = { + 'top-left': 45, + 'top-right': 135, + 'bottom-right': 45, + 'bottom-left': 135, + left: 0, + right: 0, + top: 90, + bottom: 90, +}; + +const rotateToHandleMap: { + [key: number]: StandardCursor; +} = { + 0: 'ew-resize', + 45: 'nwse-resize', + 90: 'ns-resize', + 135: 'nesw-resize', +}; + +export function getRotatedResizeCursor(option: { + handle: ResizeHandle; + angle: number; +}) { + const angle = + (Math.round( + (handleToRotateMap[option.handle] + ((option.angle + 360) % 360)) / 45 + ) % + 4) * + 45; + + return rotateToHandleMap[angle] || 'default'; +} diff --git a/blocksuite/affine/widgets/edgeless-selected-rect/src/view.ts b/blocksuite/affine/widgets/edgeless-selected-rect/src/view.ts new file mode 100644 index 000000000..daaa61199 --- /dev/null +++ b/blocksuite/affine/widgets/edgeless-selected-rect/src/view.ts @@ -0,0 +1,23 @@ +import { + type ViewExtensionContext, + ViewExtensionProvider, +} from '@blocksuite/affine-ext-loader'; + +import { effects } from './effects'; +import { edgelessSelectedRectWidget } from './spec'; + +export class EdgelessSelectedRectViewExtension extends ViewExtensionProvider { + override name = 'affine-edgeless-selected-rect-widget'; + + override effect() { + super.effect(); + effects(); + } + + override setup(context: ViewExtensionContext) { + super.setup(context); + if (this.isEdgeless(context.scope)) { + context.register(edgelessSelectedRectWidget); + } + } +} diff --git a/blocksuite/affine/widgets/edgeless-selected-rect/tsconfig.json b/blocksuite/affine/widgets/edgeless-selected-rect/tsconfig.json new file mode 100644 index 000000000..b102b54ee --- /dev/null +++ b/blocksuite/affine/widgets/edgeless-selected-rect/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo" + }, + "include": ["./src"], + "references": [ + { "path": "../../blocks/frame" }, + { "path": "../../blocks/note" }, + { "path": "../../blocks/surface" }, + { "path": "../../components" }, + { "path": "../../ext-loader" }, + { "path": "../../gfx/connector" }, + { "path": "../../gfx/shape" }, + { "path": "../../gfx/text" }, + { "path": "../../model" }, + { "path": "../../shared" }, + { "path": "../../../framework/global" }, + { "path": "../../../framework/std" } + ] +} diff --git a/tools/utils/src/workspace.gen.ts b/tools/utils/src/workspace.gen.ts index 3a29cc84c..c9f72197f 100644 --- a/tools/utils/src/workspace.gen.ts +++ b/tools/utils/src/workspace.gen.ts @@ -53,6 +53,7 @@ export const PackageList = [ 'blocksuite/affine/shared', 'blocksuite/affine/widgets/drag-handle', 'blocksuite/affine/widgets/edgeless-auto-connect', + 'blocksuite/affine/widgets/edgeless-selected-rect', 'blocksuite/affine/widgets/edgeless-toolbar', 'blocksuite/affine/widgets/edgeless-zoom-toolbar', 'blocksuite/affine/widgets/frame-title', @@ -372,6 +373,7 @@ export const PackageList = [ 'blocksuite/affine/model', 'blocksuite/affine/rich-text', 'blocksuite/affine/shared', + 'blocksuite/affine/widgets/edgeless-selected-rect', 'blocksuite/affine/widgets/edgeless-toolbar', 'blocksuite/affine/data-view', 'blocksuite/framework/global', @@ -858,6 +860,24 @@ export const PackageList = [ 'blocksuite/framework/std', ], }, + { + location: 'blocksuite/affine/widgets/edgeless-selected-rect', + name: '@blocksuite/affine-widget-edgeless-selected-rect', + workspaceDependencies: [ + 'blocksuite/affine/blocks/frame', + 'blocksuite/affine/blocks/note', + 'blocksuite/affine/blocks/surface', + 'blocksuite/affine/components', + 'blocksuite/affine/ext-loader', + 'blocksuite/affine/gfx/connector', + 'blocksuite/affine/gfx/shape', + 'blocksuite/affine/gfx/text', + 'blocksuite/affine/model', + 'blocksuite/affine/shared', + 'blocksuite/framework/global', + 'blocksuite/framework/std', + ], + }, { location: 'blocksuite/affine/widgets/edgeless-toolbar', name: '@blocksuite/affine-widget-edgeless-toolbar', @@ -1450,6 +1470,7 @@ export type PackageName = | '@blocksuite/affine-shared' | '@blocksuite/affine-widget-drag-handle' | '@blocksuite/affine-widget-edgeless-auto-connect' + | '@blocksuite/affine-widget-edgeless-selected-rect' | '@blocksuite/affine-widget-edgeless-toolbar' | '@blocksuite/affine-widget-edgeless-zoom-toolbar' | '@blocksuite/affine-widget-frame-title' diff --git a/tsconfig.json b/tsconfig.json index 9838a18bb..18f61e326 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -100,6 +100,7 @@ { "path": "./blocksuite/affine/shared" }, { "path": "./blocksuite/affine/widgets/drag-handle" }, { "path": "./blocksuite/affine/widgets/edgeless-auto-connect" }, + { "path": "./blocksuite/affine/widgets/edgeless-selected-rect" }, { "path": "./blocksuite/affine/widgets/edgeless-toolbar" }, { "path": "./blocksuite/affine/widgets/edgeless-zoom-toolbar" }, { "path": "./blocksuite/affine/widgets/frame-title" }, diff --git a/yarn.lock b/yarn.lock index ae1daf0f6..4de33f3f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2888,6 +2888,7 @@ __metadata: "@blocksuite/affine-model": "workspace:*" "@blocksuite/affine-rich-text": "workspace:*" "@blocksuite/affine-shared": "workspace:*" + "@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*" "@blocksuite/affine-widget-edgeless-toolbar": "workspace:*" "@blocksuite/data-view": "workspace:*" "@blocksuite/global": "workspace:*" @@ -3790,6 +3791,32 @@ __metadata: languageName: unknown linkType: soft +"@blocksuite/affine-widget-edgeless-selected-rect@workspace:*, @blocksuite/affine-widget-edgeless-selected-rect@workspace:blocksuite/affine/widgets/edgeless-selected-rect": + version: 0.0.0-use.local + resolution: "@blocksuite/affine-widget-edgeless-selected-rect@workspace:blocksuite/affine/widgets/edgeless-selected-rect" + dependencies: + "@blocksuite/affine-block-frame": "workspace:*" + "@blocksuite/affine-block-note": "workspace:*" + "@blocksuite/affine-block-surface": "workspace:*" + "@blocksuite/affine-components": "workspace:*" + "@blocksuite/affine-ext-loader": "workspace:*" + "@blocksuite/affine-gfx-connector": "workspace:*" + "@blocksuite/affine-gfx-shape": "workspace:*" + "@blocksuite/affine-gfx-text": "workspace:*" + "@blocksuite/affine-model": "workspace:*" + "@blocksuite/affine-shared": "workspace:*" + "@blocksuite/global": "workspace:*" + "@blocksuite/icons": "npm:^2.2.12" + "@blocksuite/std": "workspace:*" + "@lit/context": "npm:^1.1.2" + "@preact/signals-core": "npm:^1.8.0" + "@toeverything/theme": "npm:^1.1.14" + lit: "npm:^3.2.0" + rxjs: "npm:^7.8.1" + yjs: "npm:^13.6.21" + languageName: unknown + linkType: soft + "@blocksuite/affine-widget-edgeless-toolbar@workspace:*, @blocksuite/affine-widget-edgeless-toolbar@workspace:blocksuite/affine/widgets/edgeless-toolbar": version: 0.0.0-use.local resolution: "@blocksuite/affine-widget-edgeless-toolbar@workspace:blocksuite/affine/widgets/edgeless-toolbar" @@ -4102,6 +4129,7 @@ __metadata: "@blocksuite/affine-shared": "workspace:*" "@blocksuite/affine-widget-drag-handle": "workspace:*" "@blocksuite/affine-widget-edgeless-auto-connect": "workspace:*" + "@blocksuite/affine-widget-edgeless-selected-rect": "workspace:*" "@blocksuite/affine-widget-edgeless-toolbar": "workspace:*" "@blocksuite/affine-widget-edgeless-zoom-toolbar": "workspace:*" "@blocksuite/affine-widget-frame-title": "workspace:*"