chore: optimize context menu behaviour (#885)

Co-authored-by: sealday <sealday@gmail.com>
Reviewed-on: daoyoucloud/tachybase#885
This commit is contained in:
sealday 2024-04-29 01:17:14 +08:00
parent 51335834c8
commit cfa2bc9775
2 changed files with 282 additions and 267 deletions

View File

@ -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>
);
};

View File

@ -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);
};
});
};