Add transformer to note

This commit is contained in:
Mitchell McCaffrey
2021-08-05 16:24:46 +10:00
parent 59d46e1d27
commit d1e62e850a
9 changed files with 170 additions and 135 deletions

View File

@@ -20,10 +20,13 @@ import { Note as NoteType } from "../../types/Note";
import {
NoteChangeEventHandler,
NoteDragEventHandler,
NoteMenuCloseEventHandler,
NoteMenuOpenEventHandler,
} from "../../types/Events";
import { Map } from "../../types/Map";
import Transformer from "./Transformer";
const defaultFontSize = 16;
type NoteProps = {
@@ -31,10 +34,12 @@ type NoteProps = {
map: Map | null;
onNoteChange?: NoteChangeEventHandler;
onNoteMenuOpen?: NoteMenuOpenEventHandler;
onNoteMenuClose?: NoteMenuCloseEventHandler;
draggable: boolean;
onNoteDragStart?: NoteDragEventHandler;
onNoteDragEnd?: NoteDragEventHandler;
fadeOnHover: boolean;
selected: boolean;
};
function Note({
@@ -42,10 +47,12 @@ function Note({
map,
onNoteChange,
onNoteMenuOpen,
onNoteMenuClose,
draggable,
onNoteDragStart,
onNoteDragEnd,
fadeOnHover,
selected,
}: NoteProps) {
const userId = useUserId();
@@ -173,6 +180,30 @@ function Note({
const textRef = useRef<Konva.Text>(null);
const noteRef = useRef<Konva.Group>(null);
const [isTransforming, setIsTransforming] = useState(false);
function handleTransformStart() {
setIsTransforming(true);
onNoteMenuClose?.();
}
function handleTransformEnd(event: Konva.KonvaEventObject<Event>) {
if (noteRef.current) {
const sizeChange = event.target.scaleX();
const rotation = event.target.rotation();
onNoteChange?.({
[note.id]: {
size: note.size * sizeChange,
rotation: rotation,
},
});
onNoteMenuOpen?.(note.id, noteRef.current);
noteRef.current.scaleX(1);
noteRef.current.scaleY(1);
}
setIsTransforming(false);
}
// Animate to new note positions if edited by others
const noteX = note.x * mapWidth;
const noteY = note.y * mapHeight;
@@ -194,62 +225,73 @@ function Note({
const noteName = `note${note.locked ? "-locked" : ""}`;
return (
<animated.Group
{...props}
id={note.id}
onClick={handleClick}
onTap={handleClick}
width={noteWidth}
height={note.textOnly ? undefined : noteHeight}
offsetX={noteWidth / 2}
offsetY={noteHeight / 2}
draggable={draggable}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragMove={handleDragMove}
onMouseDown={handlePointerDown}
onMouseUp={handlePointerUp}
onTouchStart={handlePointerDown}
onTouchEnd={handlePointerUp}
onMouseEnter={handlePointerEnter}
onMouseLeave={handlePointerLeave}
opacity={note.visible ? noteOpacity : 0.5}
name={noteName}
>
{!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]}
<>
<animated.Group
{...props}
id={note.id}
onClick={handleClick}
onTap={handleClick}
width={noteWidth}
height={note.textOnly ? undefined : noteHeight}
rotation={note.rotation}
offsetX={noteWidth / 2}
offsetY={noteHeight / 2}
draggable={draggable}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragMove={handleDragMove}
onMouseDown={handlePointerDown}
onMouseUp={handlePointerUp}
onTouchStart={handlePointerDown}
onTouchEnd={handlePointerUp}
onMouseEnter={handlePointerEnter}
onMouseLeave={handlePointerLeave}
opacity={note.visible ? noteOpacity : 0.5}
name={noteName}
ref={noteRef}
>
{!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]}
/>
)}
<Text
text={note.text}
fill={
note.textOnly
? colors[note.color]
: note.color === "black" || note.color === "darkGray"
? "white"
: "black"
}
align="left"
verticalAlign="middle"
padding={notePadding / fontScale}
fontSize={defaultFontSize}
// Scale font instead of changing font size to avoid kerning issues with Firefox
scaleX={fontScale}
scaleY={fontScale}
width={noteWidth / fontScale}
height={note.textOnly ? undefined : noteHeight / fontScale}
wrap="word"
/>
)}
<Text
text={note.text}
fill={
note.textOnly
? colors[note.color]
: note.color === "black" || note.color === "darkGray"
? "white"
: "black"
}
align="left"
verticalAlign="middle"
padding={notePadding / fontScale}
fontSize={defaultFontSize}
// Scale font instead of changing font size to avoid kerning issues with Firefox
scaleX={fontScale}
scaleY={fontScale}
width={noteWidth / fontScale}
height={note.textOnly ? undefined : noteHeight / fontScale}
wrap="word"
{/* Use an invisible text block to work out text sizing */}
<Text visible={false} ref={textRef} text={note.text} wrap="none" />
</animated.Group>
<Transformer
active={(!note.locked && selected) || isTransforming}
nodeRef={noteRef}
onTransformEnd={handleTransformEnd}
onTransformStart={handleTransformStart}
gridScale={map?.grid.measurement.scale || ""}
/>
{/* Use an invisible text block to work out text sizing */}
<Text visible={false} ref={textRef} text={note.text} wrap="none" />
</animated.Group>
</>
);
}

View File

@@ -148,7 +148,7 @@ function Transformer({
if (movingAnchor === "rotater") {
text.text(`${node.rotation().toFixed(0)}°`);
} else {
const nodeRect = node.getClientRect();
const nodeRect = node.getClientRect({ skipShadow: true });
const nodeScale = Vector2.divide(
{ x: nodeRect.width, y: nodeRect.height },
gridCellAbsoluteSize

View File

@@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import { Box, Flex, Text, IconButton } from "theme-ui";
import { Box, Flex, IconButton } from "theme-ui";
import Konva from "konva";
import Slider from "../Slider";
import TextareaAutosize from "../TextareaAutoSize";
import MapMenu from "../map/MapMenu";
@@ -27,8 +26,6 @@ import {
import { Note } from "../../types/Note";
import { Map } from "../../types/Map";
const defaultNoteMaxSize = 6;
type NoteMenuProps = {
isOpen: boolean;
onRequestClose: RequestCloseEventHandler;
@@ -50,12 +47,10 @@ function NoteMenu({
const wasOpen = usePrevious(isOpen);
const [noteMaxSize, setNoteMaxSize] = useState(defaultNoteMaxSize);
const [menuLeft, setMenuLeft] = useState(0);
const [menuTop, setMenuTop] = useState(0);
useEffect(() => {
if (isOpen && !wasOpen && note) {
setNoteMaxSize(Math.max(note.size, defaultNoteMaxSize));
// Update menu position
if (noteNode) {
const nodeRect = noteNode.getClientRect();
@@ -64,8 +59,8 @@ function NoteMenu({
const mapRect = mapElement.getBoundingClientRect();
// Center X for the menu which is 156px wide
setMenuLeft(mapRect.left + nodeRect.x + nodeRect.width / 2 - 156 / 2);
// Y 12px from the bottom
setMenuTop(mapRect.top + nodeRect.y + nodeRect.height + 12);
// Y 20px from the bottom
setMenuTop(mapRect.top + nodeRect.y + nodeRect.height + 20);
}
}
}
@@ -83,11 +78,6 @@ function NoteMenu({
onNoteChange({ [note.id]: { color: color } });
}
function handleSizeChange(event: React.ChangeEvent<HTMLInputElement>) {
const newSize = parseFloat(event.target.value);
note && onNoteChange({ [note.id]: { size: newSize } });
}
function handleVisibleChange() {
note && onNoteChange({ [note.id]: { visible: !note.visible } });
}
@@ -198,24 +188,6 @@ function NoteMenu({
</Box>
))}
</Box>
<Flex sx={{ alignItems: "center" }}>
<Text
as="label"
variant="body2"
sx={{ width: "40%", fontSize: "16px" }}
p={1}
>
Size:
</Text>
<Slider
value={(note && note.size) || 1}
onChange={handleSizeChange}
step={0.5}
min={0.5}
max={noteMaxSize}
mr={1}
/>
</Flex>
{/* Only show hide and lock token actions to map owners */}
{map && map.owner === userId && (
<Flex sx={{ alignItems: "center", justifyContent: "space-around" }}>

View File

@@ -18,8 +18,6 @@ import { Map } from "../../types/Map";
import { Note as NoteType } from "../../types/Note";
import {
NoteCreateEventHander,
NoteChangeEventHandler,
NoteDragEventHandler,
NoteMenuOpenEventHandler,
} from "../../types/Events";
@@ -29,26 +27,16 @@ type MapNoteProps = {
map: Map | null;
active: boolean;
onNoteCreate: NoteCreateEventHander;
onNoteChange: NoteChangeEventHandler;
notes: NoteType[];
onNoteMenuOpen: NoteMenuOpenEventHandler;
draggable: boolean;
onNoteDragStart: NoteDragEventHandler;
onNoteDragEnd: NoteDragEventHandler;
fadeOnHover: boolean;
children: React.ReactNode;
};
function NoteTool({
map,
active,
onNoteCreate,
onNoteChange,
notes,
onNoteMenuOpen,
draggable,
onNoteDragStart,
onNoteDragEnd,
fadeOnHover,
children,
}: MapNoteProps) {
const interactionEmitter = useInteractionEmitter();
const userId = useUserId();
@@ -101,6 +89,7 @@ function NoteTool({
locked: false,
color: "yellow",
textOnly: false,
rotation: 0,
});
setIsBrushDown(true);
}
@@ -147,21 +136,11 @@ function NoteTool({
return (
<Group id="notes">
{notes.map((note) => (
<Note
note={note}
map={map}
key={note.id}
onNoteMenuOpen={onNoteMenuOpen}
draggable={draggable && !note.locked}
onNoteChange={onNoteChange}
onNoteDragStart={onNoteDragStart}
onNoteDragEnd={onNoteDragEnd}
fadeOnHover={fadeOnHover}
/>
))}
{children}
<Group ref={creatingNoteRef}>
{isBrushDown && noteData && <Note note={noteData} map={map} />}
{isBrushDown && noteData && (
<Note note={noteData} map={map} selected={false} />
)}
</Group>
</Group>
);