Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support dragging when space is being pressed #212

Merged
merged 21 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/atoms/gridInteractionStateAtom.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { atom } from 'recoil';
import { Coordinate } from '../gridGL/types/size';

export enum PanMode {
Disabled = 'DISABLED',
Enabled = 'ENABLED',
Dragging = 'DRAGGING',
}

export interface GridInteractionState {
panMode: PanMode;
keyboardMovePosition: Coordinate;
cursorPosition: Coordinate;
showMultiCursor: boolean;
Expand All @@ -13,6 +21,7 @@ export interface GridInteractionState {
}

export const gridInteractionStateDefault: GridInteractionState = {
panMode: PanMode.Disabled,
keyboardMovePosition: { x: 0, y: 0 },
cursorPosition: { x: 0, y: 0 },
showMultiCursor: false,
Expand Down
3 changes: 3 additions & 0 deletions src/constants/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { isMobile } from 'react-device-detect';

export const IS_READONLY_MODE = isMobile;
77 changes: 69 additions & 8 deletions src/gridGL/QuadraticGrid.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useLoading } from '../contexts/LoadingContext';
import { gridInteractionStateAtom } from '../atoms/gridInteractionStateAtom';
import { editorInteractionStateAtom } from '../atoms/editorInteractionStateAtom';
Expand All @@ -9,6 +9,7 @@ import { ensureVisible } from './interaction/viewportHelper';
import { CellInput } from './interaction/CellInput';
import { SheetController } from '../grid/controller/sheetController';
import { FloatingContextMenu } from '../ui/menus/ContextMenu/FloatingContextMenu';
import { PanMode } from '../atoms/gridInteractionStateAtom';

interface IProps {
sheetController: SheetController;
Expand All @@ -34,13 +35,23 @@ export default function QuadraticGrid(props: IProps) {

// Interaction State hook
const [interactionState, setInteractionState] = useRecoilState(gridInteractionStateAtom);
let prevPanModeRef = useRef(interactionState.panMode);
useEffect(() => {
props.app?.settings.updateInteractionState(interactionState, setInteractionState);
ensureVisible({
sheet: props.sheetController.sheet,
app: props.app,
interactionState,
});

// If we're not dealing with a change in pan mode, ensure the cursor stays
// visible on screen (if we did have a change in pan mode, the user is
// panning and we don't want to change the visibility of the screen when
// they’re done)
if (prevPanModeRef.current === interactionState.panMode) {
ensureVisible({
sheet: props.sheetController.sheet,
app: props.app,
interactionState,
});
}
// Store the previous state for our check above
prevPanModeRef.current = interactionState.panMode;
}, [props.app, props.app?.settings, interactionState, setInteractionState, props.sheetController.sheet]);

const [editorInteractionState, setEditorInteractionState] = useRecoilState(editorInteractionStateAtom);
Expand All @@ -51,7 +62,45 @@ export default function QuadraticGrid(props: IProps) {
// Right click menu
const [showContextMenu, setShowContextMenu] = useState(false);

const { onKeyDown } = useKeyboard({
// Pan mode
const [mouseIsDown, setMouseIsDown] = useState(false);
const [spaceIsDown, setSpaceIsDown] = useState(false);
const onMouseDown = () => {
setMouseIsDown(true);
if (interactionState.panMode === PanMode.Enabled) {
setInteractionState({ ...interactionState, panMode: PanMode.Dragging });
}
};
const onMouseUp = () => {
setMouseIsDown(false);
if (interactionState.panMode !== PanMode.Disabled) {
setInteractionState({ ...interactionState, panMode: spaceIsDown ? PanMode.Enabled : PanMode.Disabled });
}
};
const onKeyDown = (e: React.KeyboardEvent<HTMLElement>) => {
if (e.code === 'Space') {
setSpaceIsDown(true);
if (interactionState.panMode === PanMode.Disabled) {
setInteractionState({
...interactionState,
panMode: PanMode.Enabled,
});
}
}
};
const onKeyUp = (e: React.KeyboardEvent<HTMLElement>) => {
if (e.code === 'Space') {
setSpaceIsDown(false);
if (interactionState.panMode !== PanMode.Disabled && !mouseIsDown) {
setInteractionState({
...interactionState,
panMode: PanMode.Disabled,
});
}
}
};

const { onKeyDown: onKeyDownFromUseKeyboard } = useKeyboard({
sheetController: props.sheetController,
interactionState,
setInteractionState,
Expand All @@ -71,6 +120,12 @@ export default function QuadraticGrid(props: IProps) {
outline: 'none',
overflow: 'hidden',
WebkitTapHighlightColor: 'transparent',
cursor:
interactionState.panMode === PanMode.Enabled
? 'grab'
: interactionState.panMode === PanMode.Dragging
? 'grabbing'
: 'unset',
}}
onContextMenu={(event) => {
event.preventDefault();
Expand All @@ -86,7 +141,13 @@ export default function QuadraticGrid(props: IProps) {
setShowContextMenu(false);
}
}}
onKeyDown={onKeyDown}
onMouseDown={onMouseDown}
onMouseUp={onMouseUp}
onKeyDown={(e) => {
onKeyDown(e);
onKeyDownFromUseKeyboard(e);
}}
onKeyUp={onKeyUp}
>
<CellInput
interactionState={interactionState}
Expand Down
3 changes: 3 additions & 0 deletions src/gridGL/interaction/CellInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,9 @@ export const CellInput = (props: CellInputProps) => {
closeInput({ x: 0, y: 1 });
} else if ((event.metaKey || event.ctrlKey) && event.code === 'KeyP') {
event.preventDefault();
} else if (event.code === 'Space') {
// Don't propagate so panning mode doesn't get triggered
event.stopPropagation();
}
}}
></input>
Expand Down
4 changes: 2 additions & 2 deletions src/gridGL/interaction/keyboard/keyboardCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,8 @@ export function keyboardCell(options: {
event.preventDefault();
}

// if key is a letter number or space start taking input
if (isAlphaNumeric(event.key) || event.key === ' ' || event.key === '.') {
// if key is a letter number start taking input
if (isAlphaNumeric(event.key) || event.key === '.') {
setInteractionState({
...interactionState,
...{
Expand Down
1 change: 0 additions & 1 deletion src/gridGL/interaction/pointer/Pointer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export class Pointer {
}

private handlePointerDown = (e: InteractionEvent): void => {
this.app.canvas.style.cursor = 'auto';
const world = this.app.viewport.toWorld(e.data.global);
const event = e.data.originalEvent;
this.headingResize.pointerDown(world, event) || this.pointerDown.pointerDown(world, event as PointerEvent);
Expand Down
10 changes: 8 additions & 2 deletions src/gridGL/interaction/pointer/PointerDown.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Point } from 'pixi.js';
import { isMobile } from 'react-device-detect';
import { IS_READONLY_MODE } from '../../../constants/app';
import { Sheet } from '../../../grid/sheet/Sheet';
import { PixiApp } from '../../pixiApp/PixiApp';
import { doubleClickCell } from './doubleClickCell';
import { DOUBLE_CLICK_TIME } from './pointerUtils';
import { PanMode } from '../../../atoms/gridInteractionStateAtom';

const MINIMUM_MOVE_POSITION = 5;

Expand Down Expand Up @@ -42,7 +43,8 @@ export class PointerDown {
}

pointerDown(world: Point, event: PointerEvent): void {
if (isMobile) return;
if (IS_READONLY_MODE) return;
if (this.app.settings.interactionState.panMode !== PanMode.Disabled) return;

const { settings, cursor } = this.app;
const { interactionState, setInteractionState } = settings;
Expand Down Expand Up @@ -156,6 +158,8 @@ export class PointerDown {
}

pointerMove(world: Point): void {
if (this.app.settings.interactionState.panMode !== PanMode.Disabled) return;

const { viewport, settings, cursor } = this.app;
const { gridOffsets } = this.sheet;

Expand Down Expand Up @@ -208,6 +212,7 @@ export class PointerDown {
if (column === this.position.x && row === this.position.y) {
// hide multi cursor when only selecting one cell
settings.setInteractionState({
...settings.interactionState,
keyboardMovePosition: { x: this.position.x, y: this.position.y },
cursorPosition: { x: this.position.x, y: this.position.y },
multiCursorPosition: {
Expand Down Expand Up @@ -240,6 +245,7 @@ export class PointerDown {
if (hasMoved) {
// update multiCursor
settings.setInteractionState({
...settings.interactionState,
keyboardMovePosition: { x: column, y: row },
cursorPosition: { x: this.position.x, y: this.position.y },
multiCursorPosition: {
Expand Down
16 changes: 10 additions & 6 deletions src/gridGL/interaction/pointer/PointerHeading.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { zoomToFit } from '../../helpers/zoom';
import { PixiApp } from '../../pixiApp/PixiApp';
import { DOUBLE_CLICK_TIME } from './pointerUtils';
import { HeadingSize } from '../../../grid/sheet/useHeadings';
import { PanMode } from '../../../atoms/gridInteractionStateAtom';

const MINIMUM_COLUMN_SIZE = 20;

Expand Down Expand Up @@ -127,13 +128,16 @@ export class PointerHeading {
}

pointerMove(world: Point): boolean {
const { canvas, headings, cells, gridLines, cursor } = this.app;
const { canvas, headings, cells, gridLines, cursor, settings } = this.app;
const { gridOffsets } = this.sheet;
const headingResize = headings.intersectsHeadingGridLine(world);
if (headingResize) {
canvas.style.cursor = headingResize.column !== undefined ? 'col-resize' : 'row-resize';
} else {
canvas.style.cursor = headings.intersectsHeadings(world) ? 'pointer' : 'auto';
// Only style the heading resize cursor if panning mode is disabled
if (settings.interactionState.panMode === PanMode.Disabled) {
const headingResize = headings.intersectsHeadingGridLine(world);
if (headingResize) {
canvas.style.cursor = headingResize.column !== undefined ? 'col-resize' : 'row-resize';
} else {
canvas.style.cursor = headings.intersectsHeadings(world) ? 'pointer' : 'unset';
}
}
if (!this.active) {
return false;
Expand Down
10 changes: 8 additions & 2 deletions src/gridGL/pixiApp/PixiApp.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Renderer, Container, Graphics } from 'pixi.js';
import { Viewport } from 'pixi-viewport';
import { isMobileOnly } from 'react-device-detect';
import { PixiAppSettings } from './PixiAppSettings';
import { Pointer } from '../interaction/pointer/Pointer';
import { Update } from './Update';
Expand All @@ -19,6 +18,7 @@ import { SheetController } from '../../grid/controller/sheetController';
import { HEADING_SIZE } from '../../constants/gridConstants';
import { editorInteractionStateDefault } from '../../atoms/editorInteractionStateAtom';
import { gridInteractionStateDefault } from '../../atoms/gridInteractionStateAtom';
import { IS_READONLY_MODE } from '../../constants/app';

export class PixiApp {
private parent?: HTMLDivElement;
Expand Down Expand Up @@ -73,7 +73,10 @@ export class PixiApp {
this.viewport = new Viewport({ interaction: this.renderer.plugins.interaction });
this.stage.addChild(this.viewport);
this.viewport
.drag({ pressDrag: isMobileOnly }) // enable drag on mobile, no where else
.drag({
pressDrag: true,
...(IS_READONLY_MODE ? {} : { keyToPress: ['Space'] }),
})
.decelerate()
.pinch()
.wheel({ trackpadPinch: true, wheelZoom: false, percent: 1.5 })
Expand All @@ -82,6 +85,9 @@ export class PixiApp {
maxScale: 10,
});

// hack to ensure pointermove works outside of canvas
this.viewport.off('pointerout');

// this holds the viewport's contents so it can be reused in Quadrants
this.viewportContents = this.viewport.addChild(new Container());

Expand Down
6 changes: 2 additions & 4 deletions src/quadratic/QuadraticApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { loadPython } from '../grid/computations/python/loadPython';
import { FileLoadingComponent } from './FileLoadingComponent';
import { AnalyticsProvider } from './AnalyticsProvider';
import { loadAssets } from '../gridGL/loadAssets';
import { isMobileOnly } from 'react-device-detect';
import { IS_READONLY_MODE } from '../constants/app';
import { debugSkipPythonLoad } from '../debugFlags';
import { GetCellsDBSetSheet } from '../grid/sheet/Cells/GetCellsDB';
import { localFiles } from '../grid/sheet/localFiles';
Expand All @@ -22,13 +22,11 @@ export const QuadraticApp = () => {
// Loading Effect
useEffect(() => {
if (loading) {
if (!isMobileOnly && !debugSkipPythonLoad) {
// Load Python on desktop
if (!IS_READONLY_MODE && !debugSkipPythonLoad) {
loadPython().then(() => {
incrementLoadingCount();
});
} else {
// Don't load python on mobile
incrementLoadingCount();
}
loadAssets().then(() => {
Expand Down
6 changes: 3 additions & 3 deletions src/ui/menus/TopBar/SubMenus/QuadraticMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useCallback, useEffect, useState } from 'react';
import Button from '@mui/material/Button';
import KeyboardArrowDown from '@mui/icons-material/KeyboardArrowDown';
import { Menu, MenuItem, SubMenu, MenuDivider, MenuHeader } from '@szhsin/react-menu';
import { isMobileOnly } from 'react-device-detect';
import { IS_READONLY_MODE } from '../../../../constants/app';
import { useGridSettings } from './useGridSettings';
import { useAuth0 } from '@auth0/auth0-react';

Expand Down Expand Up @@ -50,9 +50,9 @@ export const QuadraticMenu = (props: Props) => {

const { isAuthenticated, user, logout } = useAuth0();

// On Mobile set Headers to not visible by default
// For readonly, set Headers to not visible by default
useEffect(() => {
if (isMobileOnly) {
if (IS_READONLY_MODE) {
settings.setShowHeadings(false);
}
// eslint-disable-next-line
Expand Down
8 changes: 4 additions & 4 deletions src/ui/menus/TopBar/TopBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DataMenu } from './SubMenus/DataMenu';
import { NumberFormatMenu } from './SubMenus/NumberFormatMenu';
import { ZoomDropdown } from './ZoomDropdown';
import { electronMaximizeCurrentWindow } from '../../../helpers/electronMaximizeCurrentWindow';
import { isMobileOnly } from 'react-device-detect';
import { IS_READONLY_MODE } from '../../../constants/app';
import { PixiApp } from '../../../gridGL/pixiApp/PixiApp';
import { useLocalFiles } from '../../../hooks/useLocalFiles';
import { SheetController } from '../../../grid/controller/sheetController';
Expand Down Expand Up @@ -64,7 +64,7 @@ export const TopBar = (props: IProps) => {
}}
>
<QuadraticMenu sheetController={props.sheetController} />
{!isMobileOnly && (
{!IS_READONLY_MODE && (
<>
<DataMenu></DataMenu>
<FormatMenu app={props.app} sheet_controller={props.sheetController} />
Expand All @@ -73,7 +73,7 @@ export const TopBar = (props: IProps) => {
)}
</Box>

{isMobileOnly ? (
{IS_READONLY_MODE ? (
<Box
sx={{
display: 'flex',
Expand Down Expand Up @@ -120,7 +120,7 @@ export const TopBar = (props: IProps) => {
WebkitAppRegion: 'no-drag',
}}
>
{!isMobileOnly && (
{!IS_READONLY_MODE && (
<>
{/* {user !== undefined && (
<AvatarGroup>
Expand Down