mirror of
https://github.com/stan-smith/FossFLOW.git
synced 2025-12-23 22:48:57 -05:00
feat: Add advanced pan controls with configurable options
Implements multiple pan methods to improve canvas navigation: Mouse controls: - Click and drag on empty area - Middle mouse button pan - Right click pan (disables context menu when enabled) - Ctrl + click pan - Alt + click pan Keyboard controls: - Arrow keys navigation - WASD keys navigation - IJKL keys navigation - Adjustable pan speed (5-50 pixels) All options are individually configurable through the Settings dialog with a dedicated Pan Controls tab. Default settings enable empty area drag and arrow keys for intuitive behavior. Also updates README.md to document recent feature additions. Closes #25 (click + drag on empty area) Related to improved UX requests
This commit is contained in:
24
README.md
24
README.md
@@ -7,21 +7,25 @@ FossFLOW is a powerful, open-source Progressive Web App (PWA) for creating beaut
|
||||
- **📝 [FOSSFLOW_TODO.md](https://github.com/stan-smith/FossFLOW/blob/main/ISOFLOW_TODO.md)** - Current issues and roadmap with codebase mappings, most gripes are with the isoflow library itself.
|
||||
- **🤝 [CONTRIBUTORS.md](https://github.com/stan-smith/FossFLOW/blob/main/CONTRIBUTORS.md)** - How to contribute to the project.
|
||||
|
||||
## ✨ Recent Updates (August 2025)
|
||||
## Recent Updates (August 2025)
|
||||
|
||||
### Enhanced Interaction Features
|
||||
- **Configurable Hotkeys** - Three profiles (QWERTY, SMNRCT, None) for tool selection with visual indicators
|
||||
- **Advanced Pan Controls** - Multiple pan methods including empty area drag, middle/right click, modifier keys (Ctrl/Alt), and keyboard navigation (Arrow/WASD/IJKL)
|
||||
- **Toggle Connector Arrows** - Option to show/hide arrows on individual connectors
|
||||
- **Persistent Tool Selection** - Connector tool remains active after creating connections
|
||||
- **Settings Dialog** - Centralized configuration for hotkeys and pan controls
|
||||
|
||||
### Docker & CI/CD Improvements
|
||||
- **Automated Docker Builds** - GitHub Actions workflow for automatic Docker Hub deployment on commits
|
||||
- **Multi-architecture Support** - Docker images for both `linux/amd64` and `linux/arm64`
|
||||
- **Pre-built Images** - Available at `stnsmith/fossflow:latest`
|
||||
|
||||
### Monorepo Architecture
|
||||
We've successfully migrated from separate repositories to a unified monorepo structure, making development and contribution significantly easier:
|
||||
- **Single repository** for both the library (`fossflow-lib`) and application (`fossflow-app`)
|
||||
- **Single repository** for both library and application
|
||||
- **NPM Workspaces** for streamlined dependency management
|
||||
- **Instant library updates** available in the app during development
|
||||
- **Unified build process** with `npm run build` at the root
|
||||
|
||||
### 🐳 Docker & Deployment Improvements
|
||||
- **Multi-architecture Docker support** - Images now support both `linux/amd64` and `linux/arm64`
|
||||
- **Docker Hub integration** - Pre-built images available at `stnsmith/fossflow:latest`
|
||||
- **Simple deployment** - Just run `docker compose up` to deploy
|
||||
- **Production-ready** - Nginx-based serving with optimized builds
|
||||
|
||||
### UI Fixes
|
||||
- Fixed Quill editor toolbar icons display issue
|
||||
- Resolved React key warnings in context menus
|
||||
|
||||
154
packages/fossflow-lib/src/components/PanSettings/PanSettings.tsx
Normal file
154
packages/fossflow-lib/src/components/PanSettings/PanSettings.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
FormControlLabel,
|
||||
Switch,
|
||||
Slider,
|
||||
Paper,
|
||||
Divider
|
||||
} from '@mui/material';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
|
||||
export const PanSettings = () => {
|
||||
const panSettings = useUiStateStore((state) => state.panSettings);
|
||||
const setPanSettings = useUiStateStore((state) => state.actions.setPanSettings);
|
||||
|
||||
const handleToggle = (setting: keyof typeof panSettings) => {
|
||||
if (typeof panSettings[setting] === 'boolean') {
|
||||
setPanSettings({
|
||||
...panSettings,
|
||||
[setting]: !panSettings[setting]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleSpeedChange = (value: number) => {
|
||||
setPanSettings({
|
||||
...panSettings,
|
||||
keyboardPanSpeed: value
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Pan Settings
|
||||
</Typography>
|
||||
|
||||
<Paper sx={{ p: 2, mb: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Mouse Pan Options
|
||||
</Typography>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.emptyAreaClickPan}
|
||||
onChange={() => handleToggle('emptyAreaClickPan')}
|
||||
/>
|
||||
}
|
||||
label="Click and drag on empty area"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.middleClickPan}
|
||||
onChange={() => handleToggle('middleClickPan')}
|
||||
/>
|
||||
}
|
||||
label="Middle click and drag"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.rightClickPan}
|
||||
onChange={() => handleToggle('rightClickPan')}
|
||||
/>
|
||||
}
|
||||
label="Right click and drag"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.ctrlClickPan}
|
||||
onChange={() => handleToggle('ctrlClickPan')}
|
||||
/>
|
||||
}
|
||||
label="Ctrl + click and drag"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.altClickPan}
|
||||
onChange={() => handleToggle('altClickPan')}
|
||||
/>
|
||||
}
|
||||
label="Alt + click and drag"
|
||||
/>
|
||||
</Paper>
|
||||
|
||||
<Paper sx={{ p: 2 }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Keyboard Pan Options
|
||||
</Typography>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.arrowKeysPan}
|
||||
onChange={() => handleToggle('arrowKeysPan')}
|
||||
/>
|
||||
}
|
||||
label="Arrow keys"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.wasdPan}
|
||||
onChange={() => handleToggle('wasdPan')}
|
||||
/>
|
||||
}
|
||||
label="WASD keys"
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={panSettings.ijklPan}
|
||||
onChange={() => handleToggle('ijklPan')}
|
||||
/>
|
||||
}
|
||||
label="IJKL keys"
|
||||
/>
|
||||
|
||||
<Divider sx={{ my: 2 }} />
|
||||
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Keyboard Pan Speed
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ px: 2 }}>
|
||||
<Slider
|
||||
value={panSettings.keyboardPanSpeed}
|
||||
onChange={(_, value) => handleSpeedChange(value as number)}
|
||||
min={5}
|
||||
max={50}
|
||||
step={5}
|
||||
marks
|
||||
valueLabelDisplay="auto"
|
||||
/>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 2, display: 'block' }}>
|
||||
Note: Pan options work in addition to the dedicated Pan tool
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
@@ -1,19 +1,24 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
Button,
|
||||
IconButton
|
||||
IconButton,
|
||||
Tabs,
|
||||
Tab,
|
||||
Box
|
||||
} from '@mui/material';
|
||||
import { Close as CloseIcon } from '@mui/icons-material';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { HotkeySettings } from '../HotkeySettings/HotkeySettings';
|
||||
import { PanSettings } from '../PanSettings/PanSettings';
|
||||
|
||||
export const SettingsDialog = () => {
|
||||
const dialog = useUiStateStore((state) => state.dialog);
|
||||
const setDialog = useUiStateStore((state) => state.actions.setDialog);
|
||||
const [tabValue, setTabValue] = useState(0);
|
||||
|
||||
const isOpen = dialog === 'SETTINGS';
|
||||
|
||||
@@ -21,11 +26,15 @@ export const SettingsDialog = () => {
|
||||
setDialog(null);
|
||||
};
|
||||
|
||||
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
|
||||
setTabValue(newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={isOpen}
|
||||
onClose={handleClose}
|
||||
maxWidth="sm"
|
||||
maxWidth="md"
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitle>
|
||||
@@ -44,7 +53,15 @@ export const SettingsDialog = () => {
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent dividers>
|
||||
<HotkeySettings />
|
||||
<Tabs value={tabValue} onChange={handleTabChange} sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||
<Tab label="Hotkeys" />
|
||||
<Tab label="Pan Controls" />
|
||||
</Tabs>
|
||||
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{tabValue === 0 && <HotkeySettings />}
|
||||
{tabValue === 1 && <PanSettings />}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
|
||||
33
packages/fossflow-lib/src/config/panSettings.ts
Normal file
33
packages/fossflow-lib/src/config/panSettings.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
export interface PanSettings {
|
||||
// Mouse pan options
|
||||
middleClickPan: boolean;
|
||||
rightClickPan: boolean;
|
||||
ctrlClickPan: boolean;
|
||||
altClickPan: boolean;
|
||||
emptyAreaClickPan: boolean;
|
||||
|
||||
// Keyboard pan options
|
||||
arrowKeysPan: boolean;
|
||||
wasdPan: boolean;
|
||||
ijklPan: boolean;
|
||||
|
||||
// Pan speed
|
||||
keyboardPanSpeed: number;
|
||||
}
|
||||
|
||||
export const DEFAULT_PAN_SETTINGS: PanSettings = {
|
||||
// Mouse options - start with common defaults
|
||||
middleClickPan: true,
|
||||
rightClickPan: false,
|
||||
ctrlClickPan: false,
|
||||
altClickPan: false,
|
||||
emptyAreaClickPan: true,
|
||||
|
||||
// Keyboard options
|
||||
arrowKeysPan: true,
|
||||
wasdPan: false,
|
||||
ijklPan: false,
|
||||
|
||||
// Pan speed (pixels per key press)
|
||||
keyboardPanSpeed: 20
|
||||
};
|
||||
@@ -28,12 +28,8 @@ export const Pan: ModeActions = {
|
||||
setWindowCursor('grabbing');
|
||||
},
|
||||
mouseup: ({ uiState }) => {
|
||||
if (uiState.mode.type !== 'PAN') return;
|
||||
setWindowCursor('grab');
|
||||
// Always revert to CURSOR mode after panning
|
||||
uiState.actions.setMode({
|
||||
type: 'CURSOR',
|
||||
showCursor: true,
|
||||
mousedownItem: null
|
||||
});
|
||||
// Note: Mode switching is now handled by usePanHandlers
|
||||
}
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Connector } from './modes/Connector';
|
||||
import { Pan } from './modes/Pan';
|
||||
import { PlaceIcon } from './modes/PlaceIcon';
|
||||
import { TextBox } from './modes/TextBox';
|
||||
import { usePanHandlers } from './usePanHandlers';
|
||||
|
||||
const modes: { [k in string]: ModeActions } = {
|
||||
CURSOR: Cursor,
|
||||
@@ -55,6 +56,7 @@ export const useInteractionManager = () => {
|
||||
const { size: rendererSize } = useResizeObserver(uiState.rendererEl);
|
||||
const { undo, redo, canUndo, canRedo } = useHistory();
|
||||
const { createTextBox } = scene;
|
||||
const { handleMouseDown: handlePanMouseDown, handleMouseUp: handlePanMouseUp } = usePanHandlers();
|
||||
|
||||
// Keyboard shortcuts for undo/redo
|
||||
useEffect(() => {
|
||||
@@ -165,6 +167,14 @@ export const useInteractionManager = () => {
|
||||
(e: SlimMouseEvent) => {
|
||||
if (!rendererRef.current) return;
|
||||
|
||||
// Check pan handlers first
|
||||
if (e.type === 'mousedown' && handlePanMouseDown(e)) {
|
||||
return;
|
||||
}
|
||||
if (e.type === 'mouseup' && handlePanMouseUp(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mode = modes[uiState.mode.type];
|
||||
const modeFunction = getModeFunction(mode, e);
|
||||
|
||||
@@ -207,13 +217,18 @@ export const useInteractionManager = () => {
|
||||
modeFunction(baseState);
|
||||
reducerTypeRef.current = uiState.mode.type;
|
||||
},
|
||||
[model, scene, uiState, rendererSize]
|
||||
[model, scene, uiState, rendererSize, handlePanMouseDown, handlePanMouseUp]
|
||||
);
|
||||
|
||||
const onContextMenu = useCallback(
|
||||
(e: SlimMouseEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Don't show context menu if right-click pan is enabled
|
||||
if (uiState.panSettings.rightClickPan) {
|
||||
return;
|
||||
}
|
||||
|
||||
const itemAtTile = getItemAtTile({
|
||||
tile: uiState.mouse.position.tile,
|
||||
scene
|
||||
@@ -232,7 +247,7 @@ export const useInteractionManager = () => {
|
||||
});
|
||||
}
|
||||
},
|
||||
[uiState.mouse, scene, uiState.actions]
|
||||
[uiState.mouse, scene, uiState.actions, uiState.panSettings]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -245,7 +260,8 @@ export const useInteractionManager = () => {
|
||||
...e,
|
||||
clientX: Math.floor(e.touches[0].clientX),
|
||||
clientY: Math.floor(e.touches[0].clientY),
|
||||
type: 'mousedown'
|
||||
type: 'mousedown',
|
||||
button: 0
|
||||
});
|
||||
};
|
||||
|
||||
@@ -254,7 +270,8 @@ export const useInteractionManager = () => {
|
||||
...e,
|
||||
clientX: Math.floor(e.touches[0].clientX),
|
||||
clientY: Math.floor(e.touches[0].clientY),
|
||||
type: 'mousemove'
|
||||
type: 'mousemove',
|
||||
button: 0
|
||||
});
|
||||
};
|
||||
|
||||
@@ -263,7 +280,8 @@ export const useInteractionManager = () => {
|
||||
...e,
|
||||
clientX: 0,
|
||||
clientY: 0,
|
||||
type: 'mouseup'
|
||||
type: 'mouseup',
|
||||
button: 0
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
194
packages/fossflow-lib/src/interaction/usePanHandlers.ts
Normal file
194
packages/fossflow-lib/src/interaction/usePanHandlers.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { useCallback, useEffect, useRef } from 'react';
|
||||
import { useUiStateStore } from 'src/stores/uiStateStore';
|
||||
import { CoordsUtils, getItemAtTile } from 'src/utils';
|
||||
import { useScene } from 'src/hooks/useScene';
|
||||
import { SlimMouseEvent } from 'src/types';
|
||||
|
||||
export const usePanHandlers = () => {
|
||||
const uiState = useUiStateStore((state) => state);
|
||||
const scene = useScene();
|
||||
const isPanningRef = useRef(false);
|
||||
const panMethodRef = useRef<string | null>(null);
|
||||
|
||||
// Helper to start panning
|
||||
const startPan = useCallback((method: string) => {
|
||||
if (uiState.mode.type !== 'PAN') {
|
||||
isPanningRef.current = true;
|
||||
panMethodRef.current = method;
|
||||
uiState.actions.setMode({
|
||||
type: 'PAN',
|
||||
showCursor: false
|
||||
});
|
||||
}
|
||||
}, [uiState.mode.type, uiState.actions]);
|
||||
|
||||
// Helper to end panning
|
||||
const endPan = useCallback(() => {
|
||||
if (isPanningRef.current) {
|
||||
isPanningRef.current = false;
|
||||
panMethodRef.current = null;
|
||||
uiState.actions.setMode({
|
||||
type: 'CURSOR',
|
||||
showCursor: true,
|
||||
mousedownItem: null
|
||||
});
|
||||
}
|
||||
}, [uiState.actions]);
|
||||
|
||||
// Check if click is on empty area
|
||||
const isEmptyArea = useCallback((e: SlimMouseEvent): boolean => {
|
||||
if (!uiState.rendererEl || e.target !== uiState.rendererEl) return false;
|
||||
|
||||
const itemAtTile = getItemAtTile({
|
||||
tile: uiState.mouse.position.tile,
|
||||
scene
|
||||
});
|
||||
|
||||
return !itemAtTile;
|
||||
}, [uiState.rendererEl, uiState.mouse.position.tile, scene]);
|
||||
|
||||
// Enhanced mouse down handler
|
||||
const handleMouseDown = useCallback((e: SlimMouseEvent): boolean => {
|
||||
const panSettings = uiState.panSettings;
|
||||
|
||||
// Middle click pan
|
||||
if (panSettings.middleClickPan && e.button === 1) {
|
||||
e.preventDefault();
|
||||
startPan('middle');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Right click pan
|
||||
if (panSettings.rightClickPan && e.button === 2) {
|
||||
e.preventDefault();
|
||||
startPan('right');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ctrl + click pan
|
||||
if (panSettings.ctrlClickPan && e.ctrlKey && e.button === 0) {
|
||||
e.preventDefault();
|
||||
startPan('ctrl');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Alt + click pan
|
||||
if (panSettings.altClickPan && e.altKey && e.button === 0) {
|
||||
e.preventDefault();
|
||||
startPan('alt');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Empty area click pan
|
||||
if (panSettings.emptyAreaClickPan && e.button === 0 && isEmptyArea(e)) {
|
||||
startPan('empty');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [uiState.panSettings, startPan, isEmptyArea]);
|
||||
|
||||
// Enhanced mouse up handler
|
||||
const handleMouseUp = useCallback((e: SlimMouseEvent): boolean => {
|
||||
if (isPanningRef.current) {
|
||||
endPan();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, [endPan]);
|
||||
|
||||
// Keyboard pan handler
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
// Don't handle if typing in input fields
|
||||
const target = e.target as HTMLElement;
|
||||
if (
|
||||
target.tagName === 'INPUT' ||
|
||||
target.tagName === 'TEXTAREA' ||
|
||||
target.contentEditable === 'true' ||
|
||||
target.closest('.ql-editor')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const panSettings = uiState.panSettings;
|
||||
const speed = panSettings.keyboardPanSpeed;
|
||||
let dx = 0;
|
||||
let dy = 0;
|
||||
|
||||
// Arrow keys
|
||||
if (panSettings.arrowKeysPan) {
|
||||
if (e.key === 'ArrowUp') {
|
||||
dy = speed;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
dy = -speed;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowLeft') {
|
||||
dx = speed;
|
||||
e.preventDefault();
|
||||
} else if (e.key === 'ArrowRight') {
|
||||
dx = -speed;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// WASD keys
|
||||
if (panSettings.wasdPan) {
|
||||
const key = e.key.toLowerCase();
|
||||
if (key === 'w') {
|
||||
dy = speed;
|
||||
e.preventDefault();
|
||||
} else if (key === 's') {
|
||||
dy = -speed;
|
||||
e.preventDefault();
|
||||
} else if (key === 'a') {
|
||||
dx = speed;
|
||||
e.preventDefault();
|
||||
} else if (key === 'd') {
|
||||
dx = -speed;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// IJKL keys
|
||||
if (panSettings.ijklPan) {
|
||||
const key = e.key.toLowerCase();
|
||||
if (key === 'i') {
|
||||
dy = speed;
|
||||
e.preventDefault();
|
||||
} else if (key === 'k') {
|
||||
dy = -speed;
|
||||
e.preventDefault();
|
||||
} else if (key === 'j') {
|
||||
dx = speed;
|
||||
e.preventDefault();
|
||||
} else if (key === 'l') {
|
||||
dx = -speed;
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Apply pan if any movement
|
||||
if (dx !== 0 || dy !== 0) {
|
||||
const newPosition = CoordsUtils.add(
|
||||
uiState.scroll.position,
|
||||
{ x: dx, y: dy }
|
||||
);
|
||||
uiState.actions.setScroll({
|
||||
position: newPosition,
|
||||
offset: uiState.scroll.offset
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||
}, [uiState.panSettings, uiState.scroll, uiState.actions]);
|
||||
|
||||
return {
|
||||
handleMouseDown,
|
||||
handleMouseUp,
|
||||
isPanning: isPanningRef.current
|
||||
};
|
||||
};
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import { UiStateStore } from 'src/types';
|
||||
import { INITIAL_UI_STATE } from 'src/config';
|
||||
import { DEFAULT_HOTKEY_PROFILE } from 'src/config/hotkeys';
|
||||
import { DEFAULT_PAN_SETTINGS } from 'src/config/panSettings';
|
||||
|
||||
const initialState = () => {
|
||||
return createStore<UiStateStore>((set, get) => {
|
||||
@@ -32,6 +33,7 @@ const initialState = () => {
|
||||
itemControls: null,
|
||||
enableDebugTools: false,
|
||||
hotkeyProfile: DEFAULT_HOTKEY_PROFILE,
|
||||
panSettings: DEFAULT_PAN_SETTINGS,
|
||||
actions: {
|
||||
setView: (view) => {
|
||||
set({ view });
|
||||
@@ -96,6 +98,9 @@ const initialState = () => {
|
||||
},
|
||||
setHotkeyProfile: (hotkeyProfile) => {
|
||||
set({ hotkeyProfile });
|
||||
},
|
||||
setPanSettings: (panSettings) => {
|
||||
set({ panSettings });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export type BoundingBox = [Coords, Coords, Coords, Coords];
|
||||
|
||||
export type SlimMouseEvent = Pick<
|
||||
MouseEvent,
|
||||
'clientX' | 'clientY' | 'target' | 'type' | 'preventDefault'
|
||||
'clientX' | 'clientY' | 'target' | 'type' | 'preventDefault' | 'button' | 'ctrlKey' | 'altKey' | 'shiftKey' | 'metaKey'
|
||||
>;
|
||||
|
||||
export const EditorModeEnum = {
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Coords, EditorModeEnum, MainMenuOptions } from './common';
|
||||
import { Icon } from './model';
|
||||
import { ItemReference } from './scene';
|
||||
import { HotkeyProfile } from 'src/config/hotkeys';
|
||||
import { PanSettings } from 'src/config/panSettings';
|
||||
|
||||
interface AddItemControls {
|
||||
type: 'ADD_ITEM';
|
||||
@@ -151,6 +152,7 @@ export interface UiState {
|
||||
rendererEl: HTMLDivElement | null;
|
||||
enableDebugTools: boolean;
|
||||
hotkeyProfile: HotkeyProfile;
|
||||
panSettings: PanSettings;
|
||||
}
|
||||
|
||||
export interface UiStateActions {
|
||||
@@ -172,6 +174,7 @@ export interface UiStateActions {
|
||||
setRendererEl: (el: HTMLDivElement) => void;
|
||||
setEnableDebugTools: (enabled: boolean) => void;
|
||||
setHotkeyProfile: (profile: HotkeyProfile) => void;
|
||||
setPanSettings: (settings: PanSettings) => void;
|
||||
}
|
||||
|
||||
export type UiStateStore = UiState & {
|
||||
|
||||
Reference in New Issue
Block a user