2020-11-04 15:03:34 +11:00
|
|
|
import React, { useContext, useEffect, useState, useRef } from "react";
|
2020-11-05 15:17:23 +11:00
|
|
|
import { Rect, Text } from "react-konva";
|
|
|
|
|
import { useSpring, animated } from "react-spring/konva";
|
2020-11-03 17:15:39 +11:00
|
|
|
|
2020-11-04 15:03:34 +11:00
|
|
|
import AuthContext from "../../contexts/AuthContext";
|
2020-11-03 17:15:39 +11:00
|
|
|
import MapInteractionContext from "../../contexts/MapInteractionContext";
|
|
|
|
|
|
2020-11-04 15:03:34 +11:00
|
|
|
import * as Vector2 from "../../helpers/vector2";
|
|
|
|
|
import colors from "../../helpers/colors";
|
2020-11-05 15:17:23 +11:00
|
|
|
import usePrevious from "../../helpers/usePrevious";
|
2020-11-04 15:03:34 +11:00
|
|
|
|
|
|
|
|
const snappingThreshold = 1 / 5;
|
|
|
|
|
const textPadding = 4;
|
|
|
|
|
|
2020-11-05 14:41:33 +11:00
|
|
|
function Note({
|
|
|
|
|
note,
|
|
|
|
|
map,
|
|
|
|
|
onNoteChange,
|
|
|
|
|
onNoteMenuOpen,
|
|
|
|
|
draggable,
|
|
|
|
|
onNoteDragStart,
|
|
|
|
|
onNoteDragEnd,
|
|
|
|
|
}) {
|
2020-11-04 15:03:34 +11:00
|
|
|
const { userId } = useContext(AuthContext);
|
2020-11-05 12:28:28 +11:00
|
|
|
const { mapWidth, mapHeight, setPreventMapInteraction } = useContext(
|
|
|
|
|
MapInteractionContext
|
|
|
|
|
);
|
2020-11-03 17:15:39 +11:00
|
|
|
|
|
|
|
|
const noteWidth = map && (mapWidth / map.grid.size.x) * note.size;
|
|
|
|
|
const noteHeight = map && (mapHeight / map.grid.size.y) * note.size;
|
|
|
|
|
|
2020-11-05 14:41:33 +11:00
|
|
|
function handleDragStart(event) {
|
|
|
|
|
onNoteDragStart && onNoteDragStart(event, note.id);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-04 15:03:34 +11:00
|
|
|
function handleDragMove(event) {
|
|
|
|
|
const noteGroup = event.target;
|
|
|
|
|
// Snap to corners of grid
|
|
|
|
|
if (map.snapToGrid) {
|
|
|
|
|
const offset = Vector2.multiply(map.grid.inset.topLeft, {
|
|
|
|
|
x: mapWidth,
|
|
|
|
|
y: mapHeight,
|
|
|
|
|
});
|
|
|
|
|
const position = {
|
|
|
|
|
x: noteGroup.x() + noteGroup.width() / 2,
|
|
|
|
|
y: noteGroup.y() + noteGroup.height() / 2,
|
|
|
|
|
};
|
|
|
|
|
const gridSize = {
|
|
|
|
|
x:
|
|
|
|
|
(mapWidth *
|
|
|
|
|
(map.grid.inset.bottomRight.x - map.grid.inset.topLeft.x)) /
|
|
|
|
|
map.grid.size.x,
|
|
|
|
|
y:
|
|
|
|
|
(mapHeight *
|
|
|
|
|
(map.grid.inset.bottomRight.y - map.grid.inset.topLeft.y)) /
|
|
|
|
|
map.grid.size.y,
|
|
|
|
|
};
|
|
|
|
|
// Transform into offset space, round, then transform back
|
|
|
|
|
const gridSnap = Vector2.add(
|
|
|
|
|
Vector2.roundTo(Vector2.subtract(position, offset), gridSize),
|
|
|
|
|
offset
|
|
|
|
|
);
|
|
|
|
|
const gridDistance = Vector2.length(Vector2.subtract(gridSnap, position));
|
|
|
|
|
const minGrid = Vector2.min(gridSize);
|
|
|
|
|
if (gridDistance < minGrid * snappingThreshold) {
|
|
|
|
|
noteGroup.x(gridSnap.x - noteGroup.width() / 2);
|
|
|
|
|
noteGroup.y(gridSnap.y - noteGroup.height() / 2);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDragEnd(event) {
|
|
|
|
|
const noteGroup = event.target;
|
|
|
|
|
onNoteChange &&
|
|
|
|
|
onNoteChange({
|
|
|
|
|
...note,
|
|
|
|
|
x: noteGroup.x() / mapWidth,
|
|
|
|
|
y: noteGroup.y() / mapHeight,
|
|
|
|
|
lastModifiedBy: userId,
|
|
|
|
|
lastModified: Date.now(),
|
|
|
|
|
});
|
2020-11-05 14:41:33 +11:00
|
|
|
onNoteDragEnd && onNoteDragEnd(note.id);
|
|
|
|
|
setPreventMapInteraction(false);
|
2020-11-04 15:03:34 +11:00
|
|
|
}
|
|
|
|
|
|
2020-11-05 15:30:22 +11:00
|
|
|
function handleClick(event) {
|
|
|
|
|
if (draggable) {
|
|
|
|
|
const noteNode = event.target;
|
|
|
|
|
onNoteMenuOpen && onNoteMenuOpen(note.id, noteNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Store note pointer down time to check for a click when note is locked
|
|
|
|
|
const notePointerDownTimeRef = useRef();
|
|
|
|
|
function handlePointerDown(event) {
|
2020-11-05 12:28:28 +11:00
|
|
|
if (draggable) {
|
|
|
|
|
setPreventMapInteraction(true);
|
|
|
|
|
}
|
2020-11-05 15:30:22 +11:00
|
|
|
if (note.locked && map.owner === userId) {
|
|
|
|
|
notePointerDownTimeRef.current = event.evt.timeStamp;
|
|
|
|
|
}
|
2020-11-05 12:28:28 +11:00
|
|
|
}
|
|
|
|
|
|
2020-11-05 15:30:22 +11:00
|
|
|
function handlePointerUp(event) {
|
2020-11-05 12:28:28 +11:00
|
|
|
if (draggable) {
|
|
|
|
|
setPreventMapInteraction(false);
|
|
|
|
|
}
|
2020-11-05 15:30:22 +11:00
|
|
|
// Check note click when locked and we are the map owner
|
|
|
|
|
// We can't use onClick because that doesn't check pointer distance
|
|
|
|
|
if (note.locked && map.owner === userId) {
|
|
|
|
|
// If down and up time is small trigger a click
|
|
|
|
|
const delta = event.evt.timeStamp - notePointerDownTimeRef.current;
|
|
|
|
|
if (delta < 300) {
|
|
|
|
|
const noteNode = event.target;
|
|
|
|
|
onNoteMenuOpen(note.id, noteNode);
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-11-05 12:28:28 +11:00
|
|
|
}
|
|
|
|
|
|
2020-11-04 15:03:34 +11:00
|
|
|
const [fontSize, setFontSize] = useState(1);
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
const text = textRef.current;
|
2020-11-05 15:17:23 +11:00
|
|
|
|
|
|
|
|
if (!text) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-04 15:03:34 +11:00
|
|
|
function findFontSize() {
|
|
|
|
|
// Create an array from 4 to 18 scaled to the note size
|
|
|
|
|
const sizes = Array.from(
|
|
|
|
|
{ length: 14 * note.size },
|
|
|
|
|
(_, i) => i + 4 * note.size
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return sizes.reduce((prev, curr) => {
|
|
|
|
|
text.fontSize(curr);
|
|
|
|
|
const width = text.getTextWidth() + textPadding * 2;
|
|
|
|
|
if (width < noteWidth) {
|
|
|
|
|
return curr;
|
|
|
|
|
} else {
|
|
|
|
|
return prev;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
setFontSize(findFontSize());
|
|
|
|
|
}, [note, noteWidth]);
|
|
|
|
|
|
|
|
|
|
const textRef = useRef();
|
|
|
|
|
|
2020-11-05 15:17:23 +11:00
|
|
|
// Animate to new note positions if edited by others
|
|
|
|
|
const noteX = note.x * mapWidth;
|
|
|
|
|
const noteY = note.y * mapHeight;
|
|
|
|
|
const previousWidth = usePrevious(mapWidth);
|
|
|
|
|
const previousHeight = usePrevious(mapHeight);
|
|
|
|
|
const resized = mapWidth !== previousWidth || mapHeight !== previousHeight;
|
|
|
|
|
const skipAnimation = note.lastModifiedBy === userId || resized;
|
|
|
|
|
const props = useSpring({
|
|
|
|
|
x: noteX,
|
|
|
|
|
y: noteY,
|
|
|
|
|
immediate: skipAnimation,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// When a note is hidden if you aren't the map owner hide it completely
|
|
|
|
|
if (map && !note.visible && map.owner !== userId) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-03 17:15:39 +11:00
|
|
|
return (
|
2020-11-05 15:17:23 +11:00
|
|
|
<animated.Group
|
|
|
|
|
{...props}
|
2020-11-04 15:03:34 +11:00
|
|
|
onClick={handleClick}
|
|
|
|
|
onTap={handleClick}
|
|
|
|
|
width={noteWidth}
|
|
|
|
|
height={noteHeight}
|
|
|
|
|
offsetX={noteWidth / 2}
|
|
|
|
|
offsetY={noteHeight / 2}
|
|
|
|
|
draggable={draggable}
|
2020-11-05 14:41:33 +11:00
|
|
|
onDragStart={handleDragStart}
|
2020-11-04 15:03:34 +11:00
|
|
|
onDragEnd={handleDragEnd}
|
|
|
|
|
onDragMove={handleDragMove}
|
2020-11-05 12:28:28 +11:00
|
|
|
onMouseDown={handlePointerDown}
|
|
|
|
|
onMouseUp={handlePointerUp}
|
|
|
|
|
onTouchStart={handlePointerDown}
|
|
|
|
|
onTouchEnd={handlePointerUp}
|
2020-11-05 15:17:23 +11:00
|
|
|
opacity={note.visible ? 1.0 : 0.5}
|
2020-11-04 15:03:34 +11:00
|
|
|
>
|
2020-11-03 17:15:39 +11:00
|
|
|
<Rect
|
|
|
|
|
width={noteWidth}
|
|
|
|
|
height={noteHeight}
|
|
|
|
|
shadowColor="rgba(0, 0, 0, 0.16)"
|
|
|
|
|
shadowOffset={{ x: 0, y: 3 }}
|
|
|
|
|
shadowBlur={6}
|
2020-11-04 15:03:34 +11:00
|
|
|
cornerRadius={0.25}
|
|
|
|
|
fill={colors[note.color]}
|
|
|
|
|
/>
|
|
|
|
|
<Text
|
|
|
|
|
text={note.text}
|
2020-11-05 14:48:32 +11:00
|
|
|
fill={
|
|
|
|
|
note.color === "black" || note.color === "darkGray"
|
|
|
|
|
? "white"
|
|
|
|
|
: "black"
|
|
|
|
|
}
|
2020-11-04 15:03:34 +11:00
|
|
|
align="center"
|
|
|
|
|
verticalAlign="middle"
|
|
|
|
|
padding={textPadding}
|
|
|
|
|
fontSize={fontSize}
|
|
|
|
|
wrap="word"
|
|
|
|
|
width={noteWidth}
|
|
|
|
|
height={noteHeight}
|
2020-11-05 12:28:28 +11:00
|
|
|
ellipsis={true}
|
2020-11-03 17:15:39 +11:00
|
|
|
/>
|
2020-11-04 15:03:34 +11:00
|
|
|
{/* Use an invisible text block to work out text sizing */}
|
|
|
|
|
<Text visible={false} ref={textRef} text={note.text} wrap="none" />
|
2020-11-05 15:17:23 +11:00
|
|
|
</animated.Group>
|
2020-11-03 17:15:39 +11:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2020-11-05 14:41:33 +11:00
|
|
|
export default Note;
|