Files
AFFiNE-Mirror/blocksuite/blocks/src/root-block/edgeless/gfx-tool/note-tool.ts
2024-12-29 06:51:48 +00:00

211 lines
5.8 KiB
TypeScript

import type { SurfaceBlockComponent } from '@blocksuite/affine-block-surface';
import { addNote } from '@blocksuite/affine-block-surface';
import {
DEFAULT_NOTE_HEIGHT,
DEFAULT_NOTE_WIDTH,
} from '@blocksuite/affine-model';
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import type { NoteChildrenFlavour } from '@blocksuite/affine-shared/types';
import type { PointerEventState } from '@blocksuite/block-std';
import { BaseTool } from '@blocksuite/block-std/gfx';
import { Point } from '@blocksuite/global/utils';
import { effect } from '@preact/signals-core';
import { hasClassNameInList } from '../../../_common/utils/index.js';
import { EXCLUDING_MOUSE_OUT_CLASS_LIST } from '../utils/consts.js';
import { DraggingNoteOverlay, NoteOverlay } from '../utils/tool-overlay.js';
export type NoteToolOption = {
childFlavour: NoteChildrenFlavour;
childType: string | null;
tip: string;
};
export class NoteTool extends BaseTool<NoteToolOption> {
static override toolName = 'affine:note';
private _draggingNoteOverlay: DraggingNoteOverlay | null = null;
private _noteOverlay: NoteOverlay | null = null;
// Ensure clear overlay before adding a new note
private _clearOverlay() {
this._noteOverlay = this._disposeOverlay(this._noteOverlay);
this._draggingNoteOverlay = this._disposeOverlay(this._draggingNoteOverlay);
(this.gfx.surfaceComponent as SurfaceBlockComponent).refresh();
}
private _disposeOverlay(overlay: NoteOverlay | null) {
if (!overlay) return null;
overlay.dispose();
(
this.gfx.surfaceComponent as SurfaceBlockComponent
)?.renderer.removeOverlay(overlay);
return null;
}
// Should hide overlay when mouse is out of viewport or on menu and toolbar
private _hideOverlay() {
if (!this._noteOverlay) return;
this._noteOverlay.globalAlpha = 0;
(this.gfx.surfaceComponent as SurfaceBlockComponent)?.refresh();
}
private _resize(shift = false) {
const { _draggingNoteOverlay } = this;
if (!_draggingNoteOverlay) return;
const draggingArea = this.controller.draggingArea$.peek();
const { startX, startY } = draggingArea;
let { endX, endY } = this.controller.draggingArea$.peek();
if (shift) {
const w = Math.abs(endX - startX);
const h = Math.abs(endY - startY);
const m = Math.max(w, h);
endX = startX + (endX > startX ? m : -m);
endY = startY + (endY > startY ? m : -m);
}
_draggingNoteOverlay.slots.draggingNoteUpdated.emit({
xywh: [
Math.min(startX, endX),
Math.min(startY, endY),
Math.abs(startX - endX),
Math.abs(startY - endY),
],
});
}
private _updateOverlayPosition(x: number, y: number) {
if (!this._noteOverlay) return;
this._noteOverlay.x = x;
this._noteOverlay.y = y;
(this.gfx.surfaceComponent as SurfaceBlockComponent).refresh();
}
override activate() {
const attributes =
this.std.get(EditPropsStore).lastProps$.value['affine:note'];
const background = attributes.background;
this._noteOverlay = new NoteOverlay(this.gfx, background);
this._noteOverlay.text = this.activatedOption.tip;
(this.gfx.surfaceComponent as SurfaceBlockComponent).renderer.addOverlay(
this._noteOverlay
);
}
override click(e: PointerEventState): void {
this._clearOverlay();
const { childFlavour, childType } = this.activatedOption;
const options = {
childFlavour,
childType,
collapse: false,
};
const point = new Point(e.point.x, e.point.y);
addNote(this.std, point, options);
}
override deactivate() {
this._clearOverlay();
}
override dragEnd() {
if (!this._draggingNoteOverlay) return;
const { x, y, width, height } = this._draggingNoteOverlay;
this._disposeOverlay(this._draggingNoteOverlay);
const { childFlavour, childType } = this.activatedOption;
const options = {
childFlavour,
childType,
collapse: true,
};
const [viewX, viewY] = this.gfx.viewport.toViewCoord(x, y);
const point = new Point(viewX, viewY);
this.doc.captureSync();
addNote(
this.std,
point,
options,
Math.max(width, DEFAULT_NOTE_WIDTH),
Math.max(height, DEFAULT_NOTE_HEIGHT)
);
}
override dragMove(e: PointerEventState) {
if (!this._draggingNoteOverlay) return;
this._resize(e.keys.shift || this.gfx.keyboard.shiftKey$.peek());
}
override dragStart() {
this._clearOverlay();
const attributes =
this.std.get(EditPropsStore).lastProps$.value['affine:note'];
const background = attributes.background;
this._draggingNoteOverlay = new DraggingNoteOverlay(this.gfx, background);
(this.gfx.surfaceComponent as SurfaceBlockComponent).renderer.addOverlay(
this._draggingNoteOverlay
);
}
override mounted() {
this.disposable.add(
effect(() => {
const shiftPressed = this.gfx.keyboard.shiftKey$.value;
if (!this._draggingNoteOverlay) {
return;
}
this._resize(shiftPressed);
})
);
}
override pointerMove(e: PointerEventState) {
if (!this._noteOverlay) return;
// if mouse is in viewport and move, update overlay pointion and show overlay
if (this._noteOverlay.globalAlpha === 0) this._noteOverlay.globalAlpha = 1;
const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y);
this._updateOverlayPosition(x, y);
}
override pointerOut(e: PointerEventState) {
// should not hide the overlay when pointer on the area of other notes
if (
e.raw.relatedTarget &&
hasClassNameInList(
e.raw.relatedTarget as Element,
EXCLUDING_MOUSE_OUT_CLASS_LIST
)
)
return;
this._hideOverlay();
}
}
declare module '@blocksuite/block-std/gfx' {
interface GfxToolsMap {
'affine:note': NoteTool;
}
interface GfxToolsOption {
'affine:note': NoteToolOption;
}
}