Add transformer to note
This commit is contained in:
parent
59d46e1d27
commit
d1e62e850a
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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" }}>
|
||||
|
@ -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>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import Konva from "konva";
|
||||
import { KonvaEventObject } from "konva/lib/Node";
|
||||
import { useState } from "react";
|
||||
import Note from "../components/konva/Note";
|
||||
import NoteDragOverlay from "../components/note/NoteDragOverlay";
|
||||
import NoteMenu from "../components/note/NoteMenu";
|
||||
import NoteTool from "../components/tools/NoteTool";
|
||||
@ -12,7 +13,11 @@ import {
|
||||
} from "../types/Events";
|
||||
import { Map, MapToolId } from "../types/Map";
|
||||
import { MapState } from "../types/MapState";
|
||||
import { Note, NoteDraggingOptions, NoteMenuOptions } from "../types/Note";
|
||||
import {
|
||||
Note as NoteType,
|
||||
NoteDraggingOptions,
|
||||
NoteMenuOptions,
|
||||
} from "../types/Note";
|
||||
|
||||
function useMapNotes(
|
||||
map: Map | null,
|
||||
@ -37,6 +42,10 @@ function useMapNotes(
|
||||
setIsNoteMenuOpen(true);
|
||||
}
|
||||
|
||||
function handleNoteMenuClose() {
|
||||
setIsNoteMenuOpen(false);
|
||||
}
|
||||
|
||||
function handleNoteDragStart(_: KonvaEventObject<DragEvent>, noteId: string) {
|
||||
setNoteDraggingOptions({ dragging: true, noteId });
|
||||
}
|
||||
@ -56,29 +65,43 @@ function useMapNotes(
|
||||
map={map}
|
||||
active={selectedToolId === "note"}
|
||||
onNoteCreate={onNoteCreate}
|
||||
onNoteChange={onNoteChange}
|
||||
notes={
|
||||
mapState
|
||||
? Object.values(mapState.notes).sort((a, b) =>
|
||||
sortNotes(a, b, noteDraggingOptions)
|
||||
)
|
||||
: []
|
||||
}
|
||||
onNoteMenuOpen={handleNoteMenuOpen}
|
||||
draggable={
|
||||
allowNoteEditing &&
|
||||
(selectedToolId === "note" || selectedToolId === "move")
|
||||
}
|
||||
onNoteDragStart={handleNoteDragStart}
|
||||
onNoteDragEnd={handleNoteDragEnd}
|
||||
fadeOnHover={selectedToolId === "drawing"}
|
||||
/>
|
||||
>
|
||||
{(mapState
|
||||
? Object.values(mapState.notes).sort((a, b) =>
|
||||
sortNotes(a, b, noteDraggingOptions)
|
||||
)
|
||||
: []
|
||||
).map((note) => (
|
||||
<Note
|
||||
note={note}
|
||||
map={map}
|
||||
key={note.id}
|
||||
onNoteMenuOpen={handleNoteMenuOpen}
|
||||
onNoteMenuClose={handleNoteMenuClose}
|
||||
draggable={
|
||||
allowNoteEditing &&
|
||||
(selectedToolId === "note" || selectedToolId === "move") &&
|
||||
!note.locked
|
||||
}
|
||||
onNoteChange={onNoteChange}
|
||||
onNoteDragStart={handleNoteDragStart}
|
||||
onNoteDragEnd={handleNoteDragEnd}
|
||||
fadeOnHover={selectedToolId === "drawing"}
|
||||
selected={
|
||||
!!noteMenuOptions &&
|
||||
isNoteMenuOpen &&
|
||||
noteMenuOptions.noteId === note.id
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</NoteTool>
|
||||
);
|
||||
|
||||
const noteMenu = (
|
||||
<NoteMenu
|
||||
isOpen={isNoteMenuOpen}
|
||||
onRequestClose={() => setIsNoteMenuOpen(false)}
|
||||
onRequestClose={handleNoteMenuClose}
|
||||
onNoteChange={onNoteChange}
|
||||
note={noteMenuOptions && mapState?.notes[noteMenuOptions.noteId]}
|
||||
noteNode={noteMenuOptions?.noteNode}
|
||||
@ -100,8 +123,8 @@ function useMapNotes(
|
||||
export default useMapNotes;
|
||||
|
||||
function sortNotes(
|
||||
a: Note,
|
||||
b: Note,
|
||||
a: NoteType,
|
||||
b: NoteType,
|
||||
noteDraggingOptions?: NoteDraggingOptions
|
||||
) {
|
||||
if (
|
||||
|
@ -46,6 +46,7 @@ export type NoteMenuOpenEventHandler = (
|
||||
noteId: string,
|
||||
noteNode: Konva.Node
|
||||
) => void;
|
||||
export type NoteMenuCloseEventHandler = () => void;
|
||||
export type NoteDragEventHandler = (
|
||||
event: Konva.KonvaEventObject<DragEvent>,
|
||||
noteId: string
|
||||
|
@ -13,6 +13,7 @@ export type Note = {
|
||||
visible: boolean;
|
||||
x: number;
|
||||
y: number;
|
||||
rotation: number;
|
||||
};
|
||||
|
||||
export type NoteMenuOptions = {
|
||||
|
@ -834,9 +834,22 @@ export const versions: Record<number, VersionCallback> = {
|
||||
_uncommittedChanges: null,
|
||||
});
|
||||
},
|
||||
// v1.10.0 - Add rotation to notes
|
||||
37(v, onUpgrade) {
|
||||
v.stores({}).upgrade((tx) => {
|
||||
onUpgrade?.(37);
|
||||
tx.table("states")
|
||||
.toCollection()
|
||||
.modify((state) => {
|
||||
for (let id in state.notes) {
|
||||
state.notes[id].rotation = 0;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const latestVersion = 36;
|
||||
export const latestVersion = 37;
|
||||
|
||||
/**
|
||||
* Load versions onto a database up to a specific version number
|
||||
|
@ -40,6 +40,9 @@ export const NoteSchema: JSONSchemaType<Note> = {
|
||||
y: {
|
||||
type: "number",
|
||||
},
|
||||
rotation: {
|
||||
type: "number",
|
||||
},
|
||||
},
|
||||
required: [
|
||||
"color",
|
||||
@ -53,6 +56,7 @@ export const NoteSchema: JSONSchemaType<Note> = {
|
||||
"visible",
|
||||
"x",
|
||||
"y",
|
||||
"rotation",
|
||||
],
|
||||
type: "object",
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user