improve forms by adding select and float inputs
performance fix
This commit is contained in:
@@ -9,6 +9,7 @@ import { AnimatedNumber, AnimatedVec2, AnimatedVec3 } from "primitives/Values";
|
||||
import { useKeyframeStore } from "stores/keyframe.store";
|
||||
import { produce } from "immer";
|
||||
import KeyframePopover from "./KeyframePopover";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "components/Popover";
|
||||
|
||||
const KeyframeIndicator: FC<{
|
||||
keyframe: z.input<typeof Keyframe>;
|
||||
@@ -51,66 +52,70 @@ const KeyframeIndicator: FC<{
|
||||
|
||||
return (
|
||||
<>
|
||||
<motion.div
|
||||
drag="x"
|
||||
variants={{
|
||||
enter: {},
|
||||
from: {},
|
||||
exit: {},
|
||||
tap: {},
|
||||
drag: {},
|
||||
}}
|
||||
data-selected={selected}
|
||||
onDragStart={() => setIsDragged(true)}
|
||||
onDragEnd={(e, info) => {
|
||||
e.preventDefault();
|
||||
setIsDragged(false);
|
||||
if (onUpdate) {
|
||||
handleUpdate(info);
|
||||
}
|
||||
}}
|
||||
onMouseDown={(e) => e.preventDefault()}
|
||||
dragConstraints={{ left: 0 }}
|
||||
initial={{
|
||||
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 4,
|
||||
scale: 0,
|
||||
}}
|
||||
whileTap={{
|
||||
scale: 1.6,
|
||||
}}
|
||||
animate={{
|
||||
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 4,
|
||||
scale: 1,
|
||||
}}
|
||||
transition={ease.quint(0.4).out}
|
||||
onClick={() => {
|
||||
if (isDragged) {
|
||||
if (!selected) selectKeyframe(keyframe.id);
|
||||
} else {
|
||||
selected ? deselectKeyframe() : selectKeyframe(keyframe.id);
|
||||
}
|
||||
}}
|
||||
className="h-full absolute z-30 select-none w-3 flex items-center justify-center filter
|
||||
<Popover modal={false} open={selected}>
|
||||
<PopoverTrigger asChild>
|
||||
<motion.div
|
||||
drag="x"
|
||||
variants={{
|
||||
enter: {},
|
||||
from: {},
|
||||
exit: {},
|
||||
tap: {},
|
||||
drag: {},
|
||||
}}
|
||||
data-selected={selected}
|
||||
onDragStart={() => setIsDragged(true)}
|
||||
onDragEnd={(e, info) => {
|
||||
e.preventDefault();
|
||||
setIsDragged(false);
|
||||
if (onUpdate) {
|
||||
handleUpdate(info);
|
||||
}
|
||||
}}
|
||||
dragConstraints={{ left: 0 }}
|
||||
initial={{
|
||||
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 2,
|
||||
scale: 0,
|
||||
}}
|
||||
whileTap={{
|
||||
scale: 1.6,
|
||||
}}
|
||||
animate={{
|
||||
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 2,
|
||||
scale: 1,
|
||||
}}
|
||||
transition={ease.quint(0.4).out}
|
||||
onClick={() => {
|
||||
if (isDragged) {
|
||||
if (!selected) selectKeyframe(keyframe.id);
|
||||
} else {
|
||||
selected ? deselectKeyframe() : selectKeyframe(keyframe.id);
|
||||
}
|
||||
}}
|
||||
className="h-full absolute z-30 select-none w-3 flex items-center justify-center filter
|
||||
data-[selected=true]:drop-shadow-[0px_2px_6px_rgba(230,230,255,1)] transition-colors"
|
||||
>
|
||||
<KeyframePopover
|
||||
onUpdate={handleValueUpdate}
|
||||
keyframe={keyframe}
|
||||
open={selected}
|
||||
/>
|
||||
|
||||
<motion.span
|
||||
data-selected={selected}
|
||||
className="bg-gray-200
|
||||
>
|
||||
<motion.span
|
||||
data-selected={selected}
|
||||
className="bg-gray-200
|
||||
data-[selected=true]:bg-indigo-600
|
||||
h-full transition-colors"
|
||||
style={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
style={{
|
||||
width: 10,
|
||||
height: 10,
|
||||
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
|
||||
}}
|
||||
/>
|
||||
</motion.div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 backdrop-blur-md bg-slate-700/50">
|
||||
<KeyframePopover
|
||||
onClose={() => deselectKeyframe()}
|
||||
onUpdate={handleValueUpdate}
|
||||
keyframe={keyframe}
|
||||
/>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,29 +1,18 @@
|
||||
import { PopoverContent, PopoverPortal } from "@radix-ui/react-popover";
|
||||
import { Popover } from "components/Popover";
|
||||
import { PopoverClose } from "components/Popover";
|
||||
import { KeyframeProperties } from "components/Properties/Values";
|
||||
import { Keyframe } from "primitives/Keyframe";
|
||||
import { FC } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
const KeyframePopover: FC<{
|
||||
open: boolean;
|
||||
keyframe: z.input<typeof Keyframe>;
|
||||
onUpdate: (k: z.input<typeof Keyframe>) => void;
|
||||
}> = ({ open, keyframe, onUpdate }) => {
|
||||
onClose: () => void;
|
||||
}> = ({ keyframe, onUpdate, onClose }) => {
|
||||
return (
|
||||
<div>
|
||||
<Popover open={open}>
|
||||
<PopoverContent>
|
||||
<label>
|
||||
<span className="label">Value</span>
|
||||
<input
|
||||
onChange={(e) =>
|
||||
onUpdate({ ...keyframe, value: Number(e.target.value) })
|
||||
}
|
||||
value={keyframe.value}
|
||||
/>
|
||||
</label>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
<KeyframeProperties entity={keyframe} onUpdate={onUpdate} />
|
||||
<PopoverClose onClick={onClose} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ease } from "@unom/style";
|
||||
import { useDragControls, Reorder, motion } from "framer-motion";
|
||||
import { AnimationData, AnimatedEntity } from "primitives/AnimatedEntities";
|
||||
import { Keyframe } from "primitives/Keyframe";
|
||||
import { FC, useState } from "react";
|
||||
import { FC, memo, useState, useMemo } from "react";
|
||||
import { useEntitiesStore } from "stores/entities.store";
|
||||
import { z } from "zod";
|
||||
import { shallow } from "zustand/shallow";
|
||||
@@ -10,24 +9,23 @@ import KeyframeIndicator from "./KeyframeIndicator";
|
||||
import { TIMELINE_SCALE, calculateOffset } from "./common";
|
||||
import { TriangleDownIcon } from "@radix-ui/react-icons";
|
||||
import TrackPropertiesEditor from "./TrackPropertiesEditor";
|
||||
import { flattenedKeyframesByEntity } from "utils";
|
||||
|
||||
type TrackProps = {
|
||||
animationData: z.input<typeof AnimationData>;
|
||||
name: string;
|
||||
index: number;
|
||||
entity: z.input<typeof AnimatedEntity>;
|
||||
keyframes: Array<z.input<typeof Keyframe>>;
|
||||
};
|
||||
|
||||
const Track: FC<TrackProps> = ({
|
||||
keyframes,
|
||||
animationData,
|
||||
index,
|
||||
name,
|
||||
entity,
|
||||
}) => {
|
||||
const Track: FC<TrackProps> = ({ animationData, index, name, entity }) => {
|
||||
const controls = useDragControls();
|
||||
|
||||
const flattenedKeyframes = useMemo(
|
||||
() => flattenedKeyframesByEntity(entity),
|
||||
[entity]
|
||||
);
|
||||
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
const { updateEntity, selectEntity, selectedEntity, deselectEntity } =
|
||||
@@ -91,7 +89,7 @@ const Track: FC<TrackProps> = ({
|
||||
className="flex h-full flex-row relative bg-gray-900 select-none shrink-0"
|
||||
>
|
||||
{!isExpanded &&
|
||||
keyframes.map((keyframe, index) => (
|
||||
flattenedKeyframes.map((keyframe, index) => (
|
||||
<KeyframeIndicator
|
||||
animationData={animationData}
|
||||
keyframe={keyframe}
|
||||
@@ -205,4 +203,4 @@ const Track: FC<TrackProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default Track;
|
||||
export default memo(Track);
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Reorder } from "framer-motion";
|
||||
import TimePicker from "./Timepicker";
|
||||
import { useEntitiesStore } from "stores/entities.store";
|
||||
import Timestamp from "./Timestamp";
|
||||
import { flattenedKeyframesByEntity } from "utils";
|
||||
import { PauseIcon, PlayIcon } from "@radix-ui/react-icons";
|
||||
import { useRenderStateStore } from "stores/render-state.store";
|
||||
import Track from "./Track";
|
||||
@@ -26,7 +25,7 @@ const Timeline: FC<TimelineProps> = () => {
|
||||
}));
|
||||
|
||||
return (
|
||||
<div className="flex flex-col p-4 border transition-colors focus-within:border-gray-400 border-gray-600 rounded-md">
|
||||
<div className="flex flex-col grow shrink h-fit p-4 border transition-colors focus-within:border-gray-400 border-gray-600 rounded-md">
|
||||
<div className="flex flex-row">
|
||||
<div className="flex flex-row">
|
||||
<button onClick={() => setPlaying(true)} className="w-8 h-8">
|
||||
@@ -38,7 +37,7 @@ const Timeline: FC<TimelineProps> = () => {
|
||||
</div>
|
||||
<Timestamp />
|
||||
</div>
|
||||
<div className="gap-1 w-full flex flex-col overflow-x-auto">
|
||||
<div className="gap-1 w-full flex flex-col overflow-x-auto overflow-y-visible">
|
||||
<div className="z-20 flex flex-row gap-2">
|
||||
<div className="flex-shrink-0 min-w-[200px]" />
|
||||
<TimePicker />
|
||||
@@ -54,7 +53,6 @@ const Timeline: FC<TimelineProps> = () => {
|
||||
key={entity.id}
|
||||
name={entity.type}
|
||||
index={index}
|
||||
keyframes={flattenedKeyframesByEntity(entity)}
|
||||
animationData={entity.animation_data}
|
||||
/>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user