mirror of
https://github.com/vector-im/element-call.git
synced 2024-11-15 00:04:59 +08:00
Make tiles draggable (but not yet droppable)
This commit is contained in:
parent
d7db845f3b
commit
eedf8a6d1b
@ -1,6 +1,13 @@
|
|||||||
import { SpringRef, TransitionFn, useTransition } from "@react-spring/web";
|
import { SpringRef, TransitionFn, useTransition } from "@react-spring/web";
|
||||||
import { useDrag } from "@use-gesture/react";
|
import { useDrag, useScroll } from "@use-gesture/react";
|
||||||
import React, { FC, ReactNode, useEffect, useMemo, useState } from "react";
|
import React, {
|
||||||
|
FC,
|
||||||
|
ReactNode,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import useMeasure from "react-use-measure";
|
import useMeasure from "react-use-measure";
|
||||||
import styles from "./NewVideoGrid.module.css";
|
import styles from "./NewVideoGrid.module.css";
|
||||||
import { TileDescriptor } from "./TileDescriptor";
|
import { TileDescriptor } from "./TileDescriptor";
|
||||||
@ -50,12 +57,19 @@ interface TileSpring {
|
|||||||
opacity: number;
|
opacity: number;
|
||||||
scale: number;
|
scale: number;
|
||||||
shadow: number;
|
shadow: number;
|
||||||
|
zIndex: number;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DragState {
|
||||||
|
tileId: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
const dijkstra = (g: Grid): number[] => {
|
const dijkstra = (g: Grid): number[] => {
|
||||||
const end = findLast1By1Index(g) ?? 0;
|
const end = findLast1By1Index(g) ?? 0;
|
||||||
const endRow = row(end, g);
|
const endRow = row(end, g);
|
||||||
@ -461,7 +475,11 @@ export const NewVideoGrid: FC<Props> = ({
|
|||||||
[slotRects, grid, slotGridGeneration]
|
[slotRects, grid, slotGridGeneration]
|
||||||
);
|
);
|
||||||
|
|
||||||
const [tileTransitions] = useTransition(
|
// Drag state is stored in a ref rather than component state, because we use
|
||||||
|
// react-spring's imperative API during gestures to improve responsiveness
|
||||||
|
const dragState = useRef<DragState | null>(null);
|
||||||
|
|
||||||
|
const [tileTransitions, springRef] = useTransition(
|
||||||
tiles,
|
tiles,
|
||||||
() => ({
|
() => ({
|
||||||
key: ({ item }: Tile) => item.id,
|
key: ({ item }: Tile) => item.id,
|
||||||
@ -469,20 +487,26 @@ export const NewVideoGrid: FC<Props> = ({
|
|||||||
opacity: 0,
|
opacity: 0,
|
||||||
scale: 0,
|
scale: 0,
|
||||||
shadow: 1,
|
shadow: 1,
|
||||||
|
zIndex: 1,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
immediate: disableAnimations,
|
||||||
}),
|
}),
|
||||||
enter: { opacity: 1, scale: 1 },
|
enter: { opacity: 1, scale: 1, immediate: disableAnimations },
|
||||||
update: ({ x, y, width, height }: Tile) => ({ x, y, width, height }),
|
update: ({ item, x, y, width, height }: Tile) =>
|
||||||
leave: { opacity: 0, scale: 0 },
|
item.id === dragState.current?.tileId
|
||||||
|
? {}
|
||||||
|
: {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
immediate: disableAnimations,
|
||||||
|
},
|
||||||
|
leave: { opacity: 0, scale: 0, immediate: disableAnimations },
|
||||||
config: { mass: 0.7, tension: 252, friction: 25 },
|
config: { mass: 0.7, tension: 252, friction: 25 },
|
||||||
immediate: (key: string) =>
|
|
||||||
disableAnimations || key === "zIndex" || key === "shadow",
|
|
||||||
// If we just stopped dragging a tile, give it time for the
|
|
||||||
// animation to settle before pushing its z-index back down
|
|
||||||
delay: (key: string) => (key === "zIndex" ? 500 : 0),
|
|
||||||
}),
|
}),
|
||||||
[tiles, disableAnimations]
|
[tiles, disableAnimations]
|
||||||
// react-spring's types are bugged and can't infer the spring type
|
// react-spring's types are bugged and can't infer the spring type
|
||||||
@ -525,20 +549,81 @@ export const NewVideoGrid: FC<Props> = ({
|
|||||||
};
|
};
|
||||||
}, [grid]);
|
}, [grid]);
|
||||||
|
|
||||||
|
const animateDraggedTile = (endOfGesture: boolean) =>
|
||||||
|
springRef.start((_i, controller) => {
|
||||||
|
const { tileId, x, y } = dragState.current!;
|
||||||
|
|
||||||
|
// react-spring appears to not update a controller's item as long as the
|
||||||
|
// key remains stable, so we can use it to look up the tile's ID but not
|
||||||
|
// its position
|
||||||
|
if ((controller.item as Tile).item.id === tileId) {
|
||||||
|
if (endOfGesture) {
|
||||||
|
const tile = tiles.find((t) => t.item.id === tileId)!;
|
||||||
|
|
||||||
|
return {
|
||||||
|
scale: 1,
|
||||||
|
zIndex: 1,
|
||||||
|
shadow: 1,
|
||||||
|
x: tile.x,
|
||||||
|
y: tile.y,
|
||||||
|
immediate: disableAnimations || ((key) => key === "zIndex"),
|
||||||
|
// Allow the tile's position to settle before pushing its
|
||||||
|
// z-index back down
|
||||||
|
delay: (key) => (key === "zIndex" ? 500 : 0),
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
scale: 1.1,
|
||||||
|
zIndex: 2,
|
||||||
|
shadow: 15,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
immediate:
|
||||||
|
disableAnimations ||
|
||||||
|
((key) => key === "zIndex" || key === "x" || key === "y"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const bindTile = useDrag(
|
const bindTile = useDrag(
|
||||||
({ event, tap, args }) => {
|
({ tap, args, delta: [dx, dy], last }) => {
|
||||||
event.preventDefault();
|
|
||||||
const tileId = args[0] as string;
|
const tileId = args[0] as string;
|
||||||
|
|
||||||
if (tap) {
|
if (tap) {
|
||||||
setGrid((g) => cycleTileSize(tileId, g));
|
setGrid((g) => cycleTileSize(tileId, g));
|
||||||
} else {
|
} else {
|
||||||
// TODO
|
const tileSpring = springRef.current
|
||||||
|
.find((c) => (c.item as Tile).item.id === tileId)!
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (dragState.current === null) {
|
||||||
|
dragState.current = { tileId, x: tileSpring.x, y: tileSpring.y };
|
||||||
|
}
|
||||||
|
dragState.current.x += dx;
|
||||||
|
dragState.current.y += dy;
|
||||||
|
|
||||||
|
animateDraggedTile(last);
|
||||||
|
|
||||||
|
if (last) dragState.current = null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ filterTaps: true, pointer: { buttons: [1] } }
|
{ filterTaps: true, pointer: { buttons: [1] } }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scrollOffset = useRef(0);
|
||||||
|
|
||||||
|
const bindGrid = useScroll(({ xy: [, y], delta: [, dy] }) => {
|
||||||
|
scrollOffset.current = y;
|
||||||
|
|
||||||
|
if (dragState.current !== null) {
|
||||||
|
dragState.current.y += dy;
|
||||||
|
animateDraggedTile(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const slots = useMemo(() => {
|
const slots = useMemo(() => {
|
||||||
const slots = new Array<ReactNode>(items.length);
|
const slots = new Array<ReactNode>(items.length);
|
||||||
for (let i = 0; i < items.length; i++)
|
for (let i = 0; i < items.length; i++)
|
||||||
@ -554,7 +639,7 @@ export const NewVideoGrid: FC<Props> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={gridRef} className={styles.grid}>
|
<div {...bindGrid()} ref={gridRef} className={styles.grid}>
|
||||||
<div
|
<div
|
||||||
style={slotGridStyle}
|
style={slotGridStyle}
|
||||||
ref={setSlotGrid}
|
ref={setSlotGrid}
|
||||||
|
Loading…
Reference in New Issue
Block a user