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