feat: Enhance cursor handling and mouse action notes in the footer and builder components
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { HelpIcon } from "../icons/DashboardIcon";
|
import { HelpIcon } from "../icons/DashboardIcon";
|
||||||
import { useLogger } from "../ui/log/LoggerContext";
|
import { useLogger } from "../ui/log/LoggerContext";
|
||||||
import { GetLogIcon } from "./getLogIcons";
|
import { GetLogIcon } from "./getLogIcons";
|
||||||
@@ -8,10 +8,13 @@ import {
|
|||||||
CurserRightIcon,
|
CurserRightIcon,
|
||||||
} from "../icons/LogIcons";
|
} from "../icons/LogIcons";
|
||||||
import ShortcutHelper from "./shortcutHelper";
|
import ShortcutHelper from "./shortcutHelper";
|
||||||
import useVersionHistoryVisibleStore, { useShortcutStore } from "../../store/builder/store";
|
import useVersionHistoryVisibleStore, {
|
||||||
|
useShortcutStore,
|
||||||
|
} from "../../store/builder/store";
|
||||||
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||||
import useModuleStore, { useSubModuleStore } from "../../store/useModuleStore";
|
import useModuleStore, { useSubModuleStore } from "../../store/useModuleStore";
|
||||||
import { useVersionContext } from "../../modules/builder/version/versionContext";
|
import { useVersionContext } from "../../modules/builder/version/versionContext";
|
||||||
|
import { mouseActionHelper } from "../../utils/mouseUtils/mouseHelper";
|
||||||
|
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
const { logs, setIsLogListVisible } = useLogger();
|
const { logs, setIsLogListVisible } = useLogger();
|
||||||
@@ -25,28 +28,45 @@ const Footer: React.FC = () => {
|
|||||||
const { selectedVersionStore } = useVersionContext();
|
const { selectedVersionStore } = useVersionContext();
|
||||||
const { selectedVersion } = selectedVersionStore();
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
|
||||||
|
const [notes, setNotes] = useState({
|
||||||
|
Leftnote: "",
|
||||||
|
Middlenote: "",
|
||||||
|
Rightnote: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cleanup = mouseActionHelper(setNotes);
|
||||||
|
return () => cleanup();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const mouseButtons = [
|
||||||
|
{
|
||||||
|
icon: <CurserLeftIcon />,
|
||||||
|
label: notes.Leftnote !== "" ? notes.Leftnote : "Pan",
|
||||||
|
mouse: "left",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <CurserMiddleIcon />,
|
||||||
|
label: notes.Middlenote !== "" ? notes.Middlenote : "Scroll Zoom",
|
||||||
|
mouse: "middle",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <CurserRightIcon />,
|
||||||
|
label: notes.Rightnote !== "" ? notes.Rightnote : "Orbit / Cancel action",
|
||||||
|
mouse: "right",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="footer-container">
|
<div className="footer-container">
|
||||||
<div className="footer-wrapper">
|
<div className="footer-wrapper">
|
||||||
<div className="selection-wrapper">
|
<div className="selection-wrapper">
|
||||||
<div className="selector-wrapper">
|
{mouseButtons.map(({ icon, label, mouse }) => (
|
||||||
<div className="icon">
|
<div className="selector-wrapper" key={mouse}>
|
||||||
<CurserLeftIcon />
|
<div className="icon">{icon}</div>
|
||||||
|
<div className="selector">{label}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="selector">Selection</div>
|
))}
|
||||||
</div>
|
|
||||||
<div className="selector-wrapper">
|
|
||||||
<div className="icon">
|
|
||||||
<CurserMiddleIcon />
|
|
||||||
</div>
|
|
||||||
<div className="selector">Rotate/Zoom</div>
|
|
||||||
</div>
|
|
||||||
<div className="selector-wrapper">
|
|
||||||
<div className="icon">
|
|
||||||
<CurserRightIcon />
|
|
||||||
</div>
|
|
||||||
<div className="selector">Pan/Context Menu</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="logs-wrapper">
|
<div className="logs-wrapper">
|
||||||
@@ -68,12 +88,15 @@ const Footer: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="version" onClick={() => {
|
<div
|
||||||
setVersionHistoryVisible(true);
|
className="version"
|
||||||
setSubModule("properties");
|
onClick={() => {
|
||||||
setActiveModule('builder');
|
setVersionHistoryVisible(true);
|
||||||
}}>
|
setSubModule("properties");
|
||||||
{(selectedVersion?.version) ?? 'v 0.0.0'}
|
setActiveModule("builder");
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{selectedVersion?.version ?? "v 0.0.0"}
|
||||||
<div className="icon">
|
<div className="icon">
|
||||||
<HelpIcon />
|
<HelpIcon />
|
||||||
</div>
|
</div>
|
||||||
@@ -83,8 +106,9 @@ const Footer: React.FC = () => {
|
|||||||
|
|
||||||
{!isPlaying && (
|
{!isPlaying && (
|
||||||
<div
|
<div
|
||||||
className={`shortcut-helper-overlay ${showShortcuts ? "visible" : ""
|
className={`shortcut-helper-overlay ${
|
||||||
}`}
|
showShortcuts ? "visible" : ""
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<ShortcutHelper setShowShortcuts={setShowShortcuts} />
|
<ShortcutHelper setShowShortcuts={setShowShortcuts} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import * as Constants from '../../../types/world/worldConstants';
|
|||||||
import { useVersionContext } from '../version/versionContext';
|
import { useVersionContext } from '../version/versionContext';
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import { getUserData } from '../../../functions/getUserData';
|
import { getUserData } from '../../../functions/getUserData';
|
||||||
|
import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors';
|
||||||
|
|
||||||
// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
|
// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
|
||||||
// import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
|
// import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
|
||||||
@@ -23,7 +24,7 @@ interface LineProps {
|
|||||||
|
|
||||||
function Line({ points }: Readonly<LineProps>) {
|
function Line({ points }: Readonly<LineProps>) {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const { raycaster, camera, pointer, gl } = useThree();
|
const { raycaster, camera, pointer } = useThree();
|
||||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
const [isDeletable, setIsDeletable] = useState(false);
|
const [isDeletable, setIsDeletable] = useState(false);
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
@@ -213,7 +214,7 @@ function Line({ points }: Readonly<LineProps>) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gl.domElement.style.cursor = 'default';
|
handleCanvasCursors('default');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,7 +225,7 @@ function Line({ points }: Readonly<LineProps>) {
|
|||||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
|
||||||
if (hit) {
|
if (hit) {
|
||||||
gl.domElement.style.cursor = 'move';
|
handleCanvasCursors('grabbing');
|
||||||
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
||||||
|
|
||||||
const start = new THREE.Vector3(...points[0].position);
|
const start = new THREE.Vector3(...points[0].position);
|
||||||
@@ -269,7 +270,7 @@ function Line({ points }: Readonly<LineProps>) {
|
|||||||
|
|
||||||
const handleDragEnd = (points: [Point, Point]) => {
|
const handleDragEnd = (points: [Point, Point]) => {
|
||||||
if (toolMode !== 'move' || !dragOffset) return;
|
if (toolMode !== 'move' || !dragOffset) return;
|
||||||
gl.domElement.style.cursor = 'default';
|
handleCanvasCursors('default');
|
||||||
setDragOffset(null);
|
setDragOffset(null);
|
||||||
if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
|
if (points[0].pointType === 'Wall' && points[1].pointType === 'Wall') {
|
||||||
const updatedWalls1 = getWallsByPointId(points[0].pointUuid);
|
const updatedWalls1 = getWallsByPointId(points[0].pointUuid);
|
||||||
@@ -377,14 +378,14 @@ function Line({ points }: Readonly<LineProps>) {
|
|||||||
setHoveredLine(points);
|
setHoveredLine(points);
|
||||||
setIsHovered(true)
|
setIsHovered(true)
|
||||||
if (toolMode === 'move' && !hoveredPoint) {
|
if (toolMode === 'move' && !hoveredPoint) {
|
||||||
gl.domElement.style.cursor = 'pointer';
|
handleCanvasCursors('grab');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onPointerOut={() => {
|
onPointerOut={() => {
|
||||||
if (hoveredLine) {
|
if (hoveredLine) {
|
||||||
setHoveredLine(null);
|
setHoveredLine(null);
|
||||||
gl.domElement.style.cursor = 'default';
|
handleCanvasCursors('default');
|
||||||
}
|
}
|
||||||
setIsHovered(false)
|
setIsHovered(false)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ import { useSceneContext } from '../../scene/sceneContext';
|
|||||||
// import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
|
// import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
|
||||||
|
|
||||||
import { getUserData } from '../../../functions/getUserData';
|
import { getUserData } from '../../../functions/getUserData';
|
||||||
|
import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors';
|
||||||
|
|
||||||
function Point({ point }: { readonly point: Point }) {
|
function Point({ point }: { readonly point: Point }) {
|
||||||
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
||||||
const { raycaster, camera, pointer, gl } = useThree();
|
const { raycaster, camera, pointer } = useThree();
|
||||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
||||||
@@ -44,7 +45,7 @@ function Point({ point }: { readonly point: Point }) {
|
|||||||
const colors = getColor(point);
|
const colors = getColor(point);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
gl.domElement.style.cursor = 'default';
|
handleCanvasCursors('default');
|
||||||
}, [toolMode])
|
}, [toolMode])
|
||||||
|
|
||||||
function getColor(point: Point) {
|
function getColor(point: Point) {
|
||||||
@@ -114,7 +115,7 @@ function Point({ point }: { readonly point: Point }) {
|
|||||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
|
|
||||||
if (hit) {
|
if (hit) {
|
||||||
gl.domElement.style.cursor = 'move';
|
handleCanvasCursors('grabbing');
|
||||||
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
||||||
const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z];
|
const newPosition: [number, number, number] = [positionWithOffset.x, positionWithOffset.y, positionWithOffset.z];
|
||||||
|
|
||||||
@@ -152,7 +153,7 @@ function Point({ point }: { readonly point: Point }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleDragEnd = (point: Point) => {
|
const handleDragEnd = (point: Point) => {
|
||||||
gl.domElement.style.cursor = 'default';
|
handleCanvasCursors('default');
|
||||||
setDragOffset(null);
|
setDragOffset(null);
|
||||||
if (toolMode !== 'move') return;
|
if (toolMode !== 'move') return;
|
||||||
if (point.pointType === 'Aisle') {
|
if (point.pointType === 'Aisle') {
|
||||||
@@ -396,7 +397,7 @@ function Point({ point }: { readonly point: Point }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gl.domElement.style.cursor = 'default';
|
handleCanvasCursors('default');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,14 +432,14 @@ function Point({ point }: { readonly point: Point }) {
|
|||||||
setHoveredPoint(point);
|
setHoveredPoint(point);
|
||||||
setIsHovered(true);
|
setIsHovered(true);
|
||||||
if (toolMode === 'move') {
|
if (toolMode === 'move') {
|
||||||
gl.domElement.style.cursor = 'pointer';
|
handleCanvasCursors('grab');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onPointerOut={() => {
|
onPointerOut={() => {
|
||||||
if (hoveredPoint) {
|
if (hoveredPoint) {
|
||||||
setHoveredPoint(null);
|
setHoveredPoint(null);
|
||||||
gl.domElement.style.cursor = 'default';
|
handleCanvasCursors('default');
|
||||||
}
|
}
|
||||||
setIsHovered(false)
|
setIsHovered(false)
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import { getVersionHistoryApi } from "../services/factoryBuilder/versionControl/
|
|||||||
import { useVersionHistoryStore } from "../store/builder/useVersionHistoryStore";
|
import { useVersionHistoryStore } from "../store/builder/useVersionHistoryStore";
|
||||||
import { VersionProvider } from "../modules/builder/version/versionContext";
|
import { VersionProvider } from "../modules/builder/version/versionContext";
|
||||||
import { sharedWithMeProjects } from "../services/dashboard/sharedWithMeProject";
|
import { sharedWithMeProjects } from "../services/dashboard/sharedWithMeProject";
|
||||||
import { handleCanvasCursors } from "../utils/handleCanvasCursors";
|
import { handleCanvasCursors } from "../utils/mouseUtils/handleCanvasCursors";
|
||||||
|
|
||||||
const Project: React.FC = () => {
|
const Project: React.FC = () => {
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
@@ -134,7 +134,6 @@ const Project: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleCanvasCursors(activeTool);
|
handleCanvasCursors(activeTool);
|
||||||
console.log('activeTool: ', activeTool);
|
|
||||||
}, [activeTool]);
|
}, [activeTool]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -45,3 +45,9 @@ $cursor-grabing: url("../../assets/cursors/close.svg") 8 8, default;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scene-container.hand-closed {
|
||||||
|
canvas {
|
||||||
|
cursor: $cursor-grabing !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,19 @@ export const handleCanvasCursors = (name: string) => {
|
|||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
|
|
||||||
const cursorMap: Record<string, string> = {
|
const cursorMap: Record<string, string> = {
|
||||||
|
default: '',
|
||||||
'draw-wall': 'draw',
|
'draw-wall': 'draw',
|
||||||
'draw-aisle': 'draw',
|
'draw-aisle': 'draw',
|
||||||
'draw-zone': 'draw',
|
'draw-zone': 'draw',
|
||||||
'draw-floor': 'draw',
|
'draw-floor': 'draw',
|
||||||
measure: 'measure',
|
measure: 'measure',
|
||||||
delete: 'pointer',
|
delete: 'pointer',
|
||||||
|
pointer: 'pointer',
|
||||||
|
grab: 'hand',
|
||||||
|
grabbing: 'hand-closed',
|
||||||
pen: 'pen',
|
pen: 'pen',
|
||||||
'free-hand': 'hand',
|
'free-hand': 'hand',
|
||||||
|
move: 'move',
|
||||||
// Add more mappings as needed
|
// Add more mappings as needed
|
||||||
};
|
};
|
||||||
|
|
||||||
46
app/src/utils/mouseUtils/mouseHelper.ts
Normal file
46
app/src/utils/mouseUtils/mouseHelper.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
const actionNotes: Record<string, string> = {
|
||||||
|
'left+CONTROL': 'Box Select',
|
||||||
|
'left+SHIFT': 'Multi Select',
|
||||||
|
'middle+CONTROL': 'Zoom In',
|
||||||
|
};
|
||||||
|
|
||||||
|
export function mouseActionHelper(
|
||||||
|
onUpdate: (notes: {
|
||||||
|
Leftnote: string;
|
||||||
|
Middlenote: string;
|
||||||
|
Rightnote: string;
|
||||||
|
}) => void
|
||||||
|
) {
|
||||||
|
const activeKeys = new Set<string>();
|
||||||
|
|
||||||
|
function updateNotesFromKeys() {
|
||||||
|
const sortedKeys = Array.from(activeKeys).sort();
|
||||||
|
const leftKey = ['left', ...sortedKeys].join('+');
|
||||||
|
const middleKey = ['middle', ...sortedKeys].join('+');
|
||||||
|
const rightKey = ['right', ...sortedKeys].join('+');
|
||||||
|
|
||||||
|
onUpdate({
|
||||||
|
Leftnote: actionNotes[leftKey] || '',
|
||||||
|
Middlenote: actionNotes[middleKey] || '',
|
||||||
|
Rightnote: actionNotes[rightKey] || '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
|
activeKeys.add(event.key.toUpperCase());
|
||||||
|
updateNotesFromKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyUp(event: KeyboardEvent) {
|
||||||
|
activeKeys.delete(event.key.toUpperCase());
|
||||||
|
updateNotesFromKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
window.addEventListener('keyup', handleKeyUp);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
window.removeEventListener('keyup', handleKeyUp);
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user