feat: add basic touchscreen support
This is basically a migration from mouse events to [pointer events]( https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events ). The `PointerEvent` interface inherits all of the `MouseEvent` properties, meaning that existing code can essentially be left as-is. The only major change is making sure we only respond to the "primary" pointer. Known issues include: * stylus hover is not detected * touchscreens do not have a concept of hover, making it difficult to e.g. resize areas * no touch gesture support, e.g. "pinch-to-zoom"
This commit is contained in:
parent
075a98d444
commit
cdecf7c633
@ -20,7 +20,7 @@ import {
|
||||
import ColorPalette from "../ColorPicker";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
export default function Area({ data, onPointerDown, setResize, setInitCoords }) {
|
||||
const [hovered, setHovered] = useState(false);
|
||||
const { layout } = useLayout();
|
||||
const { settings } = useSettings();
|
||||
@ -35,8 +35,8 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
y: data.y,
|
||||
width: data.width,
|
||||
height: data.height,
|
||||
mouseX: e.clientX / transform.zoom,
|
||||
mouseY: e.clientY / transform.zoom,
|
||||
pointerX: e.clientX / transform.zoom,
|
||||
pointerY: e.clientY / transform.zoom,
|
||||
});
|
||||
};
|
||||
|
||||
@ -85,8 +85,8 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
|
||||
return (
|
||||
<g
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
onPointerEnter={(e) => e.isPrimary && setHovered(true)}
|
||||
onPointerLeave={(e) => e.isPrimary &&setHovered(false)}
|
||||
>
|
||||
<foreignObject
|
||||
key={data.id}
|
||||
@ -94,7 +94,7 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
y={data.y}
|
||||
width={data.width > 0 ? data.width : 0}
|
||||
height={data.height > 0 ? data.height : 0}
|
||||
onMouseDown={onMouseDown}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div
|
||||
className={`border-2 ${
|
||||
@ -149,7 +149,7 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nwse-resize"
|
||||
onMouseDown={(e) => handleResize(e, "tl")}
|
||||
onPointerDown={(e) => e.isPrimary && handleResize(e, "tl")}
|
||||
/>
|
||||
<circle
|
||||
cx={data.x + data.width}
|
||||
@ -159,7 +159,7 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nesw-resize"
|
||||
onMouseDown={(e) => handleResize(e, "tr")}
|
||||
onPointerDown={(e) => e.isPrimary && handleResize(e, "tr")}
|
||||
/>
|
||||
<circle
|
||||
cx={data.x}
|
||||
@ -169,7 +169,7 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nesw-resize"
|
||||
onMouseDown={(e) => handleResize(e, "bl")}
|
||||
onPointerDown={(e) => e.isPrimary && handleResize(e, "bl")}
|
||||
/>
|
||||
<circle
|
||||
cx={data.x + data.width}
|
||||
@ -179,7 +179,7 @@ export default function Area({ data, onMouseDown, setResize, setInitCoords }) {
|
||||
stroke="#5891db"
|
||||
strokeWidth={2}
|
||||
cursor="nwse-resize"
|
||||
onMouseDown={(e) => handleResize(e, "br")}
|
||||
onPointerDown={(e) => e.isPrimary && handleResize(e, "br")}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -68,14 +68,21 @@ export default function Canvas() {
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
mouseX: 0,
|
||||
mouseY: 0,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
});
|
||||
const [cursor, setCursor] = useState("default");
|
||||
|
||||
const canvas = useRef(null);
|
||||
|
||||
const handleMouseDownOnElement = (e, id, type) => {
|
||||
/**
|
||||
* @param {PointerEvent} e
|
||||
* @param {*} id
|
||||
* @param {ObjectType[keyof ObjectType]} type
|
||||
*/
|
||||
const handlePointerDownOnElement = (e, id, type) => {
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
const { clientX, clientY } = e;
|
||||
if (type === ObjectType.TABLE) {
|
||||
const table = tables.find((t) => t.id === id);
|
||||
@ -122,7 +129,12 @@ export default function Canvas() {
|
||||
}));
|
||||
};
|
||||
|
||||
const handleMouseMove = (e) => {
|
||||
/**
|
||||
* @param {PointerEvent} e
|
||||
*/
|
||||
const handlePointerMove = (e) => {
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
if (linking) {
|
||||
const rect = canvas.current.getBoundingClientRect();
|
||||
setLinkingLine({
|
||||
@ -164,34 +176,39 @@ export default function Canvas() {
|
||||
} else if (areaResize.id !== -1) {
|
||||
if (areaResize.dir === "none") return;
|
||||
let newDims = { ...initCoords };
|
||||
delete newDims.mouseX;
|
||||
delete newDims.mouseY;
|
||||
const mouseX = e.clientX / transform.zoom;
|
||||
const mouseY = e.clientY / transform.zoom;
|
||||
delete newDims.pointerX;
|
||||
delete newDims.pointerY;
|
||||
const pointerX = e.clientX / transform.zoom;
|
||||
const pointerY = e.clientY / transform.zoom;
|
||||
setPanning({ isPanning: false, x: 0, y: 0 });
|
||||
if (areaResize.dir === "br") {
|
||||
newDims.width = initCoords.width + (mouseX - initCoords.mouseX);
|
||||
newDims.height = initCoords.height + (mouseY - initCoords.mouseY);
|
||||
newDims.width = initCoords.width + (pointerX - initCoords.pointerX);
|
||||
newDims.height = initCoords.height + (pointerY - initCoords.pointerY);
|
||||
} else if (areaResize.dir === "tl") {
|
||||
newDims.x = initCoords.x + (mouseX - initCoords.mouseX);
|
||||
newDims.y = initCoords.y + (mouseY - initCoords.mouseY);
|
||||
newDims.width = initCoords.width - (mouseX - initCoords.mouseX);
|
||||
newDims.height = initCoords.height - (mouseY - initCoords.mouseY);
|
||||
newDims.x = initCoords.x + (pointerX - initCoords.pointerX);
|
||||
newDims.y = initCoords.y + (pointerY - initCoords.pointerY);
|
||||
newDims.width = initCoords.width - (pointerX - initCoords.pointerX);
|
||||
newDims.height = initCoords.height - (pointerY - initCoords.pointerY);
|
||||
} else if (areaResize.dir === "tr") {
|
||||
newDims.y = initCoords.y + (mouseY - initCoords.mouseY);
|
||||
newDims.width = initCoords.width + (mouseX - initCoords.mouseX);
|
||||
newDims.height = initCoords.height - (mouseY - initCoords.mouseY);
|
||||
newDims.y = initCoords.y + (pointerY - initCoords.pointerY);
|
||||
newDims.width = initCoords.width + (pointerX - initCoords.pointerX);
|
||||
newDims.height = initCoords.height - (pointerY - initCoords.pointerY);
|
||||
} else if (areaResize.dir === "bl") {
|
||||
newDims.x = initCoords.x + (mouseX - initCoords.mouseX);
|
||||
newDims.width = initCoords.width - (mouseX - initCoords.mouseX);
|
||||
newDims.height = initCoords.height + (mouseY - initCoords.mouseY);
|
||||
newDims.x = initCoords.x + (pointerX - initCoords.pointerX);
|
||||
newDims.width = initCoords.width - (pointerX - initCoords.pointerX);
|
||||
newDims.height = initCoords.height + (pointerY - initCoords.pointerY);
|
||||
}
|
||||
|
||||
updateArea(areaResize.id, { ...newDims });
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseDown = (e) => {
|
||||
/**
|
||||
* @param {PointerEvent} e
|
||||
*/
|
||||
const handlePointerDown = (e) => {
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
// don't pan if the sidesheet for editing a table is open
|
||||
if (
|
||||
selectedElement.element === ObjectType.TABLE &&
|
||||
@ -268,7 +285,12 @@ export default function Canvas() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
/**
|
||||
* @param {PointerEvent} e
|
||||
*/
|
||||
const handlePointerUp = (e) => {
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
if (coordsDidUpdate(dragging.element)) {
|
||||
const info = getMovedElementDetails();
|
||||
setUndoStack((prev) => [
|
||||
@ -344,8 +366,8 @@ export default function Canvas() {
|
||||
y: 0,
|
||||
width: 0,
|
||||
height: 0,
|
||||
mouseX: 0,
|
||||
mouseY: 0,
|
||||
pointerX: 0,
|
||||
pointerY: 0,
|
||||
});
|
||||
};
|
||||
|
||||
@ -411,12 +433,12 @@ export default function Canvas() {
|
||||
const theme = localStorage.getItem("theme");
|
||||
|
||||
return (
|
||||
<div className="flex-grow h-full" id="canvas">
|
||||
<div className="flex-grow h-full touch-none" id="canvas">
|
||||
<div ref={canvas} className="w-full h-full">
|
||||
<svg
|
||||
onMouseMove={handleMouseMove}
|
||||
onMouseDown={handleMouseDown}
|
||||
onMouseUp={handleMouseUp}
|
||||
onPointerMove={handlePointerMove}
|
||||
onPointerDown={handlePointerDown}
|
||||
onPointerUp={handlePointerUp}
|
||||
className="w-full h-full"
|
||||
style={{
|
||||
cursor: cursor,
|
||||
@ -464,8 +486,8 @@ export default function Canvas() {
|
||||
<Area
|
||||
key={a.id}
|
||||
data={a}
|
||||
onMouseDown={(e) =>
|
||||
handleMouseDownOnElement(e, a.id, ObjectType.AREA)
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, a.id, ObjectType.AREA)
|
||||
}
|
||||
setResize={setAreaResize}
|
||||
setInitCoords={setInitCoords}
|
||||
@ -481,8 +503,8 @@ export default function Canvas() {
|
||||
setHoveredTable={setHoveredTable}
|
||||
handleGripField={handleGripField}
|
||||
setLinkingLine={setLinkingLine}
|
||||
onMouseDown={(e) =>
|
||||
handleMouseDownOnElement(e, table.id, ObjectType.TABLE)
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, table.id, ObjectType.TABLE)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
@ -497,8 +519,8 @@ export default function Canvas() {
|
||||
<Note
|
||||
key={n.id}
|
||||
data={n}
|
||||
onMouseDown={(e) =>
|
||||
handleMouseDownOnElement(e, n.id, ObjectType.NOTE)
|
||||
onPointerDown={(e) =>
|
||||
handlePointerDownOnElement(e, n.id, ObjectType.NOTE)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
|
@ -21,7 +21,7 @@ import {
|
||||
} from "../../hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export default function Note({ data, onMouseDown }) {
|
||||
export default function Note({ data, onPointerDown }) {
|
||||
const w = 180;
|
||||
const r = 3;
|
||||
const fold = 24;
|
||||
@ -83,8 +83,8 @@ export default function Note({ data, onMouseDown }) {
|
||||
|
||||
return (
|
||||
<g
|
||||
onMouseEnter={() => setHovered(true)}
|
||||
onMouseLeave={() => setHovered(false)}
|
||||
onPointerEnter={(e) => e.isPrimary && setHovered(true)}
|
||||
onPointerLeave={(e) => e.isPrimary && setHovered(false)}
|
||||
>
|
||||
<path
|
||||
d={`M${data.x + fold} ${data.y} L${data.x + w - r} ${
|
||||
@ -133,7 +133,7 @@ export default function Note({ data, onMouseDown }) {
|
||||
y={data.y}
|
||||
width={w}
|
||||
height={data.height}
|
||||
onMouseDown={onMouseDown}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div className="text-gray-900 select-none w-full h-full cursor-move px-3 py-2">
|
||||
<div className="flex justify-between gap-1 w-full">
|
||||
|
@ -22,7 +22,7 @@ export default function Table(props) {
|
||||
const [hoveredField, setHoveredField] = useState(-1);
|
||||
const {
|
||||
tableData,
|
||||
onMouseDown,
|
||||
onPointerDown,
|
||||
setHoveredTable,
|
||||
handleGripField,
|
||||
setLinkingLine,
|
||||
@ -67,7 +67,7 @@ export default function Table(props) {
|
||||
width={settings.tableWidth}
|
||||
height={height}
|
||||
className="group drop-shadow-lg rounded-md cursor-move"
|
||||
onMouseDown={onMouseDown}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div
|
||||
onDoubleClick={openEditor}
|
||||
@ -266,14 +266,18 @@ export default function Table(props) {
|
||||
? ""
|
||||
: "border-b border-gray-400"
|
||||
} group h-[36px] px-2 py-1 flex justify-between items-center gap-1 w-full overflow-hidden`}
|
||||
onMouseEnter={() => {
|
||||
onPointerEnter={(e) => {
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
setHoveredField(index);
|
||||
setHoveredTable({
|
||||
tableId: tableData.id,
|
||||
field: index,
|
||||
});
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
onPointerLeave={(e) => {
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
setHoveredField(-1);
|
||||
}}
|
||||
>
|
||||
@ -284,7 +288,9 @@ export default function Table(props) {
|
||||
>
|
||||
<button
|
||||
className="flex-shrink-0 w-[10px] h-[10px] bg-[#2f68adcc] rounded-full"
|
||||
onMouseDown={() => {
|
||||
onPointerDown={(e) => {
|
||||
if (!e.isPrimary) return;
|
||||
|
||||
handleGripField(index);
|
||||
setLinkingLine((prev) => ({
|
||||
...prev,
|
||||
|
@ -1526,8 +1526,8 @@ export default function ControlPanel({
|
||||
)}
|
||||
<div
|
||||
className="text-xl me-1"
|
||||
onMouseEnter={() => setShowEditName(true)}
|
||||
onMouseLeave={() => setShowEditName(false)}
|
||||
onPointerEnter={(e) => e.isPrimary && setShowEditName(true)}
|
||||
onPointerLeave={(e) => e.isPrimary && setShowEditName(false)}
|
||||
onClick={() => setModal(MODAL.RENAME)}
|
||||
>
|
||||
{window.name.split(" ")[0] === "t" ? "Templates/" : "Diagrams/"}
|
||||
|
@ -83,7 +83,7 @@ export default function SidePanel({ width, resize, setResize }) {
|
||||
className={`flex justify-center items-center p-1 h-auto hover-2 cursor-col-resize ${
|
||||
resize && "bg-semi-grey-2"
|
||||
}`}
|
||||
onMouseDown={() => setResize(true)}
|
||||
onPointerDown={(e) => e.isPrimary && setResize(true)}
|
||||
>
|
||||
<div className="w-1 border-x border-color h-1/6" />
|
||||
</div>
|
||||
|
@ -24,9 +24,9 @@ function Table({ table, grab }) {
|
||||
width={tableWidth}
|
||||
height={height}
|
||||
className="drop-shadow-lg rounded-md cursor-move"
|
||||
onMouseDown={grab}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
onPointerDown={(e) => e.isPrimary && grab(e)}
|
||||
onPointerEnter={(e) => e.isPrimary && setIsHovered(true)}
|
||||
onPointerLeave={(e) => e.isPrimary && setIsHovered(false)}
|
||||
>
|
||||
<div
|
||||
className={`border-2 ${
|
||||
@ -46,8 +46,8 @@ function Table({ table, grab }) {
|
||||
className={`${
|
||||
i === table.fields.length - 1 ? "" : "border-b border-gray-400"
|
||||
} h-[36px] px-2 py-1 flex justify-between`}
|
||||
onMouseEnter={() => setHoveredField(i)}
|
||||
onMouseLeave={() => setHoveredField(-1)}
|
||||
onPointerEnter={(e) => e.isPrimary && setHoveredField(i)}
|
||||
onPointerLeave={(e) => e.isPrimary && setHoveredField(-1)}
|
||||
>
|
||||
<div className={hoveredField === i ? "text-zinc-500" : ""}>
|
||||
<button
|
||||
@ -185,9 +185,9 @@ export default function SimpleCanvas({ diagram, zoom }) {
|
||||
return (
|
||||
<svg
|
||||
className="w-full h-full cursor-grab"
|
||||
onMouseUp={releaseTable}
|
||||
onMouseMove={moveTable}
|
||||
onMouseLeave={releaseTable}
|
||||
onPointerUp={(e) => e.isPrimary && releaseTable()}
|
||||
onPointerMove={(e) => e.isPrimary && moveTable()}
|
||||
onPointerLeave={(e) => e.isPrimary && releaseTable()}
|
||||
>
|
||||
<defs>
|
||||
<pattern
|
||||
|
@ -349,9 +349,9 @@ export default function WorkSpace() {
|
||||
/>
|
||||
<div
|
||||
className="flex h-full overflow-y-auto"
|
||||
onMouseUp={() => setResize(false)}
|
||||
onMouseLeave={() => setResize(false)}
|
||||
onMouseMove={handleResize}
|
||||
onPointerUp={(e) => e.isPrimary && setResize(false)}
|
||||
onPointerLeave={(e) => e.isPrimary && setResize(false)}
|
||||
onPointerMove={(e) => e.isPrimary && handleResize(e)}
|
||||
>
|
||||
{layout.sidebar && (
|
||||
<SidePanel resize={resize} setResize={setResize} width={width} />
|
||||
|
Loading…
Reference in New Issue
Block a user