chore: optimize context menu behaviour (#885)
Co-authored-by: sealday <sealday@gmail.com> Reviewed-on: daoyoucloud/tachybase#885
This commit is contained in:
parent
51335834c8
commit
cfa2bc9775
@ -1,281 +1,59 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { css } from '@nocobase/client';
|
||||
import React, { useRef, useState } from 'react';
|
||||
import { css, useDesignable } from '@nocobase/client';
|
||||
import { ContextMenuTrigger } from '../../components/context-menu';
|
||||
import { ContextMenu } from '../../components/context-menu';
|
||||
import { ContextMenuItem } from '../../components/context-menu';
|
||||
import { ContextMenuContext } from './useContextMenu';
|
||||
import { useJoystick } from './useJoystick';
|
||||
|
||||
export const ContextMenuProvider = ({ children }) => {
|
||||
const [enable, setEnable] = useState(true);
|
||||
const [enableDragable, setEnableDragable] = useState(false);
|
||||
const { designable, setDesignable } = useDesignable();
|
||||
const ref = useRef<HTMLDivElement>();
|
||||
useEffect(() => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* 摇杆配置
|
||||
*/
|
||||
const config = {
|
||||
/** 摇杆半径 */
|
||||
radius: 100,
|
||||
};
|
||||
|
||||
/**
|
||||
* 摇杆区域元素,摇杆只会渲染在该区域
|
||||
*/
|
||||
const ele = ref.current;
|
||||
|
||||
/** 是否正在按压 */
|
||||
let pressing = false;
|
||||
|
||||
/** 初始X坐标 */
|
||||
let prevX = 0;
|
||||
/** 初始Y坐标 */
|
||||
let prevY = 0;
|
||||
|
||||
/** 当前X坐标 */
|
||||
let newX = 0;
|
||||
/** 当前Y坐标 */
|
||||
let newY = 0;
|
||||
|
||||
/** 相对X坐标 */
|
||||
let relX = 0;
|
||||
/** 相对Y坐标 */
|
||||
let relY = 0;
|
||||
|
||||
/** 摇杆canvas移动后应在的X坐标 */
|
||||
let moveX = 0;
|
||||
/** 摇杆canvas移动后应在的Y坐标 */
|
||||
let moveY = 0;
|
||||
|
||||
/** 根据半径限制相对坐标后的X坐标 */
|
||||
let displayX = 0;
|
||||
/** 根据半径限制相对坐标后的Y坐标 */
|
||||
let displayY = 0;
|
||||
|
||||
ele.addEventListener('mousedown', down);
|
||||
ele.addEventListener('mousemove', move);
|
||||
ele.addEventListener('mouseup', up);
|
||||
|
||||
ele.addEventListener('touchstart', down);
|
||||
ele.addEventListener('touchmove', move);
|
||||
ele.addEventListener('touchend', up);
|
||||
|
||||
const stickEle = createStickCanvas();
|
||||
const baseEle = createBaseCanvas();
|
||||
|
||||
// ele.style.position = 'fixed';
|
||||
ele.appendChild(baseEle);
|
||||
baseEle.style.position = 'absolute';
|
||||
baseEle.style.visibility = 'hidden';
|
||||
ele.appendChild(stickEle);
|
||||
stickEle.style.position = 'absolute';
|
||||
stickEle.style.visibility = 'hidden';
|
||||
|
||||
/**
|
||||
* 按压或鼠标点击后渲染摇杆
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function down(event: MouseEvent) {
|
||||
if (event.buttons === 1) {
|
||||
console.log('🚀 ~ file: ContextMenu.provider.tsx:81 ~ down ~ event:', event);
|
||||
pressing = true;
|
||||
prevX = getClientPosition(event).x;
|
||||
prevY = getClientPosition(event).y;
|
||||
baseEle.style.visibility = 'visible';
|
||||
stickEle.style.visibility = 'visible';
|
||||
stickMove(stickEle.style, prevX - stickEle.width / 2, prevY - stickEle.height / 2);
|
||||
stickMove(baseEle.style, prevX - baseEle.width / 2, prevY - baseEle.height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消按压或松开鼠标后隐藏摇杆,并重置display坐标
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function up(event) {
|
||||
pressing = false;
|
||||
baseEle.style.visibility = 'hidden';
|
||||
stickEle.style.visibility = 'hidden';
|
||||
displayX = 0;
|
||||
displayY = 0;
|
||||
}
|
||||
|
||||
function moveTable(relX: number, relY: number) {
|
||||
const tables = document.querySelectorAll('.ant-table-content');
|
||||
const main = document.querySelector('main');
|
||||
tables.forEach((table) => {
|
||||
table.scrollBy(relX * 10, 0);
|
||||
});
|
||||
main.scrollBy(0, relY * 10 * -1);
|
||||
}
|
||||
|
||||
let moveRelX = 0;
|
||||
let moveRelY = 0;
|
||||
|
||||
const timerId = setInterval(() => {
|
||||
if (pressing) {
|
||||
moveTable(moveRelX, moveRelY);
|
||||
}
|
||||
}, 16);
|
||||
|
||||
/**
|
||||
* 移动鼠标响应事件,根据移动坐标计算相对坐标以及需要渲染的坐标,
|
||||
* 并根据半径限制距离,对计算后的值进行四舍五入
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function move(event) {
|
||||
if (pressing) {
|
||||
newX = getClientPosition(event).x;
|
||||
newY = getClientPosition(event).y;
|
||||
relX = newX - prevX;
|
||||
relY = prevY - newY;
|
||||
const distance = Math.sqrt(Math.pow(relX, 2) + Math.pow(relY, 2));
|
||||
const stickNormalizedX = relX / distance;
|
||||
const stickNormalizedY = relY / distance;
|
||||
moveRelX = stickNormalizedX;
|
||||
moveRelY = stickNormalizedY;
|
||||
// moveTable(stickNormalizedX, stickNormalizedY);
|
||||
if (distance <= config.radius) {
|
||||
moveX = newX - stickEle.width / 2;
|
||||
moveY = newY - stickEle.height / 2;
|
||||
stickMove(stickEle.style, moveX, moveY);
|
||||
} else {
|
||||
moveX = stickNormalizedX * config.radius + prevX - stickEle.width / 2;
|
||||
moveY = -stickNormalizedY * config.radius + prevY - stickEle.height / 2;
|
||||
stickMove(stickEle.style, moveX, moveY);
|
||||
}
|
||||
displayX = Math.round(stickNormalizedX * config.radius);
|
||||
displayY = Math.round(stickNormalizedY * config.radius);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建摇杆canvas
|
||||
*/
|
||||
function createStickCanvas() {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 86;
|
||||
canvas.height = 86;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 6;
|
||||
ctx.arc(canvas.width / 2, canvas.width / 2, 40, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
return canvas;
|
||||
}
|
||||
/**
|
||||
* 创建摇杆底座canvas
|
||||
*/
|
||||
function createBaseCanvas() {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 126;
|
||||
canvas.height = 126;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 6;
|
||||
ctx.arc(canvas.width / 2, canvas.width / 2, 40, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.arc(canvas.width / 2, canvas.width / 2, 60, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
|
||||
return canvas;
|
||||
}
|
||||
/**
|
||||
* 移动摇杆
|
||||
* @param {} style 传入摇杆底座/摇杆的style属性
|
||||
* @param {} x x轴移动距离
|
||||
* @param {} y y轴移动距离
|
||||
* @example stickMove(stickEle.style, (prevX - stickEle.width/2), (prevY - stickEle.height/2));
|
||||
*/
|
||||
function stickMove(style, x, y) {
|
||||
const transform = supportTransform();
|
||||
if (transform) {
|
||||
style[transform] = 'translate(' + x + 'px,' + y + 'px)';
|
||||
} else {
|
||||
style.left = x + 'px';
|
||||
style.top = y + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看是否支持translate
|
||||
*/
|
||||
function supportTransform() {
|
||||
const styles = ['webkitTransform', 'MozTransform', 'msTransform', 'OTransform', 'transform'];
|
||||
|
||||
const el = document.createElement('p');
|
||||
let style;
|
||||
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
style = styles[i];
|
||||
if (null != el.style[style]) {
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取client坐标,不同的响应对象取值方法不同
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function getClientPosition(event) {
|
||||
if (event instanceof TouchEvent) {
|
||||
return {
|
||||
x: event.touches[0].clientX,
|
||||
y: event.touches[0].clientY,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(timerId);
|
||||
};
|
||||
});
|
||||
useJoystick(ref);
|
||||
return (
|
||||
<ContextMenuContext.Provider value={{ contextMenuEnabled: enable, setContextMenuEnable: setEnable }}>
|
||||
{enable ? (
|
||||
<>
|
||||
<ContextMenu id="my-context-menu-1">
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
setEnableDragable((enable) => !enable);
|
||||
}}
|
||||
>
|
||||
拖动助手
|
||||
</ContextMenuItem>
|
||||
</ContextMenu>
|
||||
<ContextMenuTrigger id="my-context-menu-1">
|
||||
{enableDragable && (
|
||||
<div
|
||||
ref={ref}
|
||||
className={css`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background-color: rgba(141, 141, 113, 0.274);
|
||||
`}
|
||||
></div>
|
||||
)}
|
||||
{children}
|
||||
</ContextMenuTrigger>
|
||||
</>
|
||||
) : (
|
||||
<>{children}</>
|
||||
)}
|
||||
<ContextMenu id="my-context-menu-1">
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
setEnableDragable((enable) => !enable);
|
||||
}}
|
||||
>
|
||||
拖动助手
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
setDesignable(!designable);
|
||||
}}
|
||||
>
|
||||
设计者模式
|
||||
</ContextMenuItem>
|
||||
<ContextMenuItem
|
||||
onClick={() => {
|
||||
setEnable(!enable);
|
||||
}}
|
||||
>
|
||||
禁用右键菜单
|
||||
</ContextMenuItem>
|
||||
</ContextMenu>
|
||||
<ContextMenuTrigger disable={!enable} id="my-context-menu-1">
|
||||
{enableDragable && (
|
||||
<div
|
||||
ref={ref}
|
||||
className={css`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background-color: rgba(141, 141, 113, 0.274);
|
||||
`}
|
||||
></div>
|
||||
)}
|
||||
{children}
|
||||
</ContextMenuTrigger>
|
||||
</ContextMenuContext.Provider>
|
||||
);
|
||||
};
|
||||
|
@ -0,0 +1,237 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useJoystick = (ref) => {
|
||||
useEffect(() => {
|
||||
if (!ref.current) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* 摇杆配置
|
||||
*/
|
||||
const config = {
|
||||
/** 摇杆半径 */
|
||||
radius: 100,
|
||||
};
|
||||
|
||||
/**
|
||||
* 摇杆区域元素,摇杆只会渲染在该区域
|
||||
*/
|
||||
const ele = ref.current;
|
||||
|
||||
/** 是否正在按压 */
|
||||
let pressing = false;
|
||||
|
||||
/** 初始X坐标 */
|
||||
let prevX = 0;
|
||||
/** 初始Y坐标 */
|
||||
let prevY = 0;
|
||||
|
||||
/** 当前X坐标 */
|
||||
let newX = 0;
|
||||
/** 当前Y坐标 */
|
||||
let newY = 0;
|
||||
|
||||
/** 相对X坐标 */
|
||||
let relX = 0;
|
||||
/** 相对Y坐标 */
|
||||
let relY = 0;
|
||||
|
||||
/** 摇杆canvas移动后应在的X坐标 */
|
||||
let moveX = 0;
|
||||
/** 摇杆canvas移动后应在的Y坐标 */
|
||||
let moveY = 0;
|
||||
|
||||
/** 根据半径限制相对坐标后的X坐标 */
|
||||
let displayX = 0;
|
||||
/** 根据半径限制相对坐标后的Y坐标 */
|
||||
let displayY = 0;
|
||||
|
||||
ele.addEventListener('mousedown', down);
|
||||
ele.addEventListener('mousemove', move);
|
||||
ele.addEventListener('mouseup', up);
|
||||
|
||||
ele.addEventListener('touchstart', down);
|
||||
ele.addEventListener('touchmove', move);
|
||||
ele.addEventListener('touchend', up);
|
||||
|
||||
const stickEle = createStickCanvas();
|
||||
const baseEle = createBaseCanvas();
|
||||
|
||||
// ele.style.position = 'fixed';
|
||||
ele.appendChild(baseEle);
|
||||
baseEle.style.position = 'absolute';
|
||||
baseEle.style.visibility = 'hidden';
|
||||
ele.appendChild(stickEle);
|
||||
stickEle.style.position = 'absolute';
|
||||
stickEle.style.visibility = 'hidden';
|
||||
|
||||
/**
|
||||
* 按压或鼠标点击后渲染摇杆
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function down(event: MouseEvent) {
|
||||
if (event.buttons === 1) {
|
||||
console.log('🚀 ~ file: ContextMenu.provider.tsx:81 ~ down ~ event:', event);
|
||||
pressing = true;
|
||||
prevX = getClientPosition(event).x;
|
||||
prevY = getClientPosition(event).y;
|
||||
baseEle.style.visibility = 'visible';
|
||||
stickEle.style.visibility = 'visible';
|
||||
stickMove(stickEle.style, prevX - stickEle.width / 2, prevY - stickEle.height / 2);
|
||||
stickMove(baseEle.style, prevX - baseEle.width / 2, prevY - baseEle.height / 2);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消按压或松开鼠标后隐藏摇杆,并重置display坐标
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function up(event) {
|
||||
pressing = false;
|
||||
baseEle.style.visibility = 'hidden';
|
||||
stickEle.style.visibility = 'hidden';
|
||||
displayX = 0;
|
||||
displayY = 0;
|
||||
}
|
||||
|
||||
function moveTable(relX: number, relY: number) {
|
||||
const tables = document.querySelectorAll('.ant-table-content');
|
||||
const main = document.querySelector('main');
|
||||
tables.forEach((table) => {
|
||||
table.scrollBy(relX * 10, 0);
|
||||
});
|
||||
main.scrollBy(0, relY * 10 * -1);
|
||||
}
|
||||
|
||||
let moveRelX = 0;
|
||||
let moveRelY = 0;
|
||||
|
||||
const timerId = setInterval(() => {
|
||||
if (pressing) {
|
||||
moveTable(moveRelX, moveRelY);
|
||||
}
|
||||
}, 16);
|
||||
|
||||
/**
|
||||
* 移动鼠标响应事件,根据移动坐标计算相对坐标以及需要渲染的坐标,
|
||||
* 并根据半径限制距离,对计算后的值进行四舍五入
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function move(event) {
|
||||
if (pressing) {
|
||||
newX = getClientPosition(event).x;
|
||||
newY = getClientPosition(event).y;
|
||||
relX = newX - prevX;
|
||||
relY = prevY - newY;
|
||||
const distance = Math.sqrt(Math.pow(relX, 2) + Math.pow(relY, 2));
|
||||
const stickNormalizedX = relX / distance;
|
||||
const stickNormalizedY = relY / distance;
|
||||
moveRelX = stickNormalizedX;
|
||||
moveRelY = stickNormalizedY;
|
||||
// moveTable(stickNormalizedX, stickNormalizedY);
|
||||
if (distance <= config.radius) {
|
||||
moveX = newX - stickEle.width / 2;
|
||||
moveY = newY - stickEle.height / 2;
|
||||
stickMove(stickEle.style, moveX, moveY);
|
||||
} else {
|
||||
moveX = stickNormalizedX * config.radius + prevX - stickEle.width / 2;
|
||||
moveY = -stickNormalizedY * config.radius + prevY - stickEle.height / 2;
|
||||
stickMove(stickEle.style, moveX, moveY);
|
||||
}
|
||||
displayX = Math.round(stickNormalizedX * config.radius);
|
||||
displayY = Math.round(stickNormalizedY * config.radius);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建摇杆canvas
|
||||
*/
|
||||
function createStickCanvas() {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 86;
|
||||
canvas.height = 86;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 6;
|
||||
ctx.arc(canvas.width / 2, canvas.width / 2, 40, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
return canvas;
|
||||
}
|
||||
/**
|
||||
* 创建摇杆底座canvas
|
||||
*/
|
||||
function createBaseCanvas() {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 126;
|
||||
canvas.height = 126;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 6;
|
||||
ctx.arc(canvas.width / 2, canvas.width / 2, 40, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.arc(canvas.width / 2, canvas.width / 2, 60, 0, Math.PI * 2, true);
|
||||
ctx.stroke();
|
||||
|
||||
return canvas;
|
||||
}
|
||||
/**
|
||||
* 移动摇杆
|
||||
* @param {} style 传入摇杆底座/摇杆的style属性
|
||||
* @param {} x x轴移动距离
|
||||
* @param {} y y轴移动距离
|
||||
* @example stickMove(stickEle.style, (prevX - stickEle.width/2), (prevY - stickEle.height/2));
|
||||
*/
|
||||
function stickMove(style, x, y) {
|
||||
const transform = supportTransform();
|
||||
if (transform) {
|
||||
style[transform] = 'translate(' + x + 'px,' + y + 'px)';
|
||||
} else {
|
||||
style.left = x + 'px';
|
||||
style.top = y + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看是否支持translate
|
||||
*/
|
||||
function supportTransform() {
|
||||
const styles = ['webkitTransform', 'MozTransform', 'msTransform', 'OTransform', 'transform'];
|
||||
|
||||
const el = document.createElement('p');
|
||||
let style;
|
||||
|
||||
for (let i = 0; i < styles.length; i++) {
|
||||
style = styles[i];
|
||||
if (null != el.style[style]) {
|
||||
return style;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取client坐标,不同的响应对象取值方法不同
|
||||
* @param {} event TouchEvent | MouseEvent
|
||||
*/
|
||||
function getClientPosition(event) {
|
||||
if (event instanceof TouchEvent) {
|
||||
return {
|
||||
x: event.touches[0].clientX,
|
||||
y: event.touches[0].clientY,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
x: event.clientX,
|
||||
y: event.clientY,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
clearInterval(timerId);
|
||||
};
|
||||
});
|
||||
};
|
Loading…
Reference in New Issue
Block a user