add keyframe popover

add shadcn ui stuff
This commit is contained in:
Enrico Bühler 2023-05-31 16:59:43 +02:00
parent e3098c4400
commit ebb2408a68
13 changed files with 671 additions and 1815 deletions

1343
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -14,6 +14,7 @@
"@radix-ui/react-form": "^0.0.2", "@radix-ui/react-form": "^0.0.2",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-menubar": "^1.0.2", "@radix-ui/react-menubar": "^1.0.2",
"@radix-ui/react-popover": "^1.0.6",
"@radix-ui/react-slider": "^1.1.1", "@radix-ui/react-slider": "^1.1.1",
"@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-toolbar": "^1.0.3", "@radix-ui/react-toolbar": "^1.0.3",
@ -22,11 +23,16 @@
"@types/lodash.set": "^4.3.7", "@types/lodash.set": "^4.3.7",
"@unom/style": "^0.2.14", "@unom/style": "^0.2.14",
"canvaskit-wasm": "^0.38.1", "canvaskit-wasm": "^0.38.1",
"class-variance-authority": "^0.6.0",
"clsx": "^1.2.1",
"framer-motion": "^10.12.12", "framer-motion": "^10.12.12",
"immer": "^10.0.2", "immer": "^10.0.2",
"lodash.set": "^4.3.2", "lodash.set": "^4.3.2",
"lucide-react": "^0.229.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"tailwind-merge": "^1.12.0",
"tailwindcss-animate": "^1.0.5",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"zod": "^3.21.4", "zod": "^3.21.4",
"zustand": "^4.3.8" "zustand": "^4.3.8"

View File

@ -1,5 +1,4 @@
use font_kit::source::SystemSource; use font_kit::source::SystemSource;
use rayon::prelude::*;
#[tauri::command] #[tauri::command]
pub fn get_system_fonts() -> Option<Vec<String>> { pub fn get_system_fonts() -> Option<Vec<String>> {
@ -10,7 +9,7 @@ pub fn get_system_fonts() -> Option<Vec<String>> {
match found_fonts { match found_fonts {
Ok(found_fonts) => { Ok(found_fonts) => {
let font_names: Vec<String> = found_fonts let font_names: Vec<String> = found_fonts
.par_iter() .iter()
.map(|f| f.load()) .map(|f| f.load())
.filter(|f| f.is_ok()) .filter(|f| f.is_ok())
.map(|f| f.unwrap()) .map(|f| f.unwrap())
@ -54,6 +53,6 @@ pub fn get_system_font(font_name: String) -> Option<Vec<u8>> {
None None
} }
} }
Err(_) => None, Err(_) => panic!("Err"),
} }
} }

View File

@ -0,0 +1,28 @@
import * as React from "react";
import * as PopoverPrimitive from "@radix-ui/react-popover";
import { cn } from "utils";
const Popover = PopoverPrimitive.Root;
const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef<typeof PopoverPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
ref={ref}
align={align}
sideOffset={sideOffset}
className={cn(
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none animate-in data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
export { Popover, PopoverTrigger, PopoverContent };

View File

@ -2,12 +2,13 @@ import { ease } from "@unom/style";
import { PanInfo, motion } from "framer-motion"; import { PanInfo, motion } from "framer-motion";
import { AnimationData } from "primitives/AnimatedEntities"; import { AnimationData } from "primitives/AnimatedEntities";
import { Keyframe } from "primitives/Keyframe"; import { Keyframe } from "primitives/Keyframe";
import { FC, useCallback, useState } from "react"; import { FC, useCallback, useMemo, useState } from "react";
import { z } from "zod"; import { z } from "zod";
import { TIMELINE_SCALE, calculateOffset } from "./common"; import { TIMELINE_SCALE, calculateOffset } from "./common";
import { AnimatedNumber, AnimatedVec2, AnimatedVec3 } from "primitives/Values"; import { AnimatedNumber, AnimatedVec2, AnimatedVec3 } from "primitives/Values";
import { useKeyframeStore } from "stores/keyframe.store"; import { useKeyframeStore } from "stores/keyframe.store";
import { produce } from "immer"; import { produce } from "immer";
import KeyframePopover from "./KeyframePopover";
const KeyframeIndicator: FC<{ const KeyframeIndicator: FC<{
keyframe: z.input<typeof Keyframe>; keyframe: z.input<typeof Keyframe>;
@ -17,8 +18,6 @@ const KeyframeIndicator: FC<{
const { selectedKeyframe, selectKeyframe, deselectKeyframe } = const { selectedKeyframe, selectKeyframe, deselectKeyframe } =
useKeyframeStore(); useKeyframeStore();
const selected = selectedKeyframe === keyframe.id;
const handleUpdate = useCallback( const handleUpdate = useCallback(
(info: PanInfo) => { (info: PanInfo) => {
if (onUpdate) { if (onUpdate) {
@ -34,59 +33,85 @@ const KeyframeIndicator: FC<{
[onUpdate, animationData, keyframe] [onUpdate, animationData, keyframe]
); );
const handleValueUpdate = useCallback(
(keyframe: z.input<typeof Keyframe>) => {
if (onUpdate) {
onUpdate(keyframe);
}
},
[onUpdate]
);
const selected = useMemo(
() => selectedKeyframe === keyframe.id,
[keyframe.id, selectedKeyframe]
);
const [isDragged, setIsDragged] = useState(false); const [isDragged, setIsDragged] = useState(false);
return ( return (
<motion.div <>
drag="x" <motion.div
variants={{ drag="x"
enter: {}, variants={{
from: {}, enter: {},
exit: {}, from: {},
tap: {}, exit: {},
drag: {}, 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.1 }}
animate={{
x: (animationData.offset + keyframe.offset) * TIMELINE_SCALE + 4,
scale: 1,
}}
transition={ease.quint(0.4).out}
onClick={() => {
if (!isDragged) {
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(255,255,255,1)] transition-colors"
>
<span
data-selected={selected} data-selected={selected}
className="bg-gray-200 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
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
data-[selected=true]:bg-indigo-600 data-[selected=true]:bg-indigo-600
h-full transition-colors" h-full transition-colors"
style={{ style={{
width: 10, width: 10,
height: 10, height: 10,
clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)", clipPath: "polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%)",
}} }}
/> />
</motion.div> </motion.div>
</>
); );
}; };

View File

@ -0,0 +1,31 @@
import { PopoverContent, PopoverPortal } from "@radix-ui/react-popover";
import { Popover } from "components/Popover";
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 }) => {
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>
</div>
);
};
export default KeyframePopover;

View File

@ -5,7 +5,7 @@ import {
} from "primitives/AnimatedEntities"; } from "primitives/AnimatedEntities";
import { AnimatedProperty } from "primitives/AnimatedProperty"; import { AnimatedProperty } from "primitives/AnimatedProperty";
import { AnimatedVec2, ValueType } from "primitives/Values"; import { AnimatedVec2, ValueType } from "primitives/Values";
import { FC, useCallback, useMemo, useState } from "react"; import { FC, memo, useCallback, useMemo, useState } from "react";
import { z } from "zod"; import { z } from "zod";
import { import {
AnimatedNumberKeyframeIndicator, AnimatedNumberKeyframeIndicator,
@ -84,20 +84,32 @@ const TrackAnimatedProperty: FC<{
<h4>{animatedProperty.label}</h4> <h4>{animatedProperty.label}</h4>
<ToggleGroup> <ToggleGroup>
<ToggleGroupItem <ToggleGroupItem
onClick={() => setSelectedDimension("x")} onClick={() =>
selectedDimension === "x"
? setSelectedDimension(undefined)
: setSelectedDimension("x")
}
selected={selectedDimension === "x"} selected={selectedDimension === "x"}
> >
X X
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem <ToggleGroupItem
onClick={() => setSelectedDimension("y")} onClick={() =>
selectedDimension === "y"
? setSelectedDimension(undefined)
: setSelectedDimension("y")
}
selected={selectedDimension === "y"} selected={selectedDimension === "y"}
> >
Y Y
</ToggleGroupItem> </ToggleGroupItem>
{animatedProperty.animatedValue.type === ValueType.Enum.Vec3 && ( {animatedProperty.animatedValue.type === ValueType.Enum.Vec3 && (
<ToggleGroupItem <ToggleGroupItem
onClick={() => setSelectedDimension("z")} onClick={() =>
selectedDimension === "z"
? setSelectedDimension(undefined)
: setSelectedDimension("z")
}
selected={selectedDimension === "z"} selected={selectedDimension === "z"}
> >
Z Z
@ -141,6 +153,8 @@ const TrackPropertiesEditor: FC<{
const parsedEntity = AnimatedEntity.parse(nextValue); const parsedEntity = AnimatedEntity.parse(nextValue);
console.log("reacreated callback");
entitiesStore.updateEntityById(parsedEntity.id, parsedEntity); entitiesStore.updateEntityById(parsedEntity.id, parsedEntity);
}, },
[entity] [entity]
@ -167,4 +181,4 @@ const TrackPropertiesEditor: FC<{
); );
}; };
export default TrackPropertiesEditor; export default memo(TrackPropertiesEditor);

View File

@ -14,7 +14,7 @@ const ToggleGroupItem: FC<{
onClick={onClick} onClick={onClick}
className="hover:bg-indigo-600 text-white data-[selected=true]:bg-indigo-700 className="hover:bg-indigo-600 text-white data-[selected=true]:bg-indigo-700
data-[selected=true]:text-indigo-200 flex h-6 w-6 data-[selected=true]:text-indigo-200 flex h-6 w-6
items-center justify-center bg-slate-900 text-sm leading-4 items-center justify-center bg-slate-800 text-sm leading-4
first:rounded-l last:rounded-r focus:z-10 focus:shadow-[0_0_0_2px] focus:shadow-black first:rounded-l last:rounded-r focus:z-10 focus:shadow-[0_0_0_2px] focus:shadow-black
focus:outline-none transition-colors" focus:outline-none transition-colors"
value="left" value="left"

View File

@ -156,7 +156,7 @@ function buildText(
type: "Fill", type: "Fill",
color, color,
}, },
font_name: "Arial", font_name: "Gilroy-Regular",
size, size,
align: "Center", align: "Center",
}, },
@ -226,7 +226,7 @@ function buildStaggeredText(
stagger: 0.1, stagger: 0.1,
letter: { letter: {
paint: { paint: {
font_name: "Arial", font_name: "Gilroy-Regular",
style: { style: {
type: "Fill", type: "Fill",
color, color,

View File

@ -44,6 +44,82 @@
select { select {
@apply appearance-none; @apply appearance-none;
} }
:root {
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;
--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
--ring: 215 20.2% 65.1%;
--radius: 0.5rem;
}
.dark {
--background: 224 71% 4%;
--foreground: 213 31% 91%;
--muted: 223 47% 11%;
--muted-foreground: 215.4 16.3% 56.9%;
--popover: 224 71% 4%;
--popover-foreground: 215 20.2% 65.1%;
--card: 224 71% 4%;
--card-foreground: 213 31% 91%;
--border: 216 34% 17%;
--input: 216 34% 17%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 1.2%;
--secondary: 222.2 47.4% 11.2%;
--secondary-foreground: 210 40% 98%;
--accent: 216 34% 17%;
--accent-foreground: 210 40% 98%;
--destructive: 0 63% 31%;
--destructive-foreground: 210 40% 98%;
--ring: 216 34% 17%;
--radius: 0.5rem;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
font-feature-settings: "rlig" 1, "calt" 1;
}
} }
.label { .label {

View File

@ -2,6 +2,8 @@ import { AnimatedEntity } from "primitives/AnimatedEntities";
import { Keyframe } from "primitives/Keyframe"; import { Keyframe } from "primitives/Keyframe";
import { AnimatedNumber, AnimatedVec2, AnimatedVec3 } from "primitives/Values"; import { AnimatedNumber, AnimatedVec2, AnimatedVec3 } from "primitives/Values";
import { z } from "zod"; import { z } from "zod";
import { ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function flattenAnimatedNumberKeyframes( export function flattenAnimatedNumberKeyframes(
aNumber: z.input<typeof AnimatedNumber> aNumber: z.input<typeof AnimatedNumber>
@ -112,3 +114,7 @@ export function set(object: any, path: string, value: any) {
[base]: value, [base]: value,
}; };
} }
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@ -1,8 +1,71 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
theme: { theme: {
extend: {}, container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: 0 },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: 0 },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
}, },
plugins: [], plugins: [require("tailwindcss-animate")],
}; };

File diff suppressed because it is too large Load Diff