mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-11 11:58:41 +00:00
feat(whiteboard): cursor style when dragging
This commit is contained in:
@@ -1,5 +1,13 @@
|
||||
/* eslint-disable max-lines */
|
||||
import * as React from 'react';
|
||||
import {
|
||||
memo,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useMemo,
|
||||
useState,
|
||||
type RefObject,
|
||||
} from 'react';
|
||||
import { Renderer } from '@tldraw/core';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import {
|
||||
@@ -132,13 +140,13 @@ export function Tldraw({
|
||||
getSession,
|
||||
tools,
|
||||
}: TldrawProps) {
|
||||
const [sId, set_sid] = React.useState(id);
|
||||
const [sId, setSid] = useState(id);
|
||||
const { pageClientWidth } = usePageClientWidth();
|
||||
// page padding left and right total 300px
|
||||
const editorShapeInitSize = pageClientWidth - 300;
|
||||
|
||||
// Create a new app when the component mounts.
|
||||
const [app, setApp] = React.useState(() => {
|
||||
const [app, setApp] = useState(() => {
|
||||
const app = new TldrawApp({
|
||||
id,
|
||||
callbacks,
|
||||
@@ -151,7 +159,7 @@ export function Tldraw({
|
||||
});
|
||||
|
||||
// Create a new app if the `id` prop changes.
|
||||
React.useLayoutEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (id === sId) return;
|
||||
const newApp = new TldrawApp({
|
||||
id,
|
||||
@@ -161,14 +169,14 @@ export function Tldraw({
|
||||
tools,
|
||||
});
|
||||
|
||||
set_sid(id);
|
||||
setSid(id);
|
||||
|
||||
setApp(newApp);
|
||||
}, [sId, id]);
|
||||
|
||||
// Update the document if the `document` prop changes but the ids,
|
||||
// are the same, or else load a new document if the ids are different.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!document) return;
|
||||
|
||||
if (document.id === app.document.id) {
|
||||
@@ -179,34 +187,34 @@ export function Tldraw({
|
||||
}, [document, app]);
|
||||
|
||||
// Disable assets when the `disableAssets` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
app.setDisableAssets(disableAssets);
|
||||
}, [app, disableAssets]);
|
||||
|
||||
// Change the page when the `currentPageId` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (!currentPageId) return;
|
||||
app.changePage(currentPageId);
|
||||
}, [currentPageId, app]);
|
||||
|
||||
// Toggle the app's readOnly mode when the `readOnly` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
app.readOnly = readOnly;
|
||||
}, [app, readOnly]);
|
||||
|
||||
// Toggle the app's darkMode when the `darkMode` prop changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (darkMode !== app.settings.isDarkMode) {
|
||||
app.toggleDarkMode();
|
||||
}
|
||||
}, [app, darkMode]);
|
||||
|
||||
// Update the app's callbacks when any callback changes.
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
app.callbacks = callbacks || {};
|
||||
}, [app, callbacks]);
|
||||
|
||||
React.useLayoutEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
if (typeof window === 'undefined') return;
|
||||
if (!window.document?.fonts) return;
|
||||
|
||||
@@ -260,7 +268,7 @@ interface InnerTldrawProps {
|
||||
showSponsorLink?: boolean;
|
||||
}
|
||||
|
||||
const InnerTldraw = React.memo(function InnerTldraw({
|
||||
const InnerTldraw = memo(function InnerTldraw({
|
||||
id,
|
||||
autofocus,
|
||||
showPages,
|
||||
@@ -276,7 +284,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
}: InnerTldrawProps) {
|
||||
const app = useTldrawApp();
|
||||
|
||||
const rWrapper = React.useRef<HTMLDivElement>(null);
|
||||
const rWrapper = useRef<HTMLDivElement>(null);
|
||||
|
||||
const state = app.useStore();
|
||||
|
||||
@@ -299,7 +307,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
TLDR.get_shape_util(page.shapes[selectedIds[0]].type).hideResizeHandles;
|
||||
|
||||
// Custom rendering meta, with dark mode for shapes
|
||||
const meta: TDMeta = React.useMemo(() => {
|
||||
const meta: TDMeta = useMemo(() => {
|
||||
return { isDarkMode: settings.isDarkMode, app };
|
||||
}, [settings.isDarkMode, app]);
|
||||
|
||||
@@ -308,7 +316,7 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
: appState.selectByContain;
|
||||
|
||||
// Custom theme, based on darkmode
|
||||
const theme = React.useMemo(() => {
|
||||
const theme = useMemo(() => {
|
||||
const { selectByContain } = appState;
|
||||
const { isDarkMode, isCadSelectMode } = settings;
|
||||
|
||||
@@ -373,9 +381,11 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
!isSelecting ||
|
||||
!settings.showCloneHandles ||
|
||||
pageState.camera.zoom < 0.2;
|
||||
|
||||
return (
|
||||
<StyledLayout
|
||||
ref={rWrapper}
|
||||
panning={settings.forcePanning}
|
||||
tabIndex={-0}
|
||||
penColor={app?.appState?.currentStyle?.stroke}
|
||||
>
|
||||
@@ -477,17 +487,17 @@ const InnerTldraw = React.memo(function InnerTldraw({
|
||||
);
|
||||
});
|
||||
|
||||
const OneOff = React.memo(function OneOff({
|
||||
const OneOff = memo(function OneOff({
|
||||
focusableRef,
|
||||
autofocus,
|
||||
}: {
|
||||
autofocus?: boolean;
|
||||
focusableRef: React.RefObject<HTMLDivElement>;
|
||||
focusableRef: RefObject<HTMLDivElement>;
|
||||
}) {
|
||||
useKeyboardShortcuts(focusableRef);
|
||||
useStylesheet();
|
||||
|
||||
React.useEffect(() => {
|
||||
useEffect(() => {
|
||||
if (autofocus) {
|
||||
focusableRef.current?.focus();
|
||||
}
|
||||
@@ -496,8 +506,8 @@ const OneOff = React.memo(function OneOff({
|
||||
return null;
|
||||
});
|
||||
|
||||
const StyledLayout = styled('div')<{ penColor: string }>(
|
||||
({ theme, penColor }) => {
|
||||
const StyledLayout = styled('div')<{ penColor: string; panning: boolean }>(
|
||||
({ theme, panning, penColor }) => {
|
||||
return {
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
@@ -509,6 +519,7 @@ const StyledLayout = styled('div')<{ penColor: string }>(
|
||||
overflow: 'hidden',
|
||||
boxSizing: 'border-box',
|
||||
outline: 'none',
|
||||
cursor: panning ? 'grab' : 'unset',
|
||||
|
||||
'& .tl-container': {
|
||||
position: 'absolute',
|
||||
|
||||
@@ -219,8 +219,6 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
isPointing = false;
|
||||
|
||||
isForcePanning = false;
|
||||
|
||||
editingStartTime = -1;
|
||||
|
||||
fileSystemHandle: FileSystemHandle | null = null;
|
||||
@@ -262,7 +260,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
constructor(props: TldrawAppCtorProps) {
|
||||
super(
|
||||
TldrawApp.default_state,
|
||||
TldrawApp.defaultState,
|
||||
props.id,
|
||||
TldrawApp.version,
|
||||
(prev, next, prevVersion) => {
|
||||
@@ -326,9 +324,9 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
);
|
||||
|
||||
this.patchState({
|
||||
...TldrawApp.default_state,
|
||||
...TldrawApp.defaultState,
|
||||
appState: {
|
||||
...TldrawApp.default_state.appState,
|
||||
...TldrawApp.defaultState.appState,
|
||||
status: TDStatus.Idle,
|
||||
},
|
||||
});
|
||||
@@ -1473,13 +1471,13 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
this.replace_state(
|
||||
{
|
||||
...TldrawApp.default_state,
|
||||
...TldrawApp.defaultState,
|
||||
settings: {
|
||||
...this.state.settings,
|
||||
},
|
||||
document: migrate(document, TldrawApp.version),
|
||||
appState: {
|
||||
...TldrawApp.default_state.appState,
|
||||
...TldrawApp.defaultState.appState,
|
||||
...this.state.appState,
|
||||
currentPageId: Object.keys(document.pages)[0],
|
||||
disableAssets: this.disableAssets,
|
||||
@@ -3913,7 +3911,11 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
break;
|
||||
}
|
||||
case ' ': {
|
||||
this.isForcePanning = true;
|
||||
this.patchState({
|
||||
settings: {
|
||||
forcePanning: true,
|
||||
},
|
||||
});
|
||||
this.spaceKey = true;
|
||||
break;
|
||||
}
|
||||
@@ -3976,7 +3978,12 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
break;
|
||||
}
|
||||
case ' ': {
|
||||
this.isForcePanning = false;
|
||||
this.patchState({
|
||||
settings: {
|
||||
forcePanning:
|
||||
this.currentTool.type === TDShapeType.HandDraw,
|
||||
},
|
||||
});
|
||||
this.spaceKey = false;
|
||||
break;
|
||||
}
|
||||
@@ -4069,7 +4076,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
this.pan(delta);
|
||||
|
||||
// When panning, we also want to call onPointerMove, except when "force panning" via spacebar / middle wheel button (it's called elsewhere in that case)
|
||||
if (!this.isForcePanning)
|
||||
if (!this.useStore.getState().settings.forcePanning)
|
||||
this.onPointerMove(info, e as unknown as React.PointerEvent);
|
||||
};
|
||||
|
||||
@@ -4098,7 +4105,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||
this.previousPoint = this.currentPoint;
|
||||
this.updateInputs(info, e);
|
||||
if (this.isForcePanning && this.isPointing) {
|
||||
if (this.useStore.getState().settings.forcePanning && this.isPointing) {
|
||||
this.onPan?.(
|
||||
{ ...info, delta: Vec.neg(info.delta) },
|
||||
e as unknown as WheelEvent
|
||||
@@ -4122,20 +4129,23 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
|
||||
onPointerDown: TLPointerEventHandler = (info, e) => {
|
||||
if (e.buttons === 4) {
|
||||
this.isForcePanning = true;
|
||||
this.patchState({
|
||||
settings: {
|
||||
forcePanning: true,
|
||||
},
|
||||
});
|
||||
} else if (this.isPointing) {
|
||||
return;
|
||||
}
|
||||
this.isPointing = true;
|
||||
this.originPoint = this.getPagePoint(info.point).concat(info.pressure);
|
||||
this.updateInputs(info, e);
|
||||
if (this.isForcePanning) return;
|
||||
if (this.useStore.getState().settings.forcePanning) return;
|
||||
this.currentTool.onPointerDown?.(info, e);
|
||||
};
|
||||
|
||||
onPointerUp: TLPointerEventHandler = (info, e) => {
|
||||
this.isPointing = false;
|
||||
if (!this.shiftKey) this.isForcePanning = false;
|
||||
this.updateInputs(info, e);
|
||||
this.currentTool.onPointerUp?.(info, e);
|
||||
};
|
||||
@@ -4522,7 +4532,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
assets: {},
|
||||
};
|
||||
|
||||
static default_state: TDSnapshot = {
|
||||
static defaultState: TDSnapshot = {
|
||||
settings: {
|
||||
isCadSelectMode: false,
|
||||
isPenMode: false,
|
||||
@@ -4532,6 +4542,7 @@ export class TldrawApp extends StateManager<TDSnapshot> {
|
||||
isSnapping: false,
|
||||
isDebugMode: false,
|
||||
isReadonlyMode: false,
|
||||
forcePanning: false,
|
||||
keepStyleMenuOpen: false,
|
||||
nudgeDistanceLarge: 16,
|
||||
nudgeDistanceSmall: 1,
|
||||
|
||||
@@ -18,34 +18,19 @@ export class HandDrawTool extends BaseTool {
|
||||
|
||||
/* ----------------- Event Handlers ----------------- */
|
||||
|
||||
override onPointerDown: TLPointerEventHandler = () => {
|
||||
if (this.app.readOnly) return;
|
||||
if (this.status !== Status.Idle) return;
|
||||
|
||||
this.set_status(Status.Pointing);
|
||||
override onEnter = () => {
|
||||
this.app.patchState({
|
||||
settings: {
|
||||
forcePanning: true,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
override onPointerMove: TLPointerEventHandler = (info, e) => {
|
||||
if (this.app.readOnly) return;
|
||||
const delta = Vec.div(info.delta, this.app.camera.zoom);
|
||||
const prev = this.app.camera.point;
|
||||
const next = Vec.sub(prev, delta);
|
||||
if (Vec.isEqual(next, prev)) return;
|
||||
|
||||
switch (this.status) {
|
||||
case Status.Pointing: {
|
||||
this.app.pan(Vec.neg(delta));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
override onPointerUp: TLPointerEventHandler = () => {
|
||||
this.set_status(Status.Idle);
|
||||
};
|
||||
|
||||
override onCancel = () => {
|
||||
this.set_status(Status.Idle);
|
||||
override onExit = () => {
|
||||
this.app.patchState({
|
||||
settings: {
|
||||
forcePanning: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ export interface TDSnapshot {
|
||||
isPenMode: boolean;
|
||||
isReadonlyMode: boolean;
|
||||
isZoomSnap: boolean;
|
||||
forcePanning: boolean;
|
||||
keepStyleMenuOpen: boolean;
|
||||
nudgeDistanceSmall: number;
|
||||
nudgeDistanceLarge: number;
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { PluginRenderRoot } from '../../utils';
|
||||
import { Subject, throttleTime } from 'rxjs';
|
||||
import { domToRect, last, Point } from '@toeverything/utils';
|
||||
const DRAG_THROTTLE_DELAY = 150;
|
||||
const DRAG_THROTTLE_DELAY = 60;
|
||||
export class LeftMenuPlugin extends BasePlugin {
|
||||
private _mousedown?: boolean;
|
||||
private _root?: PluginRenderRoot;
|
||||
|
||||
Reference in New Issue
Block a user