Add selection drag overlay

This commit is contained in:
Mitchell McCaffrey 2021-07-22 16:40:43 +10:00
parent 19139dec82
commit ed64294855
8 changed files with 110 additions and 19 deletions

View File

@ -30,6 +30,8 @@ type SelectionProps = {
onSelectionChange: (selection: SelectionType | null) => void; onSelectionChange: (selection: SelectionType | null) => void;
onSelectionItemsChange: SelectionItemsChangeEventHandler; onSelectionItemsChange: SelectionItemsChangeEventHandler;
onPreventSelectionChange: (preventSelection: boolean) => void; onPreventSelectionChange: (preventSelection: boolean) => void;
onSelectionDragStart: () => void;
onSelectionDragEnd: () => void;
} & Konva.ShapeConfig; } & Konva.ShapeConfig;
function Selection({ function Selection({
@ -37,6 +39,8 @@ function Selection({
onSelectionChange, onSelectionChange,
onSelectionItemsChange, onSelectionItemsChange,
onPreventSelectionChange, onPreventSelectionChange,
onSelectionDragStart,
onSelectionDragEnd,
...props ...props
}: SelectionProps) { }: SelectionProps) {
const userId = useUserId(); const userId = useUserId();
@ -75,6 +79,7 @@ function Selection({
} }
} }
} }
onSelectionDragStart();
} }
function handleDragMove(event: Konva.KonvaEventObject<DragEvent>) { function handleDragMove(event: Konva.KonvaEventObject<DragEvent>) {
@ -119,6 +124,7 @@ function Selection({
}); });
intersectingNodesRef.current = []; intersectingNodesRef.current = [];
onPreventSelectionChange(false); onPreventSelectionChange(false);
onSelectionDragEnd();
} }
function handlePointerDown() { function handlePointerDown() {

View File

@ -38,6 +38,7 @@ import {
TokenStateChangeEventHandler, TokenStateChangeEventHandler,
NoteCreateEventHander, NoteCreateEventHander,
SelectionItemsChangeEventHandler, SelectionItemsChangeEventHandler,
SelectionItemsRemoveEventHandler,
} from "../../types/Events"; } from "../../types/Events";
import useMapTokens from "../../hooks/useMapTokens"; import useMapTokens from "../../hooks/useMapTokens";
@ -52,6 +53,7 @@ type MapProps = {
onMapTokenStateChange: TokenStateChangeEventHandler; onMapTokenStateChange: TokenStateChangeEventHandler;
onMapTokenStateRemove: TokenStateRemoveHandler; onMapTokenStateRemove: TokenStateRemoveHandler;
onSelectionItemsChange: SelectionItemsChangeEventHandler; onSelectionItemsChange: SelectionItemsChangeEventHandler;
onSelectionItemsRemove: SelectionItemsRemoveEventHandler;
onMapChange: MapChangeEventHandler; onMapChange: MapChangeEventHandler;
onMapReset: MapResetEventHandler; onMapReset: MapResetEventHandler;
onMapDraw: (action: Action<DrawingState>) => void; onMapDraw: (action: Action<DrawingState>) => void;
@ -72,6 +74,7 @@ function Map({
onMapTokenStateChange, onMapTokenStateChange,
onMapTokenStateRemove, onMapTokenStateRemove,
onSelectionItemsChange, onSelectionItemsChange,
onSelectionItemsRemove,
onMapChange, onMapChange,
onMapReset, onMapReset,
onMapDraw, onMapDraw,
@ -149,13 +152,15 @@ function Map({
!!(map?.owner === userId || mapState?.editFlags.includes("notes")) !!(map?.owner === userId || mapState?.editFlags.includes("notes"))
); );
const { selectionTool, selectionMenu } = useMapSelection( const { selectionTool, selectionMenu, selectionDragOverlay } =
map, useMapSelection(
mapState, map,
onSelectionItemsChange, mapState,
selectedToolId, onSelectionItemsChange,
settings.select onSelectionItemsRemove,
); selectedToolId,
settings.select
);
return ( return (
<Box sx={{ flexGrow: 1 }}> <Box sx={{ flexGrow: 1 }}>
@ -184,6 +189,7 @@ function Map({
{selectionMenu} {selectionMenu}
{tokenDragOverlay} {tokenDragOverlay}
{noteDragOverlay} {noteDragOverlay}
{selectionDragOverlay}
</> </>
} }
selectedToolId={selectedToolId} selectedToolId={selectedToolId}

View File

@ -1,4 +1,3 @@
import Konva from "konva";
import { NoteRemoveEventHander } from "../../types/Events"; import { NoteRemoveEventHander } from "../../types/Events";
import DragOverlay from "../map/DragOverlay"; import DragOverlay from "../map/DragOverlay";
@ -6,27 +5,19 @@ import DragOverlay from "../map/DragOverlay";
type NoteDragOverlayProps = { type NoteDragOverlayProps = {
onNoteRemove: NoteRemoveEventHander; onNoteRemove: NoteRemoveEventHander;
noteId: string; noteId: string;
noteGroup: Konva.Node;
dragging: boolean; dragging: boolean;
}; };
function NoteDragOverlay({ function NoteDragOverlay({
onNoteRemove, onNoteRemove,
noteId, noteId,
noteGroup,
dragging, dragging,
}: NoteDragOverlayProps) { }: NoteDragOverlayProps) {
function handleNoteRemove() { function handleNoteRemove() {
onNoteRemove([noteId]); onNoteRemove([noteId]);
} }
return ( return <DragOverlay dragging={dragging} onRemove={handleNoteRemove} />;
<DragOverlay
dragging={dragging}
onRemove={handleNoteRemove}
node={noteGroup}
/>
);
} }
export default NoteDragOverlay; export default NoteDragOverlay;

View File

@ -0,0 +1,33 @@
import { SelectionItemsRemoveEventHandler } from "../../types/Events";
import { Selection } from "../../types/Select";
import DragOverlay from "../map/DragOverlay";
type NoteDragOverlayProps = {
onSelectionItemsRemove: SelectionItemsRemoveEventHandler;
selection: Selection;
dragging: boolean;
};
function NoteDragOverlay({
onSelectionItemsRemove,
selection,
dragging,
}: NoteDragOverlayProps) {
function handleNoteRemove() {
const tokenIds: string[] = [];
const noteIds: string[] = [];
for (let item of selection.items) {
if (item.type === "token") {
tokenIds.push(item.id);
} else {
noteIds.push(item.id);
}
}
onSelectionItemsRemove(tokenIds, noteIds);
}
return <DragOverlay dragging={dragging} onRemove={handleNoteRemove} />;
}
export default NoteDragOverlay;

View File

@ -40,6 +40,8 @@ type MapSelectProps = {
selection: SelectionType | null; selection: SelectionType | null;
onSelectionChange: React.Dispatch<React.SetStateAction<SelectionType | null>>; onSelectionChange: React.Dispatch<React.SetStateAction<SelectionType | null>>;
onSelectionMenuOpen: (open: boolean) => void; onSelectionMenuOpen: (open: boolean) => void;
onSelectionDragStart: () => void;
onSelectionDragEnd: () => void;
}; };
function SelectTool({ function SelectTool({
@ -49,6 +51,8 @@ function SelectTool({
selection, selection,
onSelectionChange, onSelectionChange,
onSelectionMenuOpen, onSelectionMenuOpen,
onSelectionDragStart,
onSelectionDragEnd,
}: MapSelectProps) { }: MapSelectProps) {
const stageScale = useDebouncedStageScale(); const stageScale = useDebouncedStageScale();
const mapWidth = useMapWidth(); const mapWidth = useMapWidth();
@ -255,6 +259,8 @@ function SelectTool({
onPreventSelectionChange={(prevent: boolean) => onPreventSelectionChange={(prevent: boolean) =>
(preventSelectionRef.current = prevent) (preventSelectionRef.current = prevent)
} }
onSelectionDragStart={onSelectionDragStart}
onSelectionDragEnd={onSelectionDragEnd}
/> />
)} )}
</Group> </Group>

View File

@ -1,7 +1,11 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import SelectionDragOverlay from "../components/selection/SelectionDragOverlay";
import SelectionMenu from "../components/selection/SelectionMenu"; import SelectionMenu from "../components/selection/SelectionMenu";
import SelectTool from "../components/tools/SelectTool"; import SelectTool from "../components/tools/SelectTool";
import { SelectionItemsChangeEventHandler } from "../types/Events"; import {
SelectionItemsChangeEventHandler,
SelectionItemsRemoveEventHandler,
} from "../types/Events";
import { Map, MapToolId } from "../types/Map"; import { Map, MapToolId } from "../types/Map";
import { MapState } from "../types/MapState"; import { MapState } from "../types/MapState";
import { Selection } from "../types/Select"; import { Selection } from "../types/Select";
@ -11,11 +15,13 @@ function useMapSelection(
map: Map | null, map: Map | null,
mapState: MapState | null, mapState: MapState | null,
onSelectionItemsChange: SelectionItemsChangeEventHandler, onSelectionItemsChange: SelectionItemsChangeEventHandler,
onSelectionItemsRemove: SelectionItemsRemoveEventHandler,
selectedToolId: MapToolId, selectedToolId: MapToolId,
settings: SelectToolSettings settings: SelectToolSettings
) { ) {
const [isSelectionMenuOpen, setIsSelectionMenuOpen] = const [isSelectionMenuOpen, setIsSelectionMenuOpen] =
useState<boolean>(false); useState<boolean>(false);
const [isSelectionDragging, setIsSelectionDragging] = useState(false);
const [selection, setSelection] = useState<Selection | null>(null); const [selection, setSelection] = useState<Selection | null>(null);
function handleSelectionMenuOpen(open: boolean) { function handleSelectionMenuOpen(open: boolean) {
@ -31,6 +37,22 @@ function useMapSelection(
} }
}, [active]); }, [active]);
function handleSelectionDragStart() {
setIsSelectionDragging(true);
}
function handleSelectionDragEnd() {
setIsSelectionDragging(false);
}
function handleSelectionItemsRemove(
tokenStateIds: string[],
noteIds: string[]
) {
setSelection(null);
onSelectionItemsRemove(tokenStateIds, noteIds);
}
const selectionTool = ( const selectionTool = (
<SelectTool <SelectTool
active={active} active={active}
@ -39,6 +61,8 @@ function useMapSelection(
selection={selection} selection={selection}
onSelectionChange={setSelection} onSelectionChange={setSelection}
onSelectionMenuOpen={handleSelectionMenuOpen} onSelectionMenuOpen={handleSelectionMenuOpen}
onSelectionDragStart={handleSelectionDragStart}
onSelectionDragEnd={handleSelectionDragEnd}
/> />
); );
@ -53,7 +77,15 @@ function useMapSelection(
/> />
); );
return { selectionTool, selectionMenu }; const selectionDragOverlay = selection ? (
<SelectionDragOverlay
dragging={isSelectionDragging}
selection={selection}
onSelectionItemsRemove={handleSelectionItemsRemove}
/>
) : null;
return { selectionTool, selectionMenu, selectionDragOverlay };
} }
export default useMapSelection; export default useMapSelection;

View File

@ -330,6 +330,18 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
]); ]);
} }
function handleSelectionItemsRemove(
tokenStateIds: string[],
noteIds: string[]
) {
const tokenAction = new RemoveStatesAction<TokenState>(tokenStateIds);
const noteAction = new RemoveStatesAction<Note>(noteIds);
addActions([
{ type: "tokens", action: tokenAction },
{ type: "notes", action: noteAction },
]);
}
useEffect(() => { useEffect(() => {
async function handlePeerData({ id, data, reply }: PeerDataEvent) { async function handlePeerData({ id, data, reply }: PeerDataEvent) {
if (id === "assetRequest") { if (id === "assetRequest") {
@ -394,6 +406,7 @@ function NetworkedMapAndTokens({ session }: { session: Session }) {
onMapTokenStateChange={handleMapTokenStateChange} onMapTokenStateChange={handleMapTokenStateChange}
onMapTokenStateRemove={handleMapTokenStateRemove} onMapTokenStateRemove={handleMapTokenStateRemove}
onSelectionItemsChange={handleSelectionItemsChange} onSelectionItemsChange={handleSelectionItemsChange}
onSelectionItemsRemove={handleSelectionItemsRemove}
onMapChange={handleMapChange} onMapChange={handleMapChange}
onMapReset={handleMapReset} onMapReset={handleMapReset}
onMapDraw={handleMapDraw} onMapDraw={handleMapDraw}

View File

@ -63,3 +63,7 @@ export type SelectionItemsChangeEventHandler = (
tokenChanges: Record<string, Partial<TokenState>>, tokenChanges: Record<string, Partial<TokenState>>,
noteChanges: Record<string, Partial<Note>> noteChanges: Record<string, Partial<Note>>
) => void; ) => void;
export type SelectionItemsRemoveEventHandler = (
tokenStateIds: string[],
noteIds: string[]
) => void;