feat(whiteboard): cursor style when dragging

This commit is contained in:
austaras
2022-08-11 16:33:03 +08:00
committed by Austaras
parent 6060a81cc3
commit 5a23f67d31
5 changed files with 72 additions and 64 deletions

View File

@@ -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',

View File

@@ -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,

View File

@@ -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,
},
});
};
}

View File

@@ -84,6 +84,7 @@ export interface TDSnapshot {
isPenMode: boolean;
isReadonlyMode: boolean;
isZoomSnap: boolean;
forcePanning: boolean;
keepStyleMenuOpen: boolean;
nudgeDistanceSmall: number;
nudgeDistanceLarge: number;

View File

@@ -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;