Start scrolling if we are dragging close to the window edges. (#2299)

Start scrolling when we get close to the edges of the window. This works
for brush selecting, translating, and resizing.


https://github.com/tldraw/tldraw/assets/2523721/4a5effc8-5445-411b-b317-36097233d36c


### Change Type

- [ ] `patch` — Bug fix
- [x] `minor` — New feature
- [ ] `major` — Breaking change
- [ ] `dependencies` — Changes to package dependencies[^1]
- [ ] `documentation` — Changes to the documentation only[^2]
- [ ] `tests` — Changes to any test code only[^2]
- [ ] `internal` — Any other changes that don't affect the published
package[^2]
- [ ] I don't know

[^1]: publishes a `patch` release, for devDependencies use `internal`
[^2]: will not publish a new version

### Test Plan

1. Select a shape.
2. Move it towards the edge of the window. The camera position should
change.
3. Also try resizing, brush selecting.

- [x] Unit Tests
- [ ] End to end tests

### Release Notes

- Adds the logic to change the camera position when you get close to the
edges of the window. This allows you to drag, resize, brush select past
the edges of the current viewport.

---------

Co-authored-by: Steve Ruiz <steveruizok@gmail.com>
playback
Mitja Bezenšek 2023-12-16 00:37:03 +01:00 zatwierdzone przez GitHub
rodzic 0171d1498d
commit 4e50c9c162
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
26 zmienionych plików z 428 dodań i 15 usunięć

Wyświetl plik

@ -77,6 +77,8 @@
"action.toggle-dark-mode": "Toggle dark mode",
"action.toggle-reduce-motion.menu": "Reduce motion",
"action.toggle-reduce-motion": "Toggle reduce motion",
"action.toggle-edge-scrolling.menu": "Edge scrolling",
"action.toggle-edge-scrolling": "Toggle edge scrolling",
"action.toggle-debug-mode.menu": "Debug mode",
"action.toggle-debug-mode": "Toggle debug mode",
"action.toggle-focus-mode.menu": "Focus mode",

Wyświetl plik

@ -1,7 +1,5 @@
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"packages": [
"packages/*"
],
"packages": ["packages/*"],
"version": "2.0.0-alpha.19"
}

Wyświetl plik

@ -488,6 +488,7 @@ export const defaultUserPreferences: Readonly<{
locale: "ar" | "ca" | "da" | "de" | "en" | "es" | "fa" | "fi" | "fr" | "gl" | "he" | "hi-in" | "hu" | "it" | "ja" | "ko-kr" | "ku" | "my" | "ne" | "no" | "pl" | "pt-br" | "pt-pt" | "ro" | "ru" | "sv" | "te" | "th" | "tr" | "uk" | "vi" | "zh-cn" | "zh-tw";
color: "#02B1CC" | "#11B3A3" | "#39B178" | "#55B467" | "#7B66DC" | "#9D5BD2" | "#BD54C6" | "#E34BA9" | "#EC5E41" | "#F04F88" | "#F2555A" | "#FF802B";
isDarkMode: false;
edgeScrollSpeed: 1;
animationSpeed: 0 | 1;
isSnapMode: false;
}>;
@ -1459,6 +1460,9 @@ export const MAX_ZOOM = 8;
// @internal (undocumented)
export const MIN_ZOOM = 0.1;
// @public
export function moveCameraWhenCloseToEdge(editor: Editor): void;
// @internal (undocumented)
export const MULTI_CLICK_DURATION = 200;
@ -1963,6 +1967,8 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// (undocumented)
onRightClick?: TLEventHandlers['onRightClick'];
// (undocumented)
onTick?: TLTickEventHandler;
// (undocumented)
onTripleClick?: TLEventHandlers['onTripleClick'];
// (undocumented)
onWheel?: TLEventHandlers['onWheel'];
@ -2702,6 +2708,9 @@ export type TLSvgOptions = {
// @public (undocumented)
export type TLTickEvent = (elapsed: number) => void;
// @public (undocumented)
export type TLTickEventHandler = () => void;
// @public
export interface TLUserPreferences {
// (undocumented)
@ -2709,6 +2718,8 @@ export interface TLUserPreferences {
// (undocumented)
color?: null | string;
// (undocumented)
edgeScrollSpeed?: null | number;
// (undocumented)
id: string;
// (undocumented)
isDarkMode?: boolean | null;

Wyświetl plik

@ -6126,7 +6126,7 @@
},
{
"kind": "Content",
"text": "<{\n name: \"New User\";\n locale: \"ar\" | \"ca\" | \"da\" | \"de\" | \"en\" | \"es\" | \"fa\" | \"fi\" | \"fr\" | \"gl\" | \"he\" | \"hi-in\" | \"hu\" | \"it\" | \"ja\" | \"ko-kr\" | \"ku\" | \"my\" | \"ne\" | \"no\" | \"pl\" | \"pt-br\" | \"pt-pt\" | \"ro\" | \"ru\" | \"sv\" | \"te\" | \"th\" | \"tr\" | \"uk\" | \"vi\" | \"zh-cn\" | \"zh-tw\";\n color: \"#02B1CC\" | \"#11B3A3\" | \"#39B178\" | \"#55B467\" | \"#7B66DC\" | \"#9D5BD2\" | \"#BD54C6\" | \"#E34BA9\" | \"#EC5E41\" | \"#F04F88\" | \"#F2555A\" | \"#FF802B\";\n isDarkMode: false;\n animationSpeed: 0 | 1;\n isSnapMode: false;\n}>"
"text": "<{\n name: \"New User\";\n locale: \"ar\" | \"ca\" | \"da\" | \"de\" | \"en\" | \"es\" | \"fa\" | \"fi\" | \"fr\" | \"gl\" | \"he\" | \"hi-in\" | \"hu\" | \"it\" | \"ja\" | \"ko-kr\" | \"ku\" | \"my\" | \"ne\" | \"no\" | \"pl\" | \"pt-br\" | \"pt-pt\" | \"ro\" | \"ru\" | \"sv\" | \"te\" | \"th\" | \"tr\" | \"uk\" | \"vi\" | \"zh-cn\" | \"zh-tw\";\n color: \"#02B1CC\" | \"#11B3A3\" | \"#39B178\" | \"#55B467\" | \"#7B66DC\" | \"#9D5BD2\" | \"#BD54C6\" | \"#E34BA9\" | \"#EC5E41\" | \"#F04F88\" | \"#F2555A\" | \"#FF802B\";\n isDarkMode: false;\n edgeScrollSpeed: 1;\n animationSpeed: 0 | 1;\n isSnapMode: false;\n}>"
}
],
"fileUrlPath": "packages/editor/src/lib/config/TLUserPreferences.ts",
@ -28314,6 +28314,52 @@
],
"extendsTokenRanges": []
},
{
"kind": "Function",
"canonicalReference": "@tldraw/editor!moveCameraWhenCloseToEdge:function(1)",
"docComment": "/**\n * Moves the camera when the mouse is close to the edge of the screen.\n *\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function moveCameraWhenCloseToEdge(editor: "
},
{
"kind": "Reference",
"text": "Editor",
"canonicalReference": "@tldraw/editor!Editor:class"
},
{
"kind": "Content",
"text": "): "
},
{
"kind": "Content",
"text": "void"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/editor/src/lib/utils/edgeScrolling.ts",
"returnTypeTokenRange": {
"startIndex": 3,
"endIndex": 4
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [
{
"parameterName": "editor",
"parameterTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isOptional": false
}
],
"name": "moveCameraWhenCloseToEdge"
},
{
"kind": "Function",
"canonicalReference": "@tldraw/editor!openWindow:function(1)",
@ -36443,6 +36489,37 @@
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#onTick:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "onTick?: "
},
{
"kind": "Reference",
"text": "TLTickEventHandler",
"canonicalReference": "@tldraw/editor!TLTickEventHandler:type"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
"name": "onTick",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"isStatic": false,
"isProtected": false,
"isAbstract": false
},
{
"kind": "Property",
"canonicalReference": "@tldraw/editor!StateNode#onTripleClick:member",
@ -43331,6 +43408,32 @@
"endIndex": 2
}
},
{
"kind": "TypeAlias",
"canonicalReference": "@tldraw/editor!TLTickEventHandler:type",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export type TLTickEventHandler = "
},
{
"kind": "Content",
"text": "() => void"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "packages/editor/src/lib/editor/types/event-types.ts",
"releaseTag": "Public",
"name": "TLTickEventHandler",
"typeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "Interface",
"canonicalReference": "@tldraw/editor!TLUserPreferences:interface",
@ -43400,6 +43503,33 @@
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/editor!TLUserPreferences#edgeScrollSpeed:member",
"docComment": "",
"excerptTokens": [
{
"kind": "Content",
"text": "edgeScrollSpeed?: "
},
{
"kind": "Content",
"text": "null | number"
},
{
"kind": "Content",
"text": ";"
}
],
"isReadonly": false,
"isOptional": true,
"releaseTag": "Public",
"name": "edgeScrollSpeed",
"propertyTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
}
},
{
"kind": "PropertySignature",
"canonicalReference": "@tldraw/editor!TLUserPreferences#id:member",

Wyświetl plik

@ -225,6 +225,7 @@ export {
type TLPointerEventName,
type TLPointerEventTarget,
type TLTickEvent,
type TLTickEventHandler,
type TLWheelEvent,
type TLWheelEventInfo,
type UiEvent,
@ -346,6 +347,7 @@ export {
setPointerCapture,
stopEventPropagation,
} from './lib/utils/dom'
export { moveCameraWhenCloseToEdge } from './lib/utils/edgeScrolling'
export { getIncrementedName } from './lib/utils/getIncrementedName'
export { getPointerInfo } from './lib/utils/getPointerInfo'
export { getSvgPathFromPoints } from './lib/utils/getSvgPathFromPoints'

Wyświetl plik

@ -18,6 +18,7 @@ export interface TLUserPreferences {
color?: string | null
isDarkMode?: boolean | null
animationSpeed?: number | null
edgeScrollSpeed?: number | null
isSnapMode?: boolean | null
}
@ -39,6 +40,7 @@ const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUserPrefere
color: T.string.nullable().optional(),
isDarkMode: T.boolean.nullable().optional(),
animationSpeed: T.number.nullable().optional(),
edgeScrollSpeed: T.number.nullable().optional(),
isSnapMode: T.boolean.nullable().optional(),
})
@ -46,10 +48,11 @@ const Versions = {
AddAnimationSpeed: 1,
AddIsSnapMode: 2,
MakeFieldsNullable: 3,
AddEdgeScrollSpeed: 4,
} as const
const userMigrations = defineMigrations({
currentVersion: Versions.MakeFieldsNullable,
currentVersion: Versions.AddEdgeScrollSpeed,
migrators: {
[Versions.AddAnimationSpeed]: {
up: (user) => {
@ -86,6 +89,17 @@ const userMigrations = defineMigrations({
}
},
},
[Versions.AddEdgeScrollSpeed]: {
up: (user: TLUserPreferences) => {
return {
...user,
edgeScrollSpeed: 1,
}
},
down: ({ edgeScrollSpeed: _, ...user }: TLUserPreferences) => {
return user
},
},
},
})
@ -131,6 +145,7 @@ export const defaultUserPreferences = Object.freeze({
locale: getDefaultTranslationLocale(),
color: getRandomColor(),
isDarkMode: false,
edgeScrollSpeed: 1,
animationSpeed: userPrefersReducedMotion() ? 0 : 1,
isSnapMode: false,
}) satisfies Readonly<Omit<TLUserPreferences, 'id'>>

Wyświetl plik

@ -92,3 +92,6 @@ export const CAMERA_MAX_RENDERING_INTERVAL = 620
/** @public */
export const HIT_TEST_MARGIN = 8
/** @internal */
export const EDGE_SCROLL_SPEED = 20

Wyświetl plik

@ -52,6 +52,13 @@ export class UserPreferencesManager {
return this.getIsDarkMode()
}
/**
* The speed at which the user can scroll by dragging toward the edge of the screen.
*/
@computed getEdgeScrollSpeed() {
return this.user.userPreferences.get().edgeScrollSpeed ?? defaultUserPreferences.edgeScrollSpeed
}
@computed getAnimationSpeed() {
return this.user.userPreferences.get().animationSpeed ?? defaultUserPreferences.animationSpeed
}

Wyświetl plik

@ -8,6 +8,7 @@ import {
TLEventInfo,
TLExitEventHandler,
TLPinchEventInfo,
TLTickEventHandler,
} from '../types/event-types'
type TLStateNodeType = 'branch' | 'leaf' | 'root'
@ -155,6 +156,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
enter = (info: any, from: string) => {
this._isActive.set(true)
this.onEnter?.(info, from)
if (this.onTick) this.editor.on('tick', this.onTick)
if (this.children && this.initial && this.getIsActive()) {
const initial = this.children[this.initial]
this._current.set(initial)
@ -165,6 +167,7 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
// todo: move this logic into transition
exit = (info: any, from: string) => {
this._isActive.set(false)
if (this.onTick) this.editor.off('tick', this.onTick)
this.onExit?.(info, from)
if (!this.getIsActive()) {
this.getCurrent()?.exit(info, from)
@ -223,4 +226,5 @@ export abstract class StateNode implements Partial<TLEventHandlers> {
onEnter?: TLEnterEventHandler
onExit?: TLExitEventHandler
onTick?: TLTickEventHandler
}

Wyświetl plik

@ -136,6 +136,8 @@ export type UiEvent =
| TLCancelEvent
| TLCompleteEvent
/** @public */
export type TLTickEventHandler = () => void
/** @public */
export type TLEnterEventHandler = (info: any, from: string) => void
/** @public */

Wyświetl plik

@ -0,0 +1,66 @@
import { EDGE_SCROLL_SPEED } from '../constants'
import { Editor } from '../editor/Editor'
/**
* Helper function to get the scroll offset for a given position.
* The closer the mouse is to the edge of the screen the faster we scroll.
* We also adjust the speed and the start offset based on the screen size and zoom level.
*
* @param editor - The mouse position on the screen in pixels
* @returns How much we should scroll in pixels
* @internal
*/
export function getEdgeProximityFactor(position: number, scrollOffset: number, extreme: number) {
if (position < 0) {
return 1
} else if (position > extreme) {
return -1
} else if (position < scrollOffset) {
return (scrollOffset - position) / scrollOffset
} else if (position > extreme - scrollOffset) {
return -(scrollOffset - extreme + position) / scrollOffset
}
return 0
}
/**
* Moves the camera when the mouse is close to the edge of the screen.
* @public
*/
export function moveCameraWhenCloseToEdge(editor: Editor) {
if (!editor.inputs.isDragging || editor.inputs.isPanning) return
const {
inputs: {
currentScreenPoint: { x, y },
},
} = editor
const zoomLevel = editor.getZoomLevel()
const screenBounds = editor.getViewportScreenBounds()
// Determines how far from the edges we start the scroll behaviour
const insetX = screenBounds.w < 1000 ? 40 : 32
const insetY = screenBounds.h < 1000 ? 40 : 32
// Determines how much the speed is affected by the screen size
const screenSizeFactorX = screenBounds.w < 1000 ? 0.612 : 1
const screenSizeFactorY = screenBounds.h < 1000 ? 0.612 : 1
// Determines the base speed of the scroll
const pxSpeed = editor.user.getEdgeScrollSpeed() * EDGE_SCROLL_SPEED
const proximityFactorX = getEdgeProximityFactor(x, insetX, screenBounds.w)
const proximityFactorY = getEdgeProximityFactor(y, insetY, screenBounds.h)
if (proximityFactorX === 0 && proximityFactorY === 0) return
const scrollDeltaX = (pxSpeed * proximityFactorX * screenSizeFactorX) / zoomLevel
const scrollDeltaY = (pxSpeed * proximityFactorY * screenSizeFactorY) / zoomLevel
const camera = editor.getCamera()
editor.setCamera({
x: camera.x + scrollDeltaX,
y: camera.y + scrollDeltaY,
})
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Wyświetl plik

@ -13,7 +13,9 @@ import {
TLPointerEventInfo,
TLShape,
TLShapeId,
TLTickEventHandler,
Vec2d,
moveCameraWhenCloseToEdge,
pointInPolygon,
polygonsIntersect,
} from '@tldraw/editor'
@ -60,6 +62,10 @@ export class Brushing extends StateNode {
this.editor.updateInstanceState({ brush: null })
}
override onTick: TLTickEventHandler = () => {
moveCameraWhenCloseToEdge(this.editor)
}
override onPointerMove = () => {
this.hitTestShapes()
}

Wyświetl plik

@ -13,10 +13,12 @@ import {
TLShape,
TLShapeId,
TLShapePartial,
TLTickEventHandler,
Vec2d,
VecLike,
areAnglesCompatible,
compact,
moveCameraWhenCloseToEdge,
} from '@tldraw/editor'
type ResizingInfo = TLPointerEventInfo & {
@ -72,6 +74,10 @@ export class Resizing extends StateNode {
this.updateShapes()
}
override onTick: TLTickEventHandler = () => {
moveCameraWhenCloseToEdge(this.editor)
}
override onPointerMove: TLEventHandlers['onPointerMove'] = () => {
this.updateShapes()
}

Wyświetl plik

@ -10,9 +10,11 @@ import {
TLPointerEventInfo,
TLShape,
TLShapePartial,
TLTickEventHandler,
Vec2d,
compact,
isPageId,
moveCameraWhenCloseToEdge,
} from '@tldraw/editor'
import { DragAndDropManager } from '../DragAndDropManager'
@ -77,12 +79,10 @@ export class Translating extends StateNode {
this.snapshot = this.selectionSnapshot
this.handleStart()
this.updateShapes()
this.editor.on('tick', this.updateParent)
}
override onExit = () => {
this.parent.setCurrentToolIdMask(undefined)
this.editor.off('tick', this.updateParent)
this.selectionSnapshot = {} as any
this.snapshot = {} as any
this.editor.snaps.clear()
@ -93,6 +93,14 @@ export class Translating extends StateNode {
this.dragAndDropManager.clear()
}
override onTick: TLTickEventHandler = () => {
this.dragAndDropManager.updateDroppingNode(
this.snapshot.movingShapes,
this.updateParentTransforms
)
moveCameraWhenCloseToEdge(this.editor)
}
override onPointerMove = () => {
this.updateShapes()
}
@ -153,11 +161,6 @@ export class Translating extends StateNode {
this.updateShapes()
}
updateParent = () => {
const { snapshot } = this
this.dragAndDropManager.updateDroppingNode(snapshot.movingShapes, this.updateParentTransforms)
}
reset() {
this.editor.bailToMark(this.markId)
}

Wyświetl plik

@ -994,6 +994,19 @@ export function ActionsProvider({ overrides, children }: ActionsProviderProps) {
},
checkbox: true,
},
{
id: 'toggle-edge-scrolling',
label: 'action.toggle-edge-scrolling',
menuLabel: 'action.toggle-edge-scrolling.menu',
readonlyOk: true,
onSelect(source) {
trackEvent('toggle-edge-scrolling', { source })
editor.user.updateUserPreferences({
edgeScrollSpeed: editor.user.getEdgeScrollSpeed() === 0 ? 1 : 0,
})
},
checkbox: true,
},
{
id: 'toggle-transparent',
label: 'action.toggle-transparent',

Wyświetl plik

@ -81,6 +81,7 @@ export interface TLUiEventMap {
'toggle-debug-mode': null
'toggle-lock': null
'toggle-reduce-motion': null
'toggle-edge-scrolling': null
'exit-pen-mode': null
'stop-following': null
'open-cursor-chat': null

Wyświetl plik

@ -51,6 +51,9 @@ export function TLUiMenuSchemaProvider({ overrides, children }: TLUiMenuSchemaPr
const isDarkMode = useValue('isDarkMode', () => editor.user.getIsDarkMode(), [editor])
const animationSpeed = useValue('animationSpeed', () => editor.user.getAnimationSpeed(), [editor])
const edgeScrollSpeed = useValue('edgeScrollSpeed', () => editor.user.getEdgeScrollSpeed(), [
editor,
])
const isGridMode = useValue('isGridMode', () => editor.getInstanceState().isGridMode, [editor])
const isSnapMode = useValue('isSnapMode', () => editor.user.getIsSnapMode(), [editor])
const isToolLock = useValue('isToolLock', () => editor.getInstanceState().isToolLocked, [editor])
@ -214,6 +217,7 @@ export function TLUiMenuSchemaProvider({ overrides, children }: TLUiMenuSchemaPr
menuItem(actions['toggle-grid'], { checked: isGridMode }),
menuItem(actions['toggle-dark-mode'], { checked: isDarkMode }),
menuItem(actions['toggle-focus-mode'], { checked: isFocusMode }),
menuItem(actions['toggle-edge-scrolling'], { checked: edgeScrollSpeed === 1 }),
menuItem(actions['toggle-reduce-motion'], { checked: animationSpeed === 0 }),
menuItem(actions['toggle-debug-mode'], { checked: isDebugMode })
)
@ -258,6 +262,7 @@ export function TLUiMenuSchemaProvider({ overrides, children }: TLUiMenuSchemaPr
isFocusMode,
exportBackground,
isDebugMode,
edgeScrollSpeed,
isZoomedTo100,
oneEmbeddableBookmarkSelected,
oneEmbedSelected,

Wyświetl plik

@ -81,6 +81,8 @@ export type TLUiTranslationKey =
| 'action.toggle-dark-mode'
| 'action.toggle-reduce-motion.menu'
| 'action.toggle-reduce-motion'
| 'action.toggle-edge-scrolling.menu'
| 'action.toggle-edge-scrolling'
| 'action.toggle-debug-mode.menu'
| 'action.toggle-debug-mode'
| 'action.toggle-focus-mode.menu'

Wyświetl plik

@ -81,6 +81,8 @@ export const DEFAULT_TRANSLATION = {
'action.toggle-dark-mode': 'Toggle dark mode',
'action.toggle-reduce-motion.menu': 'Reduce motion',
'action.toggle-reduce-motion': 'Toggle reduce motion',
'action.toggle-edge-scrolling.menu': 'Edge scrolling',
'action.toggle-edge-scrolling': 'Toggle edge scrolling',
'action.toggle-debug-mode.menu': 'Debug mode',
'action.toggle-debug-mode': 'Toggle debug mode',
'action.toggle-focus-mode.menu': 'Focus mode',

Wyświetl plik

@ -109,15 +109,18 @@ describe('TLUserPreferences', () => {
userPreferences,
}),
})
// called once in the constructor of testeditor to set edge scroll speed to 0
expect(setUserPreferences).toHaveBeenCalledTimes(1)
expect(editor.user.getName()).toBe('blah')
editor.user.updateUserPreferences({ name: null })
expect(editor.user.getName()).toBe('New User')
expect(setUserPreferences).toHaveBeenCalledTimes(1)
expect(setUserPreferences).toHaveBeenCalledTimes(2)
expect(setUserPreferences).toHaveBeenLastCalledWith({
id: '123',
name: null,
edgeScrollSpeed: 0,
})
})
})

Wyświetl plik

@ -109,6 +109,9 @@ export class TestEditor extends Editor {
})
return [{ box, text: textToMeasure }]
}
// Turn off edge scrolling for tests. Tests that require this can turn it back on.
this.user.updateUserPreferences({ edgeScrollSpeed: 0 })
}
elm: HTMLDivElement

Wyświetl plik

@ -3899,3 +3899,25 @@ describe('Resizing text from the right edge', () => {
})
})
})
describe('When resizing near the edges of the screen', () => {
it('resizes past the edge of the screen', () => {
editor.user.updateUserPreferences({ edgeScrollSpeed: 1 })
editor
.select(ids.boxA)
.pointerDown(10, 10, {
type: 'pointer',
target: 'selection',
handle: 'top_left',
})
.expectShapeToMatch({ id: ids.boxA, x: 10, y: 10, props: { w: 100, h: 100 } })
.pointerMove(10, 25)
jest.advanceTimersByTime(1000)
editor.expectShapeToMatch({
id: ids.boxA,
x: -842.5,
y: -259.58,
props: { w: 952.5, h: 369.58 },
})
})
})

Wyświetl plik

@ -1708,3 +1708,45 @@ describe('right clicking', () => {
expect(editor.getSelectedShapeIds()).toEqual([])
})
})
describe('When brushing close to the edges of the screen', () => {
it('selects shapes that are outside of the viewport', () => {
editor.user.updateUserPreferences({ edgeScrollSpeed: 1 })
editor.createShapes([{ id: ids.box1, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } }])
editor.createShapes([
{ id: ids.box2, type: 'geo', x: -150, y: -150, props: { w: 100, h: 100 } },
])
editor.pointerMove(300, 300)
editor.pointerDown()
editor.pointerMove(50, 50)
editor.expectToBeIn('select.brushing')
expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
editor.pointerMove(0, 0)
// still only box 1...
expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
jest.advanceTimersByTime(100)
// ...but now viewport will have moved to select box2 as well
expect(editor.getSelectedShapeIds()).toEqual([ids.box1, ids.box2])
editor.pointerUp()
})
it('doesnt edge scroll to the other shape', () => {
editor.user.updateUserPreferences({ edgeScrollSpeed: 0 }) // <-- no edge scrolling
editor.createShapes([{ id: ids.box1, type: 'geo', x: 100, y: 100, props: { w: 100, h: 100 } }])
editor.createShapes([
{ id: ids.box2, type: 'geo', x: -150, y: -150, props: { w: 100, h: 100 } },
])
editor.pointerMove(300, 300)
editor.pointerDown()
editor.pointerMove(50, 50)
editor.expectToBeIn('select.brushing')
expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
editor.pointerMove(0, 0)
expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
jest.advanceTimersByTime(100)
expect(editor.getSelectedShapeIds()).toEqual([ids.box1])
editor.pointerUp()
})
})

Wyświetl plik

@ -127,6 +127,40 @@ describe('When translating...', () => {
.expectShapeToMatch({ id: ids.box1, x: 60, y: 60 })
})
it('translates a single shape near the top left edge', () => {
editor.user.updateUserPreferences({ edgeScrollSpeed: 1 })
editor.pointerDown(50, 50, ids.box1).pointerMove(0, 50) // [-50, 0]
jest.advanceTimersByTime(100)
editor
// The change is bigger than expected because the camera moves
.expectShapeToMatch({ id: ids.box1, x: -160, y: 10 })
// We'll continue moving in the x postion, but now we'll also move in the y position.
// The speed in the y position is smaller since we are further away from the edge.
.pointerMove(0, 25)
jest.advanceTimersByTime(100)
editor
.expectShapeToMatch({ id: ids.box1, x: -280, y: -42.54 })
.pointerUp()
.expectShapeToMatch({ id: ids.box1, x: -280, y: -42.54 })
})
it('translates a single shape near the bottom right edge', () => {
editor.user.updateUserPreferences({ edgeScrollSpeed: 1 })
editor.pointerDown(50, 50, ids.box1).pointerMove(1080, 50)
jest.advanceTimersByTime(100)
editor
// The change is bigger than expected because the camera moves
.expectShapeToMatch({ id: ids.box1, x: 1140, y: 10 })
.pointerMove(1080, 800)
jest.advanceTimersByTime(100)
editor
.expectShapeToMatch({ id: ids.box1, x: 1280, y: 845.68 })
.pointerUp()
.expectShapeToMatch({ id: ids.box1, x: 1280, y: 845.68 })
})
it('translates multiple shapes', () => {
editor
.select(ids.box1, ids.box2)
@ -173,6 +207,7 @@ describe('When cloning...', () => {
})
it('clones a single shape and restores when stopping cloning', () => {
// Move the camera so that we are not at the edges, which causes the camera to move when we translate
expect(editor.getCurrentPageShapeIds().size).toBe(3)
expect(editor.getCurrentPageShapeIds().size).toBe(3)
editor.select(ids.box1).pointerDown(50, 50, ids.box1).pointerMove(50, 40) // [0, -10]