Files
grungnet/src/components/note/Note.js

219 lines
5.9 KiB
JavaScript
Raw Normal View History

import React, { useEffect, useState, useRef } from "react";
import { Rect, Text } from "react-konva";
import { useSpring, animated } from "react-spring/konva";
2020-11-03 17:15:39 +11:00
import { useAuth } from "../../contexts/AuthContext";
import { useMapInteraction } from "../../contexts/MapInteractionContext";
import { useGrid } from "../../contexts/GridContext";
2020-11-03 17:15:39 +11:00
2020-11-04 15:03:34 +11:00
import colors from "../../helpers/colors";
import usePrevious from "../../hooks/usePrevious";
import useGridSnapping from "../../hooks/useGridSnapping";
2020-11-04 15:03:34 +11:00
const snappingThreshold = 1 / 5;
2020-11-05 14:41:33 +11:00
function Note({
note,
map,
onNoteChange,
onNoteMenuOpen,
draggable,
onNoteDragStart,
onNoteDragEnd,
2021-01-28 16:26:28 +11:00
fadeOnHover,
2020-11-05 14:41:33 +11:00
}) {
const { userId } = useAuth();
const { mapWidth, mapHeight, setPreventMapInteraction } = useMapInteraction();
const { gridCellPixelSize } = useGrid();
2020-11-03 17:15:39 +11:00
const noteWidth = gridCellPixelSize.width * note.size;
2021-01-04 09:39:58 +11:00
const noteHeight = noteWidth;
const notePadding = noteWidth / 10;
2020-11-03 17:15:39 +11:00
const snapNodeToGrid = useGridSnapping(snappingThreshold);
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) {
snapNodeToGrid(noteGroup);
2020-11-04 15:03:34 +11:00
}
}
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
}
2021-01-28 16:26:28 +11:00
const [noteOpacity, setNoteOpacity] = useState(1);
function handlePointerEnter() {
if (fadeOnHover) {
setNoteOpacity(0.5);
}
}
function handlePointerLeave() {
if (noteOpacity !== 1.0) {
setNoteOpacity(1.0);
}
}
2020-11-04 15:03:34 +11:00
const [fontSize, setFontSize] = useState(1);
useEffect(() => {
const text = textRef.current;
if (!text) {
return;
}
2020-11-04 15:03:34 +11:00
function findFontSize() {
// Create an array from 1 / 10 of the note height to the full note height
2020-11-04 15:03:34 +11:00
const sizes = Array.from(
{ length: Math.ceil(noteHeight - notePadding * 2) },
(_, i) => i + Math.ceil(noteHeight / 10)
2020-11-04 15:03:34 +11:00
);
2020-11-27 12:23:57 +11:00
if (sizes.length > 0) {
const size = sizes.reduce((prev, curr) => {
text.fontSize(curr);
const width = text.getTextWidth() + notePadding * 2;
const height = text.height() + notePadding * 2;
if (width < noteWidth && height < noteHeight) {
2020-11-27 12:23:57 +11:00
return curr;
} else {
return prev;
}
});
setFontSize(size);
}
2020-11-04 15:03:34 +11:00
}
2020-11-27 12:23:57 +11:00
findFontSize();
2021-01-03 10:45:43 +11:00
}, [note, note.text, noteWidth, noteHeight, notePadding]);
2020-11-04 15:03:34 +11:00
const textRef = useRef();
// 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 (
<animated.Group
{...props}
2020-11-04 15:03:34 +11:00
onClick={handleClick}
onTap={handleClick}
width={noteWidth}
2021-01-25 10:03:20 +11:00
height={note.textOnly ? undefined : noteHeight}
2020-11-04 15:03:34 +11:00
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}
2021-01-28 16:26:28 +11:00
onMouseEnter={handlePointerEnter}
onMouseLeave={handlePointerLeave}
opacity={note.visible ? noteOpacity : 0.5}
2020-11-04 15:03:34 +11:00
>
2021-01-25 10:03:20 +11:00
{!note.textOnly && (
<Rect
width={noteWidth}
height={noteHeight}
shadowColor="rgba(0, 0, 0, 0.16)"
shadowOffset={{ x: 0, y: 3 }}
shadowBlur={6}
cornerRadius={0.25}
fill={colors[note.color]}
/>
)}
2020-11-04 15:03:34 +11:00
<Text
text={note.text}
fill={
2021-01-25 10:03:20 +11:00
note.textOnly
? colors[note.color]
: note.color === "black" || note.color === "darkGray"
? "white"
: "black"
}
2020-11-04 15:03:34 +11:00
align="center"
verticalAlign="middle"
padding={notePadding}
2020-11-04 15:03:34 +11:00
fontSize={fontSize}
wrap="word"
width={noteWidth}
2021-01-25 10:03:20 +11:00
height={note.textOnly ? undefined : noteHeight}
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" />
</animated.Group>
2020-11-03 17:15:39 +11:00
);
}
2020-11-05 14:41:33 +11:00
export default Note;