Skip to content

Commit

Permalink
feat(edgeless): copilot selection widget (#6497)
Browse files Browse the repository at this point in the history
  • Loading branch information
doouding authored Mar 22, 2024
1 parent aeee575 commit 1288de1
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 43 deletions.
7 changes: 6 additions & 1 deletion packages/blocks/src/_common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ export type PanTool = {
panning: boolean;
};

export type CopilotSelectionTool = {
type: 'copilot';
};

export type NoteChildrenFlavour =
| 'affine:paragraph'
| 'affine:list'
Expand Down Expand Up @@ -239,7 +243,8 @@ export type EdgelessTool =
| ConnectorTool
| EraserTool
| FrameTool
| FrameNavigatorTool;
| FrameNavigatorTool
| CopilotSelectionTool;

export type EmbedBlockDoubleClickData = {
blockId: string;
Expand Down
12 changes: 12 additions & 0 deletions packages/blocks/src/_common/utils/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,22 @@ export enum MOUSE_BUTTONS {
FIFTH = 16,
}

export enum MOUSE_BUTTON {
MAIN = 0,
AUXILIARY = 1,
SECONDARY = 2,
FORTH = 3,
FIFTH = 4,
}

export function isMiddleButtonPressed(e: MouseEvent) {
return (MOUSE_BUTTONS.AUXILIARY & e.buttons) === MOUSE_BUTTONS.AUXILIARY;
}

export function isRightButtonPressed(e: MouseEvent) {
return (MOUSE_BUTTONS.SECONDARY & e.buttons) === MOUSE_BUTTONS.SECONDARY;
}

export function stopPropagation(event: Event) {
event.stopPropagation();
}
Expand Down
2 changes: 2 additions & 0 deletions packages/blocks/src/_specs/_specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import { PageRootService } from '../root-block/page/page-root-service.js';
import { RootBlockSchema } from '../root-block/root-model.js';
import { AFFINE_DOC_REMOTE_SELECTION_WIDGET } from '../root-block/widgets/doc-remote-selection/doc-remote-selection.js';
import { AFFINE_DRAG_HANDLE_WIDGET } from '../root-block/widgets/drag-handle/drag-handle.js';
import { AFFINE_EDGELESS_COPILOT_WIDGET } from '../root-block/widgets/edgeless-copilot/index.js';
import { AFFINE_EDGELESS_REMOTE_SELECTION_WIDGET } from '../root-block/widgets/edgeless-remote-selection/index.js';
import { AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET } from '../root-block/widgets/edgeless-zoom-toolbar/index.js';
import { AFFINE_FORMAT_BAR_WIDGET } from '../root-block/widgets/format-bar/format-bar.js';
Expand Down Expand Up @@ -116,6 +117,7 @@ const EdgelessPageSpec: BlockSpec<EdgelessRootBlockWidgetName> = {
[AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET]: literal`${unsafeStatic(
AFFINE_EDGELESS_ZOOM_TOOLBAR_WIDGET
)}`,
[AFFINE_EDGELESS_COPILOT_WIDGET]: literal`${unsafeStatic(AFFINE_EDGELESS_COPILOT_WIDGET)}`,
},
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { PointerEventState } from '@blocksuite/block-std';
import { Slot } from '@blocksuite/store';

import type { CopilotSelectionTool } from '../../../../_common/utils/index.js';
import { EdgelessToolController } from './index.js';

export class CopilotSelectionController extends EdgelessToolController<CopilotSelectionTool> {
readonly tool = <CopilotSelectionTool>{
type: 'copilot',
};

private _dragStartPoint: [number, number] = [0, 0];
private _dragLastPoint: [number, number] = [0, 0];
private _dragging = false;

draggingAreaUpdated = new Slot();

get area() {
const start = new DOMPoint(
this._dragStartPoint[0],
this._dragStartPoint[1]
);
const end = new DOMPoint(this._dragLastPoint[0], this._dragLastPoint[1]);

const minX = Math.min(start.x, end.x);
const minY = Math.min(start.y, end.y);
const maxX = Math.max(start.x, end.x);
const maxY = Math.max(start.y, end.y);

return new DOMRect(minX, minY, maxX - minX, maxY - minY);
}

private _initDragState(e: PointerEventState) {
this._dragStartPoint = this._service.viewport.toModelCoord(e.x, e.y);
this._dragLastPoint = this._dragStartPoint;
}

override onContainerDragStart(e: PointerEventState): void {
this._initDragState(e);
this._dragging = true;
this.draggingAreaUpdated.emit();
}

override onContainerDragMove(e: PointerEventState): void {
if (!this._dragging) return;

this._dragLastPoint = this._service.viewport.toModelCoord(e.x, e.y);
this.draggingAreaUpdated.emit();
}

override onContainerDragEnd(): void {
this._dragging = false;
}

onContainerPointerDown(): void {}

onContainerClick(): void {}

onContainerContextMenu(): void {}

onContainerDblClick(): void {}

onContainerTripleClick(): void {}

onContainerMouseMove(): void {}

onContainerMouseOut(): void {}

onPressShiftKey(): void {}

onPressSpaceBar(): void {}

beforeModeSwitch(): void {}

afterModeSwitch(): void {}
}
23 changes: 16 additions & 7 deletions packages/blocks/src/root-block/edgeless/edgeless-keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,12 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
global: true,
}
);

this._bindShiftKey();
this._bindToggleHand();
}

private _bindShiftKey() {
this.rootElement.handleEvent(
'keyDown',
ctx => {
Expand All @@ -217,7 +223,6 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {
global: true,
}
);
this._bindToggleHand();
}

private _bindToggleHand() {
Expand Down Expand Up @@ -288,12 +293,16 @@ export class EdgelessPageKeyboardManager extends PageKeyboardManager {

private _shift(event: KeyboardEvent) {
const edgeless = this.rootElement;
if (!event.repeat) {
if (event.key.toLowerCase() === 'shift' && event.shiftKey) {
edgeless.slots.pressShiftKeyUpdated.emit(true);
} else {
edgeless.slots.pressShiftKeyUpdated.emit(false);
}

if (event.repeat) return;

const shiftKeyPressed =
event.key.toLowerCase() === 'shift' && event.shiftKey;

if (shiftKeyPressed) {
edgeless.slots.pressShiftKeyUpdated.emit(true);
} else {
edgeless.slots.pressShiftKeyUpdated.emit(false);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { readImageSize } from './components/utils.js';
import { EdgelessClipboardController } from './controllers/clipboard.js';
import { BrushToolController } from './controllers/tools/brush-tool.js';
import { ConnectorToolController } from './controllers/tools/connector-tool.js';
import { CopilotSelectionController } from './controllers/tools/copilot-tool.js';
import { DefaultToolController } from './controllers/tools/default-tool.js';
import { EraserToolController } from './controllers/tools/eraser-tool.js';
import { PresentToolController } from './controllers/tools/frame-navigator-tool.js';
Expand Down Expand Up @@ -717,6 +718,7 @@ export class EdgelessRootBlockComponent extends BlockElement<
FrameToolController,
PanToolController,
PresentToolController,
CopilotSelectionController,
] as EdgelessToolConstructor[];

tools.forEach(tool => {
Expand Down
113 changes: 79 additions & 34 deletions packages/blocks/src/root-block/edgeless/services/tools-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import type {
UIEventHandler,
UIEventState,
} from '@blocksuite/block-std';
import { IS_MAC } from '@blocksuite/global/env';
import { DisposableGroup } from '@blocksuite/global/utils';

import {
type EdgelessTool,
isMiddleButtonPressed,
isRightButtonPressed,
NoteDisplayMode,
} from '../../../_common/utils/index.js';
import type { Bound } from '../../../surface-block/utils/bound.js';
Expand Down Expand Up @@ -101,6 +103,10 @@ export class EdgelessToolsManager {
return this._controllers[this.edgelessTool.type];
}

get controllers() {
return this._controllers;
}

get draggingArea() {
if (!this.currentController.draggingArea) return null;

Expand All @@ -111,21 +117,23 @@ export class EdgelessToolsManager {
const maxY = Math.max(start.y, end.y);
return new DOMRect(minX, minY, maxX - minX, maxY - minY);
}

set spaceBar(pressed: boolean) {
this._spaceBar = pressed;
this.currentController.onPressSpaceBar(pressed);
}

get spaceBar() {
return this._spaceBar;
}
get shiftKey() {
return this._shiftKey;
}

set shiftKey(pressed: boolean) {
this._shiftKey = pressed;
this.currentController.onPressShiftKey(pressed);
}

set spaceBar(pressed: boolean) {
this._spaceBar = pressed;
this.currentController.onPressSpaceBar(pressed);
get shiftKey() {
return this._shiftKey;
}

get doc() {
Expand Down Expand Up @@ -241,7 +249,12 @@ export class EdgelessToolsManager {
// only allow pan tool in readonly mode
if (this.doc.readonly && this.edgelessTool.type !== 'pan') return;
// do nothing when holding right-key and not in pan mode
if (e.button === 2 && this.edgelessTool.type !== 'pan') return;
if (
e.button === 2 &&
this.edgelessTool.type !== 'pan' &&
this.edgelessTool.type !== 'copilot'
)
return;

return this.currentController.onContainerDragStart(e);
};
Expand All @@ -250,7 +263,12 @@ export class EdgelessToolsManager {
// only allow pan tool in readonly mode
if (this.doc.readonly && this.edgelessTool.type !== 'pan') return;
// do nothing when holding right-key and not in pan mode
if (e.button === 2 && this.edgelessTool.type !== 'pan') return;
if (
e.button === 2 &&
this.edgelessTool.type !== 'pan' &&
this.edgelessTool.type !== 'copilot'
)
return;

return this.currentController.onContainerDragMove(e);
};
Expand All @@ -259,7 +277,12 @@ export class EdgelessToolsManager {
// only allow pan tool in readonly mode
if (this.doc.readonly && this.edgelessTool.type !== 'pan') return;
// do nothing when holding right-key and not in pan mode
if (e.button === 2 && this.edgelessTool.type !== 'pan') return;
if (
e.button === 2 &&
this.edgelessTool.type !== 'pan' &&
this.edgelessTool.type !== 'copilot'
)
return;

return this.currentController.onContainerDragEnd(e);
};
Expand Down Expand Up @@ -290,33 +313,53 @@ export class EdgelessToolsManager {
};

private _onContainerPointerDown = (e: PointerEventState) => {
if (!isMiddleButtonPressed(e.raw)) {
if (this.doc.readonly) return;
const pointEvt = e.raw;
const metaKeyPressed = IS_MAC ? pointEvt.metaKey : pointEvt.ctrlKey;

return this.currentController.onContainerPointerDown(e);
if (
isMiddleButtonPressed(pointEvt) ||
isRightButtonPressed(pointEvt) ||
metaKeyPressed
) {
const isRightButton = isRightButtonPressed(pointEvt);
const targetTool = (
isRightButton || metaKeyPressed
? {
type: 'copilot',
}
: { type: 'pan', panning: true }
) as EdgelessTool;
const prevEdgelessTool = this._edgelessTool;
const targetButtonRelease = (_e: MouseEvent) =>
(isMiddleButtonPressed(e.raw) && !isMiddleButtonPressed(_e)) ||
(isRightButton && !isRightButtonPressed(_e)) ||
metaKeyPressed;

const switchToPreMode = (_e: MouseEvent) => {
if (targetButtonRelease(_e)) {
this.setEdgelessTool(
prevEdgelessTool,
undefined,
!isRightButton && !metaKeyPressed
);
document.removeEventListener('pointerup', switchToPreMode, false);
document.removeEventListener('pointerover', switchToPreMode, false);
}
};

this.dispatcher.disposables.addFromEvent(
document,
'pointerup',
switchToPreMode
);

this.setEdgelessTool(targetTool);
return;
}

const prevEdgelessTool = this._edgelessTool;
const switchToPreMode = (_e: MouseEvent) => {
if (!isMiddleButtonPressed(_e)) {
this.setEdgelessTool(prevEdgelessTool);
document.removeEventListener('pointerup', switchToPreMode, false);
document.removeEventListener('pointerover', switchToPreMode, false);
}
};
if (this.doc.readonly) return;

this.dispatcher.disposables.addFromEvent(
document,
'pointerover',
switchToPreMode
);
this.dispatcher.disposables.addFromEvent(
document,
'pointerup',
switchToPreMode
);

this.setEdgelessTool({ type: 'pan', panning: true });
return this.currentController.onContainerPointerDown(e);
};

private _onContainerPointerUp = (_ev: PointerEventState) => {};
Expand Down Expand Up @@ -353,7 +396,8 @@ export class EdgelessToolsManager {
state: EdgelessSelectionState | SurfaceSelection[] = {
elements: [],
editing: false,
}
},
restoreToLastSelection = true
) => {
const { type } = edgelessTool;
if (this.doc.readonly && type !== 'pan' && type !== 'frameNavigator') {
Expand All @@ -380,7 +424,8 @@ export class EdgelessToolsManager {
isDefaultType &&
isEmptyState &&
hasLastState &&
isNotSingleDocOnlyNote
isNotSingleDocOnlyNote &&
restoreToLastSelection
) {
state = this.selection.lastState;
}
Expand Down
Loading

1 comment on commit 1288de1

@github-actions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Size Report

Bundles

Entry Size Gzip Brotli
examples/basic 14.4 MB (+87.5 kB) 2.99 MB (+16.9 kB) 1.85 MB (+10.5 kB)

Packages

Name Size Gzip Brotli
blocks 2.25 MB (+7.04 kB) 525 kB (+1.67 kB) 383 kB (+578 B)
editor 84 B 89 B 63 B
store 83 B 88 B 63 B
inline 84 B 88 B 63 B

Please sign in to comment.