refactor(editor): remove gfx tool global type (#12116)

Closes: BS-2650
This commit is contained in:
Saul-Mirone
2025-05-04 13:53:26 +00:00
parent f3b5c36cf7
commit 30a2e5b4fb
95 changed files with 664 additions and 521 deletions

View File

@@ -1,8 +1,9 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import { css, html, LitElement } from 'lit';
import { EraserTool } from '../../../eraser-tool';
import { EdgelessEraserDarkIcon, EdgelessEraserLightIcon } from './icons.js';
export class EdgelessEraserToolButton extends EdgelessToolbarToolMixin(
@@ -33,16 +34,15 @@ export class EdgelessEraserToolButton extends EdgelessToolbarToolMixin(
override enableActiveBackground = true;
override type: GfxToolsFullOptionValue['type'] = 'eraser';
override type = EraserTool;
override firstUpdated() {
this.disposables.add(
this.edgeless.bindHotKey(
{
Escape: () => {
if (this.edgelessTool.type === 'eraser') {
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'default' });
if (this.edgelessTool.toolType === EraserTool) {
this.setEdgelessTool(DefaultTool);
}
},
},
@@ -52,7 +52,7 @@ export class EdgelessEraserToolButton extends EdgelessToolbarToolMixin(
}
override render() {
const type = this.edgelessTool?.type;
const type = this.edgelessTool?.toolType;
const appTheme = this.edgeless.std.get(ThemeProvider).app$.value;
const icon =
appTheme === 'dark' ? EdgelessEraserDarkIcon : EdgelessEraserLightIcon;
@@ -65,8 +65,8 @@ export class EdgelessEraserToolButton extends EdgelessToolbarToolMixin(
data-shortcut="${'E'}"
></affine-tooltip-content-with-shortcut>`}
.tooltipOffset=${4}
.active=${type === 'eraser'}
@click=${() => this.setEdgelessTool({ type: 'eraser' })}
.active=${type === EraserTool}
@click=${() => this.setEdgelessTool(EraserTool)}
>
<div class="eraser-button">${icon}</div>
</edgeless-toolbar-button>

View File

@@ -16,6 +16,8 @@ import { css, html, LitElement, type TemplateResult } from 'lit';
import { property } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { BrushTool } from '../../../brush-tool';
import { HighlighterTool } from '../../../highlighter-tool';
import { penInfoMap } from './consts';
import type { Pen, PenMap } from './types';
@@ -80,7 +82,11 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
private readonly _onPickPen = (tool: Pen) => {
this.pen$.value = tool;
this.setEdgelessTool(tool);
if (tool === 'brush') {
this.setEdgelessTool(BrushTool);
} else {
this.setEdgelessTool(HighlighterTool);
}
};
private readonly _onPickColor = (e: ColorEvent) => {
@@ -91,7 +97,7 @@ export class EdgelessPenMenu extends EdgelessToolbarToolMixin(
this.onChange({ color });
};
override type: Pen[] = ['brush', 'highlighter'];
override type = [BrushTool, HighlighterTool];
override render() {
const {

View File

@@ -10,6 +10,8 @@ import { css, html, LitElement, nothing } from 'lit';
import { styleMap } from 'lit/directives/style-map.js';
import { when } from 'lit/directives/when.js';
import { BrushTool } from '../../../brush-tool';
import { HighlighterTool } from '../../../highlighter-tool';
import { penIconMap, penInfoMap } from './consts';
import type { Pen } from './types';
@@ -97,19 +99,19 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
override enableActiveBackground = true;
override type: Pen[] = ['brush', 'highlighter'];
override type = [BrushTool, HighlighterTool];
override firstUpdated() {
this.disposables.add(
this.gfx.tool.currentToolName$.subscribe(name => {
const tool = this.type.find(t => t === name);
const tool = this.type.find(t => t.toolName === name);
if (!tool) {
this.tryDisposePopper();
return;
}
if (tool !== this.pen$.peek()) {
this.pen$.value = tool;
if (tool.toolName !== this.pen$.peek()) {
this.pen$.value = tool.toolName as Pen;
}
if (this.active) return;
@@ -121,7 +123,17 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
private _togglePenMenu() {
if (this.tryDisposePopper()) return;
!this.active && this.setEdgelessTool(this.pen$.peek());
const setPenByType = (pen: Pen) => {
if (pen === 'brush') {
this.setEdgelessTool(BrushTool);
} else {
this.setEdgelessTool(HighlighterTool);
}
};
if (!this.active) {
const pen = this.pen$.peek();
setPenByType(pen);
}
const menu = this.createPopper('edgeless-pen-menu', this);
Object.assign(menu.element, {
colors$: this.colors$,
@@ -132,7 +144,7 @@ export class EdgelessPenToolButton extends EdgelessToolbarToolMixin(
onChange: (props: Record<string, unknown>) => {
const pen = this.pen$.peek();
this.edgeless.std.get(EditPropsStore).recordLastProps(pen, props);
this.setEdgelessTool(pen);
setPenByType(pen);
},
});
}

View File

@@ -1,8 +1,3 @@
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
export type Pen = Extract<
GfxToolsFullOptionValue['type'],
'brush' | 'highlighter'
>;
export type Pen = 'brush' | 'highlighter';
export type PenMap<T> = Record<Pen, T>;

View File

@@ -1,5 +1,6 @@
import {
CanvasElementType,
DefaultTool,
OverlayIdentifier,
} from '@blocksuite/affine-block-surface';
import type {
@@ -104,8 +105,7 @@ export class ConnectorTool extends BaseTool<ConnectorToolOptions> {
this._allowCancel = true;
}
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
this.gfx.selection.set({ elements: [focusedId] });
}
@@ -132,8 +132,8 @@ export class ConnectorTool extends BaseTool<ConnectorToolOptions> {
const connector = this._connector;
this.doc.captureSync();
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
this.gfx.selection.set({ elements: [connector.id] });
}

View File

@@ -1,4 +1,7 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import {
DefaultTool,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import { getLineHeight } from '@blocksuite/affine-gfx-text';
import type { ConnectorElementModel } from '@blocksuite/affine-model';
import type { RichText } from '@blocksuite/affine-rich-text';
@@ -40,8 +43,7 @@ export function mountConnectorLabelEditor(
const gfx = edgeless.std.get(GfxControllerIdentifier);
// @ts-expect-error default tool should be migrated to std
gfx.tool.setTool('default');
gfx.tool.setTool(DefaultTool);
gfx.selection.set({
elements: [connector.id],
editing: true,

View File

@@ -1,4 +1,7 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import {
DefaultTool,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import type { ConnectorElementModel } from '@blocksuite/affine-model';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { IVec } from '@blocksuite/global/gfx';
@@ -24,8 +27,7 @@ export function mountConnectorLabelEditor(
const gfx = edgeless.std.get(GfxControllerIdentifier);
// @ts-expect-error FIXME: resolve after gfx tool refactor
gfx.tool.setTool('default');
gfx.tool.setTool(DefaultTool);
gfx.selection.set({
elements: [connector.id],
editing: true,

View File

@@ -8,6 +8,8 @@ import {
ConnectorLIcon,
} from '@blocksuite/icons/lit';
import { ConnectorTool } from '../connector-tool';
export const buildConnectorDenseMenu: DenseMenuBuilder = (edgeless, gfx) => {
const prevMode =
edgeless.std.get(EditPropsStore).lastProps$.value.connector.mode;
@@ -17,7 +19,7 @@ export const buildConnectorDenseMenu: DenseMenuBuilder = (edgeless, gfx) => {
const createSelect =
(mode: ConnectorMode, record = true) =>
() => {
gfx.tool.setTool('connector', {
gfx.tool.setTool(ConnectorTool, {
mode,
});
record &&

View File

@@ -16,11 +16,12 @@ import {
ConnectorEIcon,
ConnectorLIcon,
} from '@blocksuite/icons/lit';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import { computed } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { ConnectorTool } from '../connector-tool';
function ConnectorModeButtonGroup(
mode: ConnectorMode,
setConnectorMode: (props: Record<string, unknown>) => void
@@ -110,7 +111,7 @@ export class EdgelessConnectorMenu extends EdgelessToolbarToolMixin(
return this.edgeless.std.get(ThemeProvider).theme$.value;
});
override type: GfxToolsFullOptionValue['type'] = 'connector';
override type = ConnectorTool;
override render() {
const { stroke, strokeWidth, mode } = this._props$.value;

View File

@@ -10,6 +10,8 @@ import {
import { computed } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { ConnectorTool } from '../connector-tool';
const IcomMap = {
[ConnectorMode.Straight]: ConnectorLIcon(),
[ConnectorMode.Orthogonal]: ConnectorEIcon(),
@@ -30,7 +32,7 @@ export class EdgelessConnectorToolButton extends QuickToolMixin(
.mode;
});
override type = 'connector' as const;
override type = ConnectorTool;
private _toggleMenu() {
if (this.tryDisposePopper()) return;
@@ -64,7 +66,7 @@ export class EdgelessConnectorToolButton extends QuickToolMixin(
@click=${() => {
// don't update tool before toggling menu
this._toggleMenu();
this.gfx.tool.setTool('connector', {
this.gfx.tool.setTool(ConnectorTool, {
mode,
});
}}

View File

@@ -1,3 +1,4 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import type { GroupElementModel } from '@blocksuite/affine-model';
import type { RichText } from '@blocksuite/affine-rich-text';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
@@ -36,8 +37,7 @@ export function mountGroupTitleEditor(
const gfx = edgeless.std.get(GfxControllerIdentifier);
// @ts-expect-error FIXME: resolve after gfx tool refactor
gfx.tool.setTool('default');
gfx.tool.setTool(DefaultTool);
gfx.selection.set({
elements: [group.id],
editing: true,

View File

@@ -1,3 +1,4 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import type { GroupElementModel } from '@blocksuite/affine-model';
import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';
import type { BlockComponent } from '@blocksuite/std';
@@ -19,8 +20,7 @@ export function mountGroupTitleEditor(
const gfx = edgeless.std.get(GfxControllerIdentifier);
// @ts-expect-error FIXME: resolve after gfx tool refactor
gfx.tool.setTool('default');
gfx.tool.setTool(DefaultTool);
gfx.selection.set({
elements: [group.id],
editing: true,

View File

@@ -1,14 +1,13 @@
import { insertLinkByQuickSearchCommand } from '@blocksuite/affine-block-bookmark';
import { insertEmbedCard } from '@blocksuite/affine-block-embed';
import { DefaultTool } from '@blocksuite/affine-block-surface';
import { toggleEmbedCardCreateModal } from '@blocksuite/affine-components/embed-card-modal';
import { LinkIcon } from '@blocksuite/affine-components/icons';
import type * as PointerEffect from '@blocksuite/affine-gfx-pointer';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import { QuickToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import { css, html, LitElement } from 'lit';
declare type _GLOBAL_ = typeof PointerEffect;
export class EdgelessLinkToolButton extends QuickToolMixin(LitElement) {
static override styles = css`
.link-icon,
@@ -18,7 +17,7 @@ export class EdgelessLinkToolButton extends QuickToolMixin(LitElement) {
}
`;
override type = 'default' as const;
override type = DefaultTool;
private _onClick() {
const [success, { insertedLinkType }] = this.edgeless.std.command.exec(
@@ -40,6 +39,12 @@ export class EdgelessLinkToolButton extends QuickToolMixin(LitElement) {
props: { url },
});
},
},
({ mode }) => {
if (mode === 'edgeless') {
const gfx = this.edgeless.std.get(GfxControllerIdentifier);
gfx.tool.setTool(DefaultTool);
}
}
).catch(console.error);
return;

View File

@@ -17,6 +17,7 @@
"@blocksuite/affine-components": "workspace:*",
"@blocksuite/affine-ext-loader": "workspace:*",
"@blocksuite/affine-gfx-connector": "workspace:*",
"@blocksuite/affine-gfx-pointer": "workspace:*",
"@blocksuite/affine-gfx-shape": "workspace:*",
"@blocksuite/affine-gfx-text": "workspace:*",
"@blocksuite/affine-model": "workspace:*",

View File

@@ -1,4 +1,6 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import { toast } from '@blocksuite/affine-components/toast';
import { EmptyTool } from '@blocksuite/affine-gfx-pointer';
import type { MindmapStyle } from '@blocksuite/affine-model';
import {
EditPropsStore,
@@ -123,8 +125,7 @@ export class EdgelessMindmapMenu extends EdgelessToolbarToolMixin(
ToolbarMindmapItem | TextItem | ImportItem | MediaItem
>;
// @ts-expect-error FIXME: resolve after gfx tool refactor
override type = 'empty' as const;
override type = EmptyTool;
get mindMaps() {
return getMindMaps(this.theme);
@@ -223,8 +224,7 @@ export class EdgelessMindmapMenu extends EdgelessToolbarToolMixin(
this.onActiveStyleChange?.(element.data.style);
}
// a workaround to active mindmap, so that menu cannot be closed by `Escape`
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'empty' });
this.setEdgelessTool(EmptyTool);
},
onDrop: (element, bound) => {
if ('render' in element.data) {
@@ -234,8 +234,7 @@ export class EdgelessMindmapMenu extends EdgelessToolbarToolMixin(
if (!id) return;
if (element.data.type === 'mindmap') {
this.onActiveStyleChange?.(element.data.style);
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'default' });
this.setEdgelessTool(DefaultTool);
this.gfx.selection.set({
elements: [id],
editing: false,
@@ -244,8 +243,7 @@ export class EdgelessMindmapMenu extends EdgelessToolbarToolMixin(
element.data.type === 'text' ||
element.data.type === 'media'
) {
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'default' });
this.setEdgelessTool(DefaultTool);
}
})
.catch(console.error);

View File

@@ -1,4 +1,9 @@
import { EdgelessCRUDIdentifier } from '@blocksuite/affine-block-surface';
import {
DefaultTool,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import { EmptyTool } from '@blocksuite/affine-gfx-pointer';
import { TextTool } from '@blocksuite/affine-gfx-text';
import type {
MindmapElementModel,
MindmapStyle,
@@ -14,7 +19,6 @@ import {
} from '@blocksuite/affine-widget-edgeless-toolbar';
import type { Bound } from '@blocksuite/global/gfx';
import { SignalWatcher } from '@blocksuite/global/lit';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import { computed } from '@preact/signals-core';
import { css, html, LitElement, nothing } from 'lit';
import { property, query, state } from 'lit/decorators.js';
@@ -150,8 +154,7 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
override enableActiveBackground = true;
// @ts-expect-error FIXME: resolve after gfx tool refactor
override type: GfxToolsFullOptionValue['type'][] = ['empty', 'text'];
override type = [EmptyTool, TextTool];
get draggableTools(): DraggableTool[] {
const style = this._style$.value;
@@ -192,8 +195,7 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
private _toggleMenu() {
if (this.tryDisposePopper()) return;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'default' });
this.setEdgelessTool(DefaultTool);
const menu = this.createPopper('edgeless-mindmap-menu', this);
Object.assign(menu.element, {
@@ -213,8 +215,7 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
const element = this.crud.getElementById(id) as MindmapElementModel;
this.tryDisposePopper();
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'default' });
this.setEdgelessTool(DefaultTool);
this.gfx.selection.set({
elements: [element.tree.id],
editing: false,
@@ -274,15 +275,13 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
if (!id) return;
this.readyToDrop = false;
if (el.data.name === 'mindmap') {
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'default' });
this.setEdgelessTool(DefaultTool);
this.gfx.selection.set({
elements: [id],
editing: false,
});
} else if (el.data.name === 'text') {
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'default' });
this.setEdgelessTool(DefaultTool);
}
})
.catch(console.error);
@@ -314,8 +313,7 @@ export class EdgelessMindmapToolButton extends EdgelessToolbarToolMixin(
});
return;
}
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool({ type: 'empty' });
this.setEdgelessTool(EmptyTool);
const icon = this.mindmapElement;
const { x, y } = gfx.tool.lastMousePos$.peek();
const { viewport } = this.edgeless.std.get(ViewportElementProvider);

View File

@@ -14,6 +14,7 @@
{ "path": "../../components" },
{ "path": "../../ext-loader" },
{ "path": "../connector" },
{ "path": "../pointer" },
{ "path": "../shape" },
{ "path": "../text" },
{ "path": "../../model" },

View File

@@ -1,18 +1,35 @@
import type { SurfaceBlockComponent } from '@blocksuite/affine-block-surface';
import {
addNote,
DEFAULT_NOTE_OFFSET_X,
DEFAULT_NOTE_OFFSET_Y,
DefaultTool,
EdgelessCRUDIdentifier,
EXCLUDING_MOUSE_OUT_CLASS_LIST,
type SurfaceBlockComponent,
} from '@blocksuite/affine-block-surface';
import {
DEFAULT_NOTE_HEIGHT,
DEFAULT_NOTE_WIDTH,
NOTE_MIN_HEIGHT,
type NoteBlockModel,
NoteDisplayMode,
} from '@blocksuite/affine-model';
import { EditPropsStore } from '@blocksuite/affine-shared/services';
import { focusTextModel } from '@blocksuite/affine-rich-text';
import {
EditPropsStore,
TelemetryProvider,
} from '@blocksuite/affine-shared/services';
import type { NoteChildrenFlavour } from '@blocksuite/affine-shared/types';
import { hasClassNameInList } from '@blocksuite/affine-shared/utils';
import { Point } from '@blocksuite/global/gfx';
import type { PointerEventState } from '@blocksuite/std';
import { BaseTool } from '@blocksuite/std/gfx';
import {
handleNativeRangeAtPoint,
hasClassNameInList,
} from '@blocksuite/affine-shared/utils';
import { type IPoint, Point, serializeXYWH } from '@blocksuite/global/gfx';
import type { BlockStdScope, PointerEventState } from '@blocksuite/std';
import {
BaseTool,
type GfxBlockElementModel,
GfxControllerIdentifier,
} from '@blocksuite/std/gfx';
import { effect } from '@preact/signals-core';
import { DraggingNoteOverlay, NoteOverlay } from './overlay';
@@ -210,3 +227,116 @@ declare module '@blocksuite/std/gfx' {
'affine:note': NoteToolOption;
}
}
type NoteOptions = {
childFlavour: NoteChildrenFlavour;
childType: string | null;
collapse: boolean;
};
function addNote(
std: BlockStdScope,
point: Point,
options: NoteOptions,
width = DEFAULT_NOTE_WIDTH,
height = DEFAULT_NOTE_HEIGHT
) {
const noteId = addNoteAtPoint(std, point, {
width,
height,
});
const gfx = std.get(GfxControllerIdentifier);
const doc = std.store;
const blockId = doc.addBlock(
options.childFlavour,
{ type: options.childType },
noteId
);
if (options.collapse && height > NOTE_MIN_HEIGHT) {
const note = doc.getModelById(noteId) as NoteBlockModel;
doc.updateBlock(note, () => {
note.props.edgeless.collapse = true;
note.props.edgeless.collapsedHeight = height;
});
}
gfx.tool.setTool(DefaultTool);
// Wait for edgelessTool updated
requestAnimationFrame(() => {
const blocks =
(doc.root?.children.filter(
child => child.flavour === 'affine:note'
) as GfxBlockElementModel[]) ?? [];
const element = blocks.find(b => b.id === noteId);
if (element) {
gfx.selection.set({
elements: [element.id],
editing: true,
});
// Waiting dom updated, `note mask` is removed
if (blockId) {
focusTextModel(gfx.std, blockId);
} else {
// Cannot reuse `handleNativeRangeClick` directly here,
// since `retargetClick` will re-target to pervious editor
handleNativeRangeAtPoint(point.x, point.y);
}
}
});
}
function addNoteAtPoint(
std: BlockStdScope,
/**
* The point is in browser coordinate
*/
point: IPoint,
options: {
width?: number;
height?: number;
parentId?: string;
noteIndex?: number;
offsetX?: number;
offsetY?: number;
scale?: number;
} = {}
) {
const gfx = std.get(GfxControllerIdentifier);
const crud = std.get(EdgelessCRUDIdentifier);
const {
width = DEFAULT_NOTE_WIDTH,
height = DEFAULT_NOTE_HEIGHT,
offsetX = DEFAULT_NOTE_OFFSET_X,
offsetY = DEFAULT_NOTE_OFFSET_Y,
parentId = gfx.doc.root?.id,
noteIndex,
scale = 1,
} = options;
const [x, y] = gfx.viewport.toModelCoord(point.x, point.y);
const blockId = crud.addBlock(
'affine:note',
{
xywh: serializeXYWH(
x - offsetX * scale,
y - offsetY * scale,
width,
height
),
displayMode: NoteDisplayMode.EdgelessOnly,
},
parentId,
noteIndex
);
std.getOptional(TelemetryProvider)?.track('CanvasElementAdded', {
control: 'canvas:draw',
page: 'whiteboard editor',
module: 'toolbar',
segment: 'toolbar',
type: 'note',
});
return blockId;
}

View File

@@ -5,10 +5,11 @@ import {
import { type Color, DefaultTheme } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import type { XYWH } from '@blocksuite/global/gfx';
import type { GfxController, GfxToolsMap } from '@blocksuite/std/gfx';
import type { GfxController } from '@blocksuite/std/gfx';
import { effect } from '@preact/signals-core';
import { Subject } from 'rxjs';
import type { NoteTool } from '../note-tool';
import {
NOTE_OVERLAY_CORNER_RADIUS,
NOTE_OVERLAY_HEIGHT,
@@ -33,8 +34,7 @@ export class NoteOverlay extends ToolOverlay {
effect(() => {
// when change note child type, update overlay text
if (this.gfx.tool.currentToolName$.value !== 'affine:note') return;
const tool =
this.gfx.tool.currentTool$.peek() as GfxToolsMap['affine:note'];
const tool = this.gfx.tool.currentTool$.peek() as NoteTool;
this.text = this._getOverlayText(tool.activatedOption.tip);
(this.gfx.surfaceComponent as SurfaceBlockComponent).refresh();
})

View File

@@ -1,6 +1,7 @@
import { addAttachments } from '@blocksuite/affine-block-attachment';
import { insertLinkByQuickSearchCommand } from '@blocksuite/affine-block-bookmark';
import { addImages } from '@blocksuite/affine-block-image';
import { DefaultTool } from '@blocksuite/affine-block-surface';
import { MAX_IMAGE_WIDTH } from '@blocksuite/affine-model';
import { TelemetryProvider } from '@blocksuite/affine-shared/services';
import type { NoteChildrenFlavour } from '@blocksuite/affine-shared/types';
@@ -10,13 +11,13 @@ import {
} from '@blocksuite/affine-shared/utils';
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import { AttachmentIcon, ImageIcon, LinkIcon } from '@blocksuite/icons/lit';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import type { ToolOptions } from '@blocksuite/std/gfx';
import { effect } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { property, state } from 'lit/decorators.js';
import { repeat } from 'lit/directives/repeat.js';
import type { NoteToolOption } from '../note-tool.js';
import { NoteTool, type NoteToolOption } from '../note-tool.js';
import { NOTE_MENU_ITEMS } from './note-menu-config.js';
export class EdgelessNoteMenu extends EdgelessToolbarToolMixin(LitElement) {
@@ -51,7 +52,7 @@ export class EdgelessNoteMenu extends EdgelessToolbarToolMixin(LitElement) {
}
`;
override type: GfxToolsFullOptionValue['type'] = 'affine:note';
override type = NoteTool;
private async _addImages() {
this._imageLoading = true;
@@ -60,8 +61,7 @@ export class EdgelessNoteMenu extends EdgelessToolbarToolMixin(LitElement) {
maxWidth: MAX_IMAGE_WIDTH,
});
this._imageLoading = false;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
this.gfx.selection.set({ elements: ids });
}
@@ -96,10 +96,11 @@ export class EdgelessNoteMenu extends EdgelessToolbarToolMixin(LitElement) {
effect(() => {
const tool = this.gfx.tool.currentToolOption$.value;
if (tool?.type !== 'affine:note') return;
this.childFlavour = tool.childFlavour;
this.childType = tool.childType;
this.tip = tool.tip;
if (tool?.toolType !== NoteTool) return;
const options = tool.options as ToolOptions<NoteTool>;
this.childFlavour = options.childFlavour;
this.childType = options.childType;
this.tip = options.tip;
})
);
}
@@ -141,8 +142,7 @@ export class EdgelessNoteMenu extends EdgelessToolbarToolMixin(LitElement) {
const file = await openFileOrFiles();
if (!file) return;
await addAttachments(this.edgeless.std, [file]);
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
this.edgeless.std
.getOptional(TelemetryProvider)
?.track('CanvasElementAdded', {

View File

@@ -13,7 +13,7 @@ import { computed } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { state } from 'lit/decorators.js';
import type { NoteToolOption } from '../note-tool.js';
import { NoteTool, type NoteToolOption } from '../note-tool.js';
import { toShapeNotToAdapt } from './icon.js';
export class EdgelessNoteSeniorButton extends EdgelessToolbarToolMixin(
@@ -138,15 +138,14 @@ export class EdgelessNoteSeniorButton extends EdgelessToolbarToolMixin(
override enableActiveBackground = true;
override type = 'affine:note' as const;
override type = NoteTool;
private _toggleNoteMenu() {
if (this.tryDisposePopper()) return;
const { edgeless, childFlavour, childType, tip } = this;
this.setEdgelessTool({
type: 'affine:note',
this.setEdgelessTool(NoteTool, {
childFlavour,
childType,
tip,
@@ -171,8 +170,7 @@ export class EdgelessNoteSeniorButton extends EdgelessToolbarToolMixin(
Object.assign(this, { [key]: props[key] });
}
});
this.setEdgelessTool({
type: 'affine:note',
this.setEdgelessTool(NoteTool, {
childFlavour: this.childFlavour,
childType: this.childType,
tip: this.tip,

View File

@@ -4,12 +4,11 @@ import {
QuickToolMixin,
} from '@blocksuite/affine-widget-edgeless-toolbar';
import { PageIcon } from '@blocksuite/icons/lit';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import { effect } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { state } from 'lit/decorators.js';
import type { NoteToolOption } from '../note-tool.js';
import { NoteTool, type NoteToolOption } from '../note-tool.js';
import type { EdgelessNoteMenu } from './note-menu.js';
export class EdgelessNoteToolButton extends QuickToolMixin(LitElement) {
@@ -23,7 +22,7 @@ export class EdgelessNoteToolButton extends QuickToolMixin(LitElement) {
private readonly _states = ['childFlavour', 'childType', 'tip'] as const;
override type: GfxToolsFullOptionValue['type'] = 'affine:note';
override type = NoteTool;
private _disposeMenu() {
this._noteMenu?.dispose();
@@ -35,7 +34,7 @@ export class EdgelessNoteToolButton extends QuickToolMixin(LitElement) {
this._disposeMenu();
this.requestUpdate();
} else {
this.gfx.tool.setTool('affine:note', {
this.gfx.tool.setTool(NoteTool, {
childFlavour: this.childFlavour,
childType: this.childType,
tip: this.tip,
@@ -59,7 +58,7 @@ export class EdgelessNoteToolButton extends QuickToolMixin(LitElement) {
Object.assign(this, { [key]: props[key] });
}
});
this.gfx.tool.setTool('affine:note', {
this.gfx.tool.setTool(NoteTool, {
childFlavour: this.childFlavour,
childType: this.childType,
tip: this.tip,

View File

@@ -1,10 +1,12 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import { QuickToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import { HandIcon, SelectIcon } from '@blocksuite/icons/lit';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import { effect } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { query } from 'lit/decorators.js';
import { PanTool } from '../tools';
export class EdgelessDefaultToolButton extends QuickToolMixin(LitElement) {
static override styles = css`
.current-icon {
@@ -17,19 +19,19 @@ export class EdgelessDefaultToolButton extends QuickToolMixin(LitElement) {
}
`;
override type: GfxToolsFullOptionValue['type'][] = ['default', 'pan'];
override type = [DefaultTool, PanTool];
private _changeTool() {
if (this.toolbar.activePopper) {
// click manually always closes the popper
this.toolbar.activePopper.dispose();
}
const type = this.edgelessTool?.type;
const type = this.edgelessTool?.toolType?.toolName;
if (type !== 'default' && type !== 'pan') {
if (localStorage.defaultTool === 'default') {
this.setEdgelessTool('default');
this.setEdgelessTool(DefaultTool);
} else if (localStorage.defaultTool === 'pan') {
this.setEdgelessTool('pan', { panning: false });
this.setEdgelessTool(PanTool, { panning: false });
}
return;
}
@@ -37,9 +39,9 @@ export class EdgelessDefaultToolButton extends QuickToolMixin(LitElement) {
// wait for animation to finish
setTimeout(() => {
if (type === 'default') {
this.setEdgelessTool('pan', { panning: false });
this.setEdgelessTool(PanTool, { panning: false });
} else if (type === 'pan') {
this.setEdgelessTool('default');
this.setEdgelessTool(DefaultTool);
}
this._fadeIn();
}, 100);
@@ -71,7 +73,7 @@ export class EdgelessDefaultToolButton extends QuickToolMixin(LitElement) {
}
override render() {
const type = this.edgelessTool?.type;
const type = this.edgelessTool?.toolType?.toolName;
const { active } = this;
const tipInfo =
type === 'pan'

View File

@@ -1,424 +0,0 @@
import { resetNativeSelection } from '@blocksuite/affine-shared/utils';
import { DisposableGroup } from '@blocksuite/global/disposable';
import type { IVec } from '@blocksuite/global/gfx';
import type { PointerEventState } from '@blocksuite/std';
import {
BaseTool,
type GfxModel,
InteractivityIdentifier,
isGfxGroupCompatibleModel,
} from '@blocksuite/std/gfx';
import { effect } from '@preact/signals-core';
import { calPanDelta } from '../utils/panning-utils.js';
export enum DefaultModeDragType {
/** Moving selected contents */
ContentMoving = 'content-moving',
/** Native range dragging inside active note block */
NativeEditing = 'native-editing',
/** Default void state */
None = 'none',
/** Expanding the dragging area, select the content covered inside */
Selecting = 'selecting',
}
export class DefaultTool extends BaseTool {
static override toolName: string = 'default';
private _accumulateDelta: IVec = [0, 0];
private _autoPanTimer: number | null = null;
private readonly _clearDisposable = () => {
if (this._disposables) {
this._disposables.dispose();
this._disposables = null;
}
};
private readonly _clearSelectingState = () => {
this._stopAutoPanning();
this._clearDisposable();
};
private _disposables: DisposableGroup | null = null;
private _panViewport(delta: IVec) {
this._accumulateDelta[0] += delta[0];
this._accumulateDelta[1] += delta[1];
this.gfx.viewport.applyDeltaCenter(delta[0], delta[1]);
}
private _selectionRectTransition: null | {
w: number;
h: number;
startX: number;
startY: number;
endX: number;
endY: number;
} = null;
private readonly _startAutoPanning = (delta: IVec) => {
this._panViewport(delta);
this._updateSelectingState(delta);
this._stopAutoPanning();
this._autoPanTimer = window.setInterval(() => {
this._panViewport(delta);
this._updateSelectingState(delta);
}, 30);
};
private readonly _stopAutoPanning = () => {
if (this._autoPanTimer) {
clearTimeout(this._autoPanTimer);
this._autoPanTimer = null;
}
};
private _toBeMoved: GfxModel[] = [];
private readonly _updateSelectingState = (delta: IVec = [0, 0]) => {
const { gfx } = this;
if (gfx.keyboard.spaceKey$.peek() && this._selectionRectTransition) {
/* Move the selection if space is pressed */
const curDraggingViewArea = this.controller.draggingViewArea$.peek();
const { w, h, startX, startY, endX, endY } =
this._selectionRectTransition;
const { endX: lastX, endY: lastY } = curDraggingViewArea;
const dx = lastX + delta[0] - endX + this._accumulateDelta[0];
const dy = lastY + delta[1] - endY + this._accumulateDelta[1];
this.controller.draggingViewArea$.value = {
...curDraggingViewArea,
x: Math.min(startX + dx, lastX),
y: Math.min(startY + dy, lastY),
w,
h,
startX: startX + dx,
startY: startY + dy,
};
} else {
const curDraggingArea = this.controller.draggingViewArea$.peek();
const newStartX = curDraggingArea.startX - delta[0];
const newStartY = curDraggingArea.startY - delta[1];
this.controller.draggingViewArea$.value = {
...curDraggingArea,
startX: newStartX,
startY: newStartY,
x: Math.min(newStartX, curDraggingArea.endX),
y: Math.min(newStartY, curDraggingArea.endY),
w: Math.abs(curDraggingArea.endX - newStartX),
h: Math.abs(curDraggingArea.endY - newStartY),
};
}
const elements = this.interactivity?.handleBoxSelection({
box: this.controller.draggingArea$.peek(),
});
if (!elements) return;
this.selection.set({
elements: elements.map(el => el.id),
editing: false,
});
};
dragType = DefaultModeDragType.None;
movementDragging = false;
/**
* Get the end position of the dragging area in the model coordinate
*/
get dragLastPos() {
const { endX, endY } = this.controller.draggingArea$.peek();
return [endX, endY] as IVec;
}
/**
* Get the start position of the dragging area in the model coordinate
*/
get dragStartPos() {
const { startX, startY } = this.controller.draggingArea$.peek();
return [startX, startY] as IVec;
}
get selection() {
return this.gfx.selection;
}
get interactivity() {
return this.std.getOptional(InteractivityIdentifier);
}
private async _cloneContent() {
const clonedResult = await this.interactivity?.requestElementClone({
elements: this._toBeMoved,
});
if (!clonedResult) return;
this._toBeMoved = clonedResult.elements;
this.selection.set({
elements: this._toBeMoved.map(e => e.id),
editing: false,
});
}
private _determineDragType(evt: PointerEventState): DefaultModeDragType {
const { x, y } = this.controller.lastMouseModelPos$.peek();
if (this.selection.isInSelectedRect(x, y)) {
if (this.selection.selectedElements.length === 1) {
const currentHoveredElem = this._getElementInGroup(x, y);
let curSelected = this.selection.selectedElements[0];
// If one of the following condition is true, keep the selection:
// 1. if group is currently selected
// 2. if the selected element is descendant of the hovered element
// 3. not hovering any element or hovering the same element
//
// Otherwise, we update the selection to the current hovered element
const shouldKeepSelection =
isGfxGroupCompatibleModel(curSelected) ||
(isGfxGroupCompatibleModel(currentHoveredElem) &&
currentHoveredElem.hasDescendant(curSelected)) ||
!currentHoveredElem ||
currentHoveredElem === curSelected;
if (!shouldKeepSelection) {
curSelected = currentHoveredElem;
this.selection.set({
elements: [curSelected.id],
editing: false,
});
}
}
return this.selection.editing
? DefaultModeDragType.NativeEditing
: DefaultModeDragType.ContentMoving;
} else {
const checked = this.interactivity?.handleElementSelection(evt);
if (checked) {
return DefaultModeDragType.ContentMoving;
} else {
return DefaultModeDragType.Selecting;
}
}
}
private _getElementInGroup(modelX: number, modelY: number) {
const tryGetLockedAncestor = (e: GfxModel | null) => {
if (e?.isLockedByAncestor()) {
return e.groups.findLast(group => group.isLocked());
}
return e;
};
return tryGetLockedAncestor(this.gfx.getElementInGroup(modelX, modelY));
}
private initializeDragState(
dragType: DefaultModeDragType,
event: PointerEventState
) {
this.dragType = dragType;
this._clearDisposable();
this._disposables = new DisposableGroup();
// If the drag type is selecting, set up the dragging area disposable group
// If the viewport updates when dragging, should update the dragging area and selection
if (this.dragType === DefaultModeDragType.Selecting) {
this._disposables.add(
this.gfx.viewport.viewportUpdated.subscribe(() => {
if (
this.dragType === DefaultModeDragType.Selecting &&
this.controller.dragging$.peek() &&
!this._autoPanTimer
) {
this._updateSelectingState();
}
})
);
return;
}
if (this.dragType === DefaultModeDragType.ContentMoving) {
if (this.interactivity) {
this.doc.captureSync();
this.interactivity.handleElementMove({
movingElements: this._toBeMoved,
event: event.raw,
onDragEnd: () => {
this.doc.captureSync();
},
});
}
return;
}
}
override click(e: PointerEventState) {
if (this.doc.readonly) return;
if (!this.interactivity?.handleElementSelection(e)) {
this.selection.clear();
resetNativeSelection(null);
}
this.interactivity?.dispatchEvent('click', e);
}
override deactivate() {
this._stopAutoPanning();
this._clearDisposable();
this._accumulateDelta = [0, 0];
}
override doubleClick(e: PointerEventState) {
if (this.doc.readonly) {
const viewport = this.gfx.viewport;
if (viewport.zoom === 1) {
this.gfx.fitToScreen();
} else {
// Zoom to 100% and Center
const [x, y] = viewport.toModelCoord(e.x, e.y);
viewport.setViewport(1, [x, y], true);
}
return;
}
this.interactivity?.dispatchEvent('dblclick', e);
}
override dragEnd(e: PointerEventState) {
this.interactivity?.dispatchEvent('dragend', e);
if (this.selection.editing || !this.movementDragging) return;
this.movementDragging = false;
this._toBeMoved = [];
this._clearSelectingState();
this.dragType = DefaultModeDragType.None;
}
override dragMove(e: PointerEventState) {
this.interactivity?.dispatchEvent('dragmove', e);
if (!this.movementDragging) {
return;
}
const { viewport } = this.gfx;
switch (this.dragType) {
case DefaultModeDragType.Selecting: {
// Record the last drag pointer position for auto panning and view port updating
this._updateSelectingState();
const moveDelta = calPanDelta(viewport, e);
if (moveDelta) {
this._startAutoPanning(moveDelta);
} else {
this._stopAutoPanning();
}
break;
}
case DefaultModeDragType.ContentMoving: {
break;
}
case DefaultModeDragType.NativeEditing: {
// TODO reset if drag out of note
break;
}
}
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
override async dragStart(e: PointerEventState) {
const { preventDefaultState, handledByView } =
this.interactivity?.dispatchEvent('dragstart', e) ?? {};
if (this.selection.editing || preventDefaultState || handledByView) return;
this.movementDragging = true;
// Determine the drag type based on the current state and event
let dragType = this._determineDragType(e);
const elements = this.selection.selectedElements;
if (elements.some(e => e.isLocked())) return;
const toBeMoved = new Set(elements);
elements.forEach(element => {
if (isGfxGroupCompatibleModel(element)) {
element.descendantElements.forEach(ele => {
toBeMoved.add(ele);
});
}
});
this._toBeMoved = Array.from(toBeMoved);
// If alt key is pressed and content is moving, clone the content
if (dragType === DefaultModeDragType.ContentMoving && e.keys.alt) {
await this._cloneContent();
}
// Set up drag state
this.initializeDragState(dragType, e);
}
override mounted() {
this.disposable.add(
effect(() => {
const pressed = this.gfx.keyboard.spaceKey$.value;
if (pressed) {
const currentDraggingArea = this.controller.draggingViewArea$.peek();
this._selectionRectTransition = {
w: currentDraggingArea.w,
h: currentDraggingArea.h,
startX: currentDraggingArea.startX,
startY: currentDraggingArea.startY,
endX: currentDraggingArea.endX,
endY: currentDraggingArea.endY,
};
} else {
this._selectionRectTransition = null;
}
})
);
}
override pointerDown(e: PointerEventState): void {
this.interactivity?.dispatchEvent('pointerdown', e);
}
override pointerMove(e: PointerEventState) {
this.interactivity?.dispatchEvent('pointermove', e);
}
override pointerUp(e: PointerEventState) {
this.interactivity?.dispatchEvent('pointerup', e);
}
override unmounted(): void {}
}
declare module '@blocksuite/std/gfx' {
interface GfxToolsMap {
default: DefaultTool;
}
}

View File

@@ -1,3 +1,2 @@
export * from './default-tool.js';
export * from './empty-tool.js';
export * from './pan-tool.js';

View File

@@ -56,11 +56,14 @@ export class PanTool extends BaseTool<PanToolOption> {
const selection = this.gfx.selection.surfaceSelections;
const currentTool = this.controller.currentToolOption$.peek();
const restoreToPrevious = () => {
this.controller.setTool(currentTool);
this.gfx.selection.set(selection);
const { toolType, options } = currentTool;
if (toolType && options) {
this.controller.setTool(toolType, options);
this.gfx.selection.set(selection);
}
};
this.controller.setTool('pan', {
this.controller.setTool(PanTool, {
panning: true,
});

View File

@@ -1,46 +0,0 @@
import type { IVec } from '@blocksuite/global/gfx';
import type { PointerEventState } from '@blocksuite/std';
import type { Viewport } from '@blocksuite/std/gfx';
const PANNING_DISTANCE = 30;
export function calPanDelta(
viewport: Viewport,
e: PointerEventState,
edgeDistance = 20
): IVec | null {
// Get viewport edge
const { left, top } = viewport;
const { width, height } = viewport;
// Get pointer position
let { x, y } = e;
const { containerOffset } = e;
x += containerOffset.x;
y += containerOffset.y;
// Check if pointer is near viewport edge
const nearLeft = x < left + edgeDistance;
const nearRight = x > left + width - edgeDistance;
const nearTop = y < top + edgeDistance;
const nearBottom = y > top + height - edgeDistance;
// If pointer is not near viewport edge, return false
if (!(nearLeft || nearRight || nearTop || nearBottom)) return null;
// Calculate move delta
let deltaX = 0;
let deltaY = 0;
// Use PANNING_DISTANCE to limit the max delta, avoid panning too fast
if (nearLeft) {
deltaX = Math.max(-PANNING_DISTANCE, x - (left + edgeDistance));
} else if (nearRight) {
deltaX = Math.min(PANNING_DISTANCE, x - (left + width - edgeDistance));
}
if (nearTop) {
deltaY = Math.max(-PANNING_DISTANCE, y - (top + edgeDistance));
} else if (nearBottom) {
deltaY = Math.min(PANNING_DISTANCE, y - (top + height - edgeDistance));
}
return [deltaX, deltaY];
}

View File

@@ -7,7 +7,7 @@ import { effects } from './effects';
import { defaultQuickTool } from './quick-tool/quick-tool';
import { SnapExtension } from './snap/snap-manager';
import { SnapOverlay } from './snap/snap-overlay';
import { DefaultTool, EmptyTool, PanTool } from './tools';
import { EmptyTool, PanTool } from './tools';
export class PointerViewExtension extends ViewExtensionProvider {
override name = 'affine-pointer-gfx';
@@ -20,7 +20,6 @@ export class PointerViewExtension extends ViewExtensionProvider {
override setup(context: ViewExtensionContext) {
super.setup(context);
context.register(EmptyTool);
context.register(DefaultTool);
context.register(PanTool);
if (this.isEdgeless(context.scope)) {
context.register(defaultQuickTool);

View File

@@ -1,5 +1,6 @@
import {
CanvasElementType,
DefaultTool,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import {
@@ -137,7 +138,7 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
draggingShape: DraggableShape['name'] = 'roundedRect';
override type = 'shape' as const;
override type = ShapeTool;
get crud() {
return this.edgeless.std.get(EdgelessCRUDIdentifier);
@@ -169,8 +170,7 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
this.draggableController.states.draggingElement?.data.name;
if (!shapeName) return;
this.setEdgelessTool({
type: 'shape',
this.setEdgelessTool(ShapeTool, {
shapeName,
});
const controller = this.gfx.tool.currentTool$.peek();
@@ -206,8 +206,7 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
this._setShapeOverlayLock(false);
this.readyToDrop = false;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
this.gfx.selection.set({
elements: [id],
editing: false,
@@ -265,7 +264,7 @@ export class EdgelessToolbarShapeDraggable extends EdgelessToolbarToolMixin(
const clientPos = { x: x + left, y: y + top };
this.draggableController.dragAndMoveTo(el, clientPos);
} else {
this.setEdgelessTool('shape', {
this.setEdgelessTool(ShapeTool, {
shapeName: this.draggingShape,
});
}

View File

@@ -15,12 +15,16 @@ import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { SignalWatcher, WithDisposable } from '@blocksuite/global/lit';
import { StyleGeneralIcon, StyleScribbleIcon } from '@blocksuite/icons/lit';
import type { BlockComponent } from '@blocksuite/std';
import { GfxControllerIdentifier } from '@blocksuite/std/gfx';
import {
GfxControllerIdentifier,
type ToolOptionWithType,
} from '@blocksuite/std/gfx';
import { computed, effect, type Signal, signal } from '@preact/signals-core';
import { css, html, LitElement } from 'lit';
import { property } from 'lit/decorators.js';
import { when } from 'lit/directives/when.js';
import { ShapeTool } from '../shape-tool';
import { ShapeComponentConfig } from '../toolbar';
export class EdgelessShapeMenu extends SignalWatcher(
@@ -115,8 +119,12 @@ export class EdgelessShapeMenu extends SignalWatcher(
effect(() => {
const value = gfx.tool.currentToolOption$.value;
if (value && value.type === 'shape') {
this._shapeName$.value = value.shapeName;
if (value && value.toolType === ShapeTool) {
const shapeName = (value as ToolOptionWithType<ShapeTool>).options
?.shapeName;
if (shapeName) {
this._shapeName$.value = shapeName;
}
}
})
);

View File

@@ -38,7 +38,7 @@ export class EdgelessShapeToolButton extends EdgelessToolbarToolMixin(
if (!this.popper) this._toggleMenu();
};
override type = 'shape' as const;
override type = ShapeTool;
private _toggleMenu() {
this.createPopper('edgeless-shape-menu', this, {

View File

@@ -1,5 +1,6 @@
import {
CanvasElementType,
DefaultTool,
EdgelessCRUDIdentifier,
} from '@blocksuite/affine-block-surface';
import {
@@ -109,8 +110,7 @@ export class EdgelessShapeToolElement extends WithDisposable(LitElement) {
return;
}
this._dragging = false;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
if (this._isOutside) {
const rect = this._shapeElement.getBoundingClientRect();
this._backupShapeElement.style.setProperty('transition', 'none');

View File

@@ -1,5 +1,6 @@
import {
CanvasElementType,
DefaultTool,
EXCLUDING_MOUSE_OUT_CLASS_LIST,
type SurfaceBlockComponent,
} from '@blocksuite/affine-block-surface';
@@ -180,8 +181,7 @@ export class ShapeTool extends BaseTool<ShapeToolOption> {
const element = this.gfx.getElementById(id);
if (!element) return;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
this.gfx.selection.set({
elements: [element.id],
editing: false,
@@ -257,8 +257,7 @@ export class ShapeTool extends BaseTool<ShapeToolOption> {
const element = this.gfx.getElementById(id);
if (!element) return;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.controller.setTool('default');
this.controller.setTool(DefaultTool);
this.gfx.selection.set({
elements: [element.id],
});

View File

@@ -1,4 +1,5 @@
import {
DefaultTool,
EdgelessCRUDIdentifier,
TextUtils,
} from '@blocksuite/affine-block-surface';
@@ -49,8 +50,7 @@ export function mountShapeTextEditor(
return;
}
// @ts-expect-error FIXME: resolve after gfx tool refactor
gfx.tool.setTool('default');
gfx.tool.setTool(DefaultTool);
gfx.selection.set({
elements: [shapeElement.id],
editing: true,

View File

@@ -1,3 +1,4 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import {
darkToolbarStyles,
lightToolbarStyles,
@@ -314,8 +315,7 @@ export class EdgelessTemplatePanel extends WithDisposable(LitElement) {
}
} finally {
this._loadingTemplate = null;
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
}
}

View File

@@ -1,7 +1,8 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import { ArrowDownSmallIcon } from '@blocksuite/affine-components/icons';
import { once } from '@blocksuite/affine-shared/utils';
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import type { ToolOptionWithType } from '@blocksuite/std/gfx';
import {
arrow,
autoUpdate,
@@ -14,6 +15,7 @@ import { state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { repeat } from 'lit/directives/repeat.js';
import { TemplateTool } from '../template-tool';
import { TemplateCard1, TemplateCard2, TemplateCard3 } from './cards.js';
import type { EdgelessTemplatePanel } from './template-panel.js';
@@ -115,11 +117,11 @@ export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
private _cleanup: (() => void) | null = null;
private _prevTool: GfxToolsFullOptionValue | null = null;
private _prevTool: ToolOptionWithType | null = null;
override enableActiveBackground = true;
override type: GfxToolsFullOptionValue['type'] = 'template';
override type = TemplateTool;
get cards() {
const { theme } = this;
@@ -134,12 +136,15 @@ export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
this._cleanup = null;
this.requestUpdate();
if (this._prevTool && this._prevTool.type !== 'template') {
this.setEdgelessTool(this._prevTool);
if (
this._prevTool &&
this._prevTool.toolType &&
this._prevTool.toolType !== TemplateTool
) {
this.setEdgelessTool(this._prevTool.toolType, this._prevTool.options);
this._prevTool = null;
} else {
// @ts-expect-error FIXME: resolve after gfx tool refactor
this.setEdgelessTool('default');
this.setEdgelessTool(DefaultTool);
}
}
}
@@ -147,8 +152,8 @@ export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
private _togglePanel() {
if (this._openedPanel) {
this._closePanel();
if (this._prevTool) {
this.setEdgelessTool(this._prevTool);
if (this._prevTool && this._prevTool.toolType) {
this.setEdgelessTool(this._prevTool.toolType, this._prevTool.options);
this._prevTool = null;
}
return;
@@ -156,7 +161,7 @@ export class EdgelessTemplateButton extends EdgelessToolbarToolMixin(
this._prevTool = this.edgelessTool ? { ...this.edgelessTool } : null;
this.setEdgelessTool('template');
this.setEdgelessTool(TemplateTool);
const panel = document.createElement('edgeless-templates-panel');
panel.edgeless = this.edgeless;

View File

@@ -1,5 +1,6 @@
import {
CanvasElementType,
DefaultTool,
EdgelessCRUDIdentifier,
getSurfaceBlock,
type IModelCoord,
@@ -53,8 +54,7 @@ export function mountTextElementEditor(
const gfx = edgeless.std.get(GfxControllerIdentifier);
// @ts-expect-error TODO: refactor gfx tool
gfx.tool.setTool('default');
gfx.tool.setTool(DefaultTool);
gfx.selection.set({
elements: [textElement.id],
editing: true,

View File

@@ -1,5 +1,6 @@
import {
CanvasElementType,
DefaultTool,
EdgelessCRUDIdentifier,
type IModelCoord,
} from '@blocksuite/affine-block-surface';
@@ -37,8 +38,7 @@ export function mountTextElementEditor(
const gfx = edgeless.std.get(GfxControllerIdentifier);
// @ts-expect-error TODO: refactor gfx tool
gfx.tool.setTool('default');
gfx.tool.setTool(DefaultTool);
gfx.selection.set({
elements: [textElement.id],
editing: true,

View File

@@ -1,3 +1,4 @@
import { DefaultTool } from '@blocksuite/affine-block-surface';
import type { TextElementModel } from '@blocksuite/affine-model';
import {
FeatureFlagService,
@@ -49,8 +50,7 @@ export class TextTool extends BaseTool {
if (textFlag) {
const [x, y] = this.gfx.viewport.toModelCoord(e.x, e.y);
this.gfx.std.command.exec(insertEdgelessTextCommand, { x, y });
// @ts-expect-error TODO: refactor gfx tool
this.gfx.tool.setTool('default');
this.gfx.tool.setTool(DefaultTool);
} else {
addText(this.gfx, e);
}

View File

@@ -2,11 +2,12 @@ import { DefaultTheme } from '@blocksuite/affine-model';
import { ThemeProvider } from '@blocksuite/affine-shared/services';
import type { ColorEvent } from '@blocksuite/affine-shared/utils';
import { EdgelessToolbarToolMixin } from '@blocksuite/affine-widget-edgeless-toolbar';
import type { GfxToolsFullOptionValue } from '@blocksuite/std/gfx';
import { computed } from '@preact/signals-core';
import { css, html, LitElement, nothing } from 'lit';
import { property } from 'lit/decorators.js';
import { TextTool } from '../tool';
export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
static override styles = css`
:host {
@@ -20,10 +21,10 @@ export class EdgelessTextMenu extends EdgelessToolbarToolMixin(LitElement) {
return this.edgeless.std.get(ThemeProvider).theme$.value;
});
override type: GfxToolsFullOptionValue['type'] = 'text';
override type = TextTool;
override render() {
if (this.edgelessTool.type !== 'text') return nothing;
if (this.edgelessTool.toolType !== TextTool) return nothing;
return html`
<edgeless-slide-menu>