improve forms by adding select and float inputs
performance fix
This commit is contained in:
parent
ebb2408a68
commit
fcd3afa3f2
@ -5,12 +5,11 @@
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Tauri + React + TS</title>
|
||||
<title>tempblade Creator</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
|
||||
|
@ -15,6 +15,7 @@
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-menubar": "^1.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.6",
|
||||
"@radix-ui/react-select": "^1.2.2",
|
||||
"@radix-ui/react-slider": "^1.1.1",
|
||||
"@radix-ui/react-toggle-group": "^1.0.4",
|
||||
"@radix-ui/react-toolbar": "^1.0.3",
|
||||
|
@ -4,39 +4,35 @@ import Canvas from "./components/Canvas";
|
||||
import Properties, { PropertiesContainer } from "components/Properties";
|
||||
import MenuBar from "components/MenuBar";
|
||||
import ToolBar from "components/ToolBar";
|
||||
import { useEffect } from "react";
|
||||
import { invoke } from "@tauri-apps/api/tauri";
|
||||
import { useFontsStore } from "stores/fonts.store";
|
||||
import useKeyControls from "hooks/useKeyControls";
|
||||
import { useFontsStore } from "stores/fonts.store";
|
||||
|
||||
export default function App() {
|
||||
const { setFonts } = useFontsStore();
|
||||
const fontsStoreDidInit = useFontsStore((store) => store.didInit);
|
||||
|
||||
useKeyControls();
|
||||
|
||||
useEffect(() => {
|
||||
invoke("get_system_families").then((data) => {
|
||||
if (data && Array.isArray(data)) {
|
||||
setFonts(data);
|
||||
}
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="bg-gray-950 overflow-y-hidden h-full w-full flex flex-col">
|
||||
<div className="bg-gray-950 h-full w-full flex flex-col overflow-hidden">
|
||||
<MenuBar />
|
||||
<div className="flex flex-row w-full h-full">
|
||||
<div className="flex flex-row flex-[1] overflow-hidden">
|
||||
<ToolBar />
|
||||
<div className="flex flex-col ml-4 mr-4 mt-4 h-full w-full overflow-y-auto">
|
||||
<div className="flex w-full gap-4 flex-col lg:flex-row mb-4 justify-center items-center">
|
||||
<Canvas />
|
||||
<PropertiesContainer>
|
||||
<Properties />
|
||||
</PropertiesContainer>
|
||||
{fontsStoreDidInit && (
|
||||
<div className="flex flex-col pl-4 gap-4 pr-4 overflow-x-hidden overflow-y-auto">
|
||||
<div className="flex w-full gap-4 flex-col lg:flex-row justify-center items-center">
|
||||
<Canvas />
|
||||
<PropertiesContainer>
|
||||
<Properties />
|
||||
</PropertiesContainer>
|
||||
</div>
|
||||
<Timeline />
|
||||
</div>
|
||||
<Timeline />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
/* */
|
||||
}
|
||||
|
@ -1,9 +1,5 @@
|
||||
import { FC, useMemo } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTimelineStore } from "stores/timeline.store";
|
||||
import { useRenderStateStore } from "stores/render-state.store";
|
||||
import { useEntitiesStore } from "stores/entities.store";
|
||||
import { Drawer } from "drawers/draw";
|
||||
import { PlaybackService } from "services/playback.service";
|
||||
|
||||
type CanvasProps = {};
|
||||
|
30
app/src/components/Inputs/FloatInput.tsx
Normal file
30
app/src/components/Inputs/FloatInput.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import { useCallback, useState } from "react";
|
||||
import { z } from "zod";
|
||||
|
||||
type FloatInputProps = {
|
||||
value: number;
|
||||
onChange: (value: number) => void;
|
||||
id: string;
|
||||
};
|
||||
|
||||
const FloatInput: React.FC<FloatInputProps> = ({ value, onChange, id }) => {
|
||||
const [inputValue, setInputValue] = useState<string>(value.toString());
|
||||
|
||||
const handleInputChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const val = e.target.value.replace(",", ".");
|
||||
setInputValue(val);
|
||||
|
||||
const nextValue = z.coerce.number().min(-9999).max(9999).safeParse(val);
|
||||
|
||||
if (nextValue.success) {
|
||||
onChange(nextValue.data);
|
||||
}
|
||||
},
|
||||
[setInputValue, onChange]
|
||||
);
|
||||
|
||||
return <input id={id} onChange={handleInputChange} value={inputValue} />;
|
||||
};
|
||||
|
||||
export default FloatInput;
|
110
app/src/components/Inputs/Select.tsx
Normal file
110
app/src/components/Inputs/Select.tsx
Normal file
@ -0,0 +1,110 @@
|
||||
import * as React from "react";
|
||||
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
|
||||
import { cn } from "utils";
|
||||
|
||||
const Select = SelectPrimitive.Root;
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group;
|
||||
|
||||
const SelectValue = SelectPrimitive.Value;
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-transparent px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
));
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-slate-900 text-popover-foreground shadow-md animate-in fade-in-80",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<SelectPrimitive.Viewport className={cn("p-1")}>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
));
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-indigo-800 focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<Check className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
));
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-slate-800", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
};
|
@ -1,11 +1,24 @@
|
||||
import * as React from "react";
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
||||
import { cn } from "utils";
|
||||
import { Cross2Icon } from "@radix-ui/react-icons";
|
||||
|
||||
const Popover = PopoverPrimitive.Root;
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger;
|
||||
|
||||
const PopoverArrow = PopoverPrimitive.Arrow;
|
||||
|
||||
const PopoverClose: React.FC<{ onClick?: () => void }> = ({ onClick }) => (
|
||||
<PopoverPrimitive.Close
|
||||
onClick={onClick}
|
||||
className="rounded-full h-[25px] w-[25px] inline-flex items-center justify-center text-white absolute top-[5px] right-[5px] hover:bg-indigo-600 focus:shadow-[0_0_0_2px] focus:shadow-indigo-500 outline-none cursor-default"
|
||||
aria-label="Close"
|
||||
>
|
||||
<Cross2Icon />
|
||||
</PopoverPrimitive.Close>
|
||||
);
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
@ -25,4 +38,4 @@ const PopoverContent = React.forwardRef<
|
||||
));
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName;
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent };
|
||||
export { Popover, PopoverTrigger, PopoverClose, PopoverContent, PopoverArrow };
|
||||
|
@ -12,6 +12,13 @@ import { z } from "zod";
|
||||
import { AnimatedVec2Properties, ColorProperties } from "./Values";
|
||||
import { PropertiesProps } from "./common";
|
||||
import { useFontsStore } from "stores/fonts.store";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "components/Inputs/Select";
|
||||
|
||||
type TextPropertiesProps = PropertiesProps<z.input<typeof AnimatedTextEntity>>;
|
||||
type StaggeredTextPropertiesProps = PropertiesProps<
|
||||
@ -29,13 +36,17 @@ export const PaintProperties: FC<PaintPropertiesProps> = ({
|
||||
}) => {
|
||||
return (
|
||||
<div>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">PaintStyle</span>
|
||||
<select
|
||||
value={entity.style.type}
|
||||
onChange={(e) => {
|
||||
if (entity.style.type !== e.target.value) {
|
||||
const paintStyle = { type: e.target.value };
|
||||
<fieldset>
|
||||
<label htmlFor="staggered-text-letter-font">Font</label>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="paint-style-type">PaintStyle</label>
|
||||
|
||||
<Select
|
||||
defaultValue={entity.style.type}
|
||||
onValueChange={(value) => {
|
||||
if (entity.style.type !== value) {
|
||||
const paintStyle = { type: value };
|
||||
|
||||
const parsedPaintStyle = PaintStyle.parse(paintStyle);
|
||||
|
||||
@ -43,13 +54,16 @@ export const PaintProperties: FC<PaintPropertiesProps> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
{Object.keys(PaintStyleType.Values).map((key) => (
|
||||
<option key={key} value={key}>
|
||||
{key}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Choose a paint style" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="overflow-hidden">
|
||||
{Object.keys(PaintStyleType.Values).map((paintStyleType) => (
|
||||
<SelectItem value={paintStyleType}>{paintStyleType}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</fieldset>
|
||||
{entity.style.color && (
|
||||
<ColorProperties
|
||||
label="Color"
|
||||
@ -76,16 +90,18 @@ export const TextProperties: FC<TextPropertiesProps> = ({
|
||||
initial="from"
|
||||
transition={ease.quint(0.9).out}
|
||||
>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Text</span>
|
||||
<fieldset>
|
||||
<label htmlFor="text-content">Text</label>
|
||||
<input
|
||||
id="text-content"
|
||||
value={entity.text}
|
||||
onChange={(e) => onUpdate({ ...entity, text: e.target.value })}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Size</span>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="text-size">Size</label>
|
||||
<input
|
||||
id="text-size"
|
||||
value={entity.paint.size}
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
@ -93,40 +109,31 @@ export const TextProperties: FC<TextPropertiesProps> = ({
|
||||
paint: { ...entity.paint, size: Number(e.target.value) },
|
||||
})
|
||||
}
|
||||
></input>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Font</span>
|
||||
<select
|
||||
onChange={(e) =>
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="text-font">Font</label>
|
||||
|
||||
<Select
|
||||
defaultValue={entity.paint.font_name}
|
||||
onValueChange={(val) => {
|
||||
onUpdate({
|
||||
...entity,
|
||||
cache: { valid: false },
|
||||
paint: { ...entity.paint, font_name: e.target.value },
|
||||
})
|
||||
}
|
||||
value={entity.paint.font_name}
|
||||
paint: { ...entity.paint, font_name: val },
|
||||
});
|
||||
}}
|
||||
>
|
||||
{fonts.map((font) => (
|
||||
<option value={font} key={font}>
|
||||
{font}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span className="label">Size</span>
|
||||
<input
|
||||
value={entity.paint.size}
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
...entity,
|
||||
cache: { valid: false },
|
||||
paint: { ...entity.paint, size: Number(e.target.value) },
|
||||
})
|
||||
}
|
||||
></input>
|
||||
</label>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Choose a font" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{fonts.map((font) => (
|
||||
<SelectItem value={font}>{font}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</fieldset>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
@ -144,9 +151,10 @@ export const StaggeredTextProperties: FC<StaggeredTextPropertiesProps> = ({
|
||||
initial="from"
|
||||
transition={ease.quint(0.9).out}
|
||||
>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Text</span>
|
||||
<fieldset>
|
||||
<label htmlFor="staggered-text-content">Text</label>
|
||||
<input
|
||||
id="staggered-text-content"
|
||||
value={entity.text}
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
@ -156,32 +164,36 @@ export const StaggeredTextProperties: FC<StaggeredTextPropertiesProps> = ({
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Font</span>
|
||||
<select
|
||||
onChange={(e) => {
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="staggered-text-letter-font">Font</label>
|
||||
<Select
|
||||
defaultValue={entity.letter.paint.font_name}
|
||||
onValueChange={(val) => {
|
||||
onUpdate({
|
||||
...entity,
|
||||
cache: { valid: false },
|
||||
letter: {
|
||||
...entity.letter,
|
||||
paint: { ...entity.letter.paint, font_name: e.target.value },
|
||||
paint: { ...entity.letter.paint, font_name: val },
|
||||
},
|
||||
});
|
||||
}}
|
||||
value={entity.letter.paint.font_name}
|
||||
>
|
||||
{fonts.map((font) => (
|
||||
<option value={font} key={font}>
|
||||
{font}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label">Size</span>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Choose a font" />
|
||||
</SelectTrigger>
|
||||
<SelectContent className="overflow-hidden">
|
||||
{fonts.map((font) => (
|
||||
<SelectItem value={font}>{font}</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label htmlFor="staggered-text-letter-size">Size</label>
|
||||
<input
|
||||
id="staggered-text-letter-size"
|
||||
value={entity.letter.paint.size}
|
||||
onChange={(e) =>
|
||||
onUpdate({
|
||||
@ -196,8 +208,8 @@ export const StaggeredTextProperties: FC<StaggeredTextPropertiesProps> = ({
|
||||
},
|
||||
})
|
||||
}
|
||||
></input>
|
||||
</label>
|
||||
/>
|
||||
</fieldset>
|
||||
<PaintProperties
|
||||
entity={entity.letter.paint}
|
||||
onUpdate={(paint) =>
|
||||
|
@ -3,15 +3,96 @@ import { PropertiesProps } from "./common";
|
||||
import { FC } from "react";
|
||||
import { z } from "zod";
|
||||
import { produce } from "immer";
|
||||
import { Interpolation } from "primitives/Interpolation";
|
||||
import { Interpolation, InterpolationType } from "primitives/Interpolation";
|
||||
import { Color } from "primitives/Paint";
|
||||
import { colorToString, parseColor, parseCssColor } from "@tempblade/common";
|
||||
import { parseCssColor } from "@tempblade/common";
|
||||
import { rgbToHex } from "utils";
|
||||
import { SpringInterpolation } from "primitives/Interpolation";
|
||||
import FloatInput from "components/Inputs/FloatInput";
|
||||
import { Keyframe } from "primitives/Keyframe";
|
||||
|
||||
const InterpolationProperties: FC<
|
||||
const SpringInterpolationProperties: FC<
|
||||
PropertiesProps<z.input<typeof SpringInterpolation>>
|
||||
> = ({ entity, onUpdate }) => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export const InterpolationProperties: FC<
|
||||
PropertiesProps<z.input<typeof Interpolation>>
|
||||
> = ({ entity, onUpdate }) => {
|
||||
return <div>Interpolation: {entity.type}</div>;
|
||||
return (
|
||||
<fieldset>
|
||||
<label className="label" htmlFor="interpolation-type">
|
||||
Interpolation Type
|
||||
</label>
|
||||
<select
|
||||
id="interpolation-type"
|
||||
onChange={(e) => {
|
||||
onUpdate({
|
||||
...entity,
|
||||
type: e.target.value as any,
|
||||
});
|
||||
}}
|
||||
value={entity.type}
|
||||
>
|
||||
{Object.keys(InterpolationType.Values).map((key) => (
|
||||
<option key={key} value={key}>
|
||||
{key}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</fieldset>
|
||||
);
|
||||
};
|
||||
|
||||
export const KeyframeProperties: FC<
|
||||
PropertiesProps<z.input<typeof Keyframe>>
|
||||
> = ({ entity, onUpdate }) => {
|
||||
return (
|
||||
<>
|
||||
<fieldset>
|
||||
<label htmlFor="keyframe-offset">Offset</label>
|
||||
<FloatInput
|
||||
value={entity.offset}
|
||||
onChange={(value) =>
|
||||
onUpdate(
|
||||
produce(entity, (draft) => {
|
||||
draft.offset = value;
|
||||
})
|
||||
)
|
||||
}
|
||||
id="keyframe-offset"
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<label>Value</label>
|
||||
<FloatInput
|
||||
value={entity.value}
|
||||
onChange={(value) =>
|
||||
onUpdate(
|
||||
produce(entity, (draft) => {
|
||||
draft.value = value;
|
||||
})
|
||||
)
|
||||
}
|
||||
id="keyframe-value"
|
||||
/>
|
||||
</fieldset>
|
||||
|
||||
{entity.interpolation && (
|
||||
<InterpolationProperties
|
||||
onUpdate={(updatedEntity) =>
|
||||
onUpdate(
|
||||
produce(entity, (draft) => {
|
||||
draft.interpolation = updatedEntity;
|
||||
})
|
||||
)
|
||||
}
|
||||
entity={entity.interpolation}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const AnimatedNumberProperties: FC<
|
||||
@ -23,51 +104,16 @@ const AnimatedNumberProperties: FC<
|
||||
{entity.keyframes.values.map((keyframe, index) => {
|
||||
return (
|
||||
<div key={index}>
|
||||
<div className="flex flex-row gap-3">
|
||||
<label className="flex flex-col items-start w-16">
|
||||
<span className="label text-sm opacity-70">Offset</span>
|
||||
<input
|
||||
value={keyframe.offset}
|
||||
onChange={(e) =>
|
||||
onUpdate(
|
||||
produce(entity, (draft) => {
|
||||
draft.keyframes.values[index].offset = Number(
|
||||
e.target.value
|
||||
);
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label className="flex flex-col items-start">
|
||||
<span className="label text-sm opacity-70">Value</span>
|
||||
<input
|
||||
value={keyframe.value}
|
||||
onChange={(e) =>
|
||||
onUpdate(
|
||||
produce(entity, (draft) => {
|
||||
draft.keyframes.values[index].value = Number(
|
||||
e.target.value
|
||||
);
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
{keyframe.interpolation && (
|
||||
<InterpolationProperties
|
||||
onUpdate={(updatedEntity) =>
|
||||
onUpdate(
|
||||
produce(entity, (draft) => {
|
||||
draft.keyframes.values[index].interpolation =
|
||||
updatedEntity;
|
||||
})
|
||||
)
|
||||
}
|
||||
entity={keyframe.interpolation}
|
||||
/>
|
||||
)}
|
||||
<KeyframeProperties
|
||||
entity={keyframe}
|
||||
onUpdate={(nextKeyframe) =>
|
||||
onUpdate(
|
||||
produce(entity, (draft) => {
|
||||
draft.keyframes.values[index] = nextKeyframe;
|
||||
})
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
|
@ -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}
|
||||
/>
|
||||
))}
|
||||
|
@ -2,6 +2,7 @@ import {
|
||||
BoxIcon,
|
||||
CircleIcon,
|
||||
CursorArrowIcon,
|
||||
FontStyleIcon,
|
||||
MixIcon,
|
||||
Pencil1Icon,
|
||||
Pencil2Icon,
|
||||
@ -9,48 +10,81 @@ import {
|
||||
TextIcon,
|
||||
} from "@radix-ui/react-icons";
|
||||
import * as Toolbar from "@radix-ui/react-toolbar";
|
||||
import { FC, ReactNode } from "react";
|
||||
import { motion } from "framer-motion";
|
||||
import { FC, ReactNode, useMemo, useState } from "react";
|
||||
import { EntitiesService } from "services/entities.service";
|
||||
|
||||
const ToolBarButton: FC<{ children: ReactNode; onClick?: () => void }> = ({
|
||||
children,
|
||||
onClick,
|
||||
}) => {
|
||||
const [didHover, setDidHover] = useState(false);
|
||||
|
||||
const ToolBarButton: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<Toolbar.Button
|
||||
onClick={onClick}
|
||||
onMouseOver={() => !didHover && setDidHover(true)}
|
||||
asChild
|
||||
className="text-white p-[10px] bg-gray-900 flex-shrink-0 flex-grow-0
|
||||
basis-auto w-[40px] h-[40px] rounded inline-flex text-[13px] leading-none
|
||||
items-center justify-center outline-none hover:bg-indigo-900
|
||||
transition-colors
|
||||
focus:relative focus:shadow-[0_0_0_2px] focus:shadow-indigo"
|
||||
>
|
||||
{children}
|
||||
<motion.button
|
||||
animate={didHover ? "enter" : undefined}
|
||||
whileTap="press"
|
||||
variants={{
|
||||
enter: { scale: 1 },
|
||||
from: { scale: 0 },
|
||||
press: { scale: 0.9 },
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</motion.button>
|
||||
</Toolbar.Button>
|
||||
);
|
||||
};
|
||||
|
||||
const ToolBar = () => {
|
||||
const entitiesService = useMemo(() => new EntitiesService(), []);
|
||||
|
||||
return (
|
||||
<Toolbar.Root
|
||||
asChild
|
||||
className="bg-gray-800 flex flex-col gap-1 p-1 h-full"
|
||||
orientation="vertical"
|
||||
>
|
||||
<ToolBarButton>
|
||||
<CursorArrowIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<Toolbar.Separator />
|
||||
<ToolBarButton>
|
||||
<BoxIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<ToolBarButton>
|
||||
<CircleIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<ToolBarButton>
|
||||
<Pencil1Icon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<ToolBarButton>
|
||||
<MixIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<Toolbar.Separator />
|
||||
<ToolBarButton>
|
||||
<TextIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<motion.div
|
||||
animate="enter"
|
||||
initial="from"
|
||||
transition={{ staggerChildren: 0.1 }}
|
||||
variants={{ enter: {}, from: {} }}
|
||||
>
|
||||
<ToolBarButton>
|
||||
<CursorArrowIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<Toolbar.Separator />
|
||||
<ToolBarButton onClick={() => entitiesService.createRect()}>
|
||||
<BoxIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<ToolBarButton onClick={() => entitiesService.createEllipse()}>
|
||||
<CircleIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<ToolBarButton>
|
||||
<Pencil1Icon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<ToolBarButton>
|
||||
<MixIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<Toolbar.Separator />
|
||||
<ToolBarButton onClick={() => entitiesService.createText()}>
|
||||
<TextIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
<ToolBarButton onClick={() => entitiesService.createStaggeredText()}>
|
||||
<FontStyleIcon width="100%" height="100%" />
|
||||
</ToolBarButton>
|
||||
</motion.div>
|
||||
</Toolbar.Root>
|
||||
);
|
||||
};
|
||||
|
@ -2,6 +2,16 @@ import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App";
|
||||
import "./styles.css";
|
||||
import { useFontsStore } from "stores/fonts.store";
|
||||
import { invoke } from "@tauri-apps/api";
|
||||
|
||||
invoke("get_system_families").then((data) => {
|
||||
if (data && Array.isArray(data)) {
|
||||
const fontsStore = useFontsStore.getState();
|
||||
fontsStore.setFonts(data);
|
||||
fontsStore.setDidInit(true);
|
||||
}
|
||||
});
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
|
@ -87,3 +87,18 @@ export function staticAnimatedVec3(
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function staticAnimatedTransform(
|
||||
translate: [number, number],
|
||||
scale: [number, number],
|
||||
rotate: [number, number, number],
|
||||
skew: [number, number]
|
||||
): z.input<typeof AnimatedTransform> {
|
||||
return {
|
||||
type: "Transform",
|
||||
translate: staticAnimatedVec2(...translate),
|
||||
scale: staticAnimatedVec2(...scale),
|
||||
rotate: staticAnimatedVec3(...rotate),
|
||||
skew: staticAnimatedVec2(...skew),
|
||||
};
|
||||
}
|
||||
|
124
app/src/services/entities.service.ts
Normal file
124
app/src/services/entities.service.ts
Normal file
@ -0,0 +1,124 @@
|
||||
import { EntityType } from "primitives/Entities";
|
||||
import { PaintStyleType, TextAlign } from "primitives/Paint";
|
||||
import { staticAnimatedTransform, staticAnimatedVec2 } from "primitives/Values";
|
||||
import { useEntitiesStore } from "stores/entities.store";
|
||||
import { useTimelineStore } from "stores/timeline.store";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
export class EntitiesService {
|
||||
get entitiesStore() {
|
||||
return useEntitiesStore.getState();
|
||||
}
|
||||
|
||||
private get timelineStore() {
|
||||
return useTimelineStore.getState();
|
||||
}
|
||||
|
||||
createUuid() {
|
||||
return uuid();
|
||||
}
|
||||
|
||||
createRect() {
|
||||
return this.entitiesStore.createEntity({
|
||||
type: EntityType.Enum.Rect,
|
||||
id: this.createUuid(),
|
||||
cache: {},
|
||||
paint: {
|
||||
style: {
|
||||
type: PaintStyleType.Enum.Fill,
|
||||
color: {
|
||||
value: [233, 100, 150, 1.0],
|
||||
},
|
||||
},
|
||||
},
|
||||
size: staticAnimatedVec2(500, 500),
|
||||
origin: staticAnimatedVec2(-250, -250),
|
||||
position: staticAnimatedVec2(...this.timelineStore.size),
|
||||
transform: staticAnimatedTransform([0, 0], [1, 1], [0, 0, 0], [0, 0]),
|
||||
animation_data: {
|
||||
offset: 0,
|
||||
duration: 3,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
createEllipse() {
|
||||
return this.entitiesStore.createEntity({
|
||||
type: EntityType.Enum.Ellipse,
|
||||
id: this.createUuid(),
|
||||
cache: {},
|
||||
paint: {
|
||||
style: {
|
||||
type: PaintStyleType.Enum.Fill,
|
||||
color: {
|
||||
value: [233, 100, 150, 1.0],
|
||||
},
|
||||
},
|
||||
},
|
||||
radius: staticAnimatedVec2(500, 500),
|
||||
origin: staticAnimatedVec2(-250, -250),
|
||||
position: staticAnimatedVec2(...this.timelineStore.size),
|
||||
transform: staticAnimatedTransform([0, 0], [1, 1], [0, 0, 0], [0, 0]),
|
||||
animation_data: {
|
||||
offset: 0,
|
||||
duration: 3,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
createText(text?: string) {
|
||||
return this.entitiesStore.createEntity({
|
||||
type: EntityType.Enum.Text,
|
||||
id: this.createUuid(),
|
||||
cache: {},
|
||||
text: text || "Hallo Welt",
|
||||
paint: {
|
||||
align: TextAlign.Enum.Center,
|
||||
size: 20,
|
||||
font_name: "Helvetica-Bold",
|
||||
style: {
|
||||
type: PaintStyleType.Enum.Fill,
|
||||
color: {
|
||||
value: [233, 100, 150, 1.0],
|
||||
},
|
||||
},
|
||||
},
|
||||
origin: staticAnimatedVec2(-250, -250),
|
||||
transform: staticAnimatedTransform([0, 0], [1, 1], [0, 0, 0], [0, 0]),
|
||||
animation_data: {
|
||||
offset: 0,
|
||||
duration: 3,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
createStaggeredText(text?: string) {
|
||||
return this.entitiesStore.createEntity({
|
||||
type: EntityType.Enum.StaggeredText,
|
||||
id: this.createUuid(),
|
||||
cache: {},
|
||||
text: text || "Hallo Welt",
|
||||
stagger: 0.1,
|
||||
letter: {
|
||||
paint: {
|
||||
align: TextAlign.Enum.Center,
|
||||
size: 20,
|
||||
font_name: "Helvetica-Bold",
|
||||
style: {
|
||||
type: PaintStyleType.Enum.Fill,
|
||||
color: {
|
||||
value: [233, 100, 150, 1.0],
|
||||
},
|
||||
},
|
||||
},
|
||||
transform: staticAnimatedTransform([0, 0], [1, 1], [0, 0, 0], [0, 0]),
|
||||
},
|
||||
origin: staticAnimatedVec2(-250, -250),
|
||||
transform: staticAnimatedTransform([0, 0], [1, 1], [0, 0, 0], [0, 0]),
|
||||
animation_data: {
|
||||
offset: 0,
|
||||
duration: 3,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
@ -15,7 +15,9 @@ interface EntitiesStore {
|
||||
index: number,
|
||||
entity: Partial<z.input<typeof AnimatedEntity>>
|
||||
) => void;
|
||||
|
||||
createEntity: (
|
||||
entity: z.input<typeof AnimatedEntity>
|
||||
) => z.input<typeof AnimatedEntity>;
|
||||
deleteEntity: (index: number) => void;
|
||||
updateEntityById: (
|
||||
id: string,
|
||||
@ -23,13 +25,20 @@ interface EntitiesStore {
|
||||
) => void;
|
||||
}
|
||||
|
||||
const useEntitiesStore = create<EntitiesStore>((set) => ({
|
||||
const useEntitiesStore = create<EntitiesStore>((set, get) => ({
|
||||
entities: EXAMPLE_ANIMATED_ENTITIES,
|
||||
selectedKeyframe: undefined,
|
||||
selectEntity: (index) => set(() => ({ selectedEntity: index })),
|
||||
deselectEntity: () => set(() => ({ selectedEntity: undefined })),
|
||||
selectedEntity: undefined,
|
||||
setEntities: (entities) => set({ entities }),
|
||||
setEntities: (entities) => {
|
||||
console.log("set entities");
|
||||
set({ entities });
|
||||
},
|
||||
createEntity: (entity) => {
|
||||
set({ entities: [...get().entities, entity] });
|
||||
return entity;
|
||||
},
|
||||
updateEntityById: (id, entity) =>
|
||||
set(({ entities }) => {
|
||||
const nextEntities = produce(entities, (draft) => {
|
||||
|
@ -2,11 +2,15 @@ import { create } from "zustand";
|
||||
|
||||
interface FontsStore {
|
||||
fonts: Array<string>;
|
||||
didInit: boolean;
|
||||
setDidInit: (didInit: boolean) => void;
|
||||
setFonts: (fonts: Array<string>) => void;
|
||||
}
|
||||
|
||||
const useFontsStore = create<FontsStore>((set) => ({
|
||||
fonts: [],
|
||||
didInit: false,
|
||||
setDidInit: (didInit) => set({ didInit }),
|
||||
setFonts: (fonts) => set({ fonts }),
|
||||
}));
|
||||
|
||||
|
@ -13,6 +13,10 @@
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
span {
|
||||
@apply text-white;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
@ -122,12 +126,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
.label {
|
||||
@apply mb-1;
|
||||
label {
|
||||
@apply mb-1 text-sm opacity-70;
|
||||
}
|
||||
|
||||
label {
|
||||
@apply mb-2;
|
||||
fieldset {
|
||||
@apply mb-2 flex flex-col items-start w-16;
|
||||
}
|
||||
|
||||
:root {
|
||||
|
290
app/yarn.lock
290
app/yarn.lock
@ -27,7 +27,7 @@
|
||||
resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.3.tgz"
|
||||
integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==
|
||||
|
||||
"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.20.12":
|
||||
"@babel/core@^7.20.12":
|
||||
version "7.22.1"
|
||||
resolved "https://registry.npmjs.org/@babel/core/-/core-7.22.1.tgz"
|
||||
integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==
|
||||
@ -234,6 +234,111 @@
|
||||
resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.4.tgz"
|
||||
integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
|
||||
|
||||
"@esbuild/android-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz#bafb75234a5d3d1b690e7c2956a599345e84a2fd"
|
||||
integrity sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==
|
||||
|
||||
"@esbuild/android-arm@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.19.tgz#5898f7832c2298bc7d0ab53701c57beb74d78b4d"
|
||||
integrity sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==
|
||||
|
||||
"@esbuild/android-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.19.tgz#658368ef92067866d95fb268719f98f363d13ae1"
|
||||
integrity sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==
|
||||
|
||||
"@esbuild/darwin-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz#584c34c5991b95d4d48d333300b1a4e2ff7be276"
|
||||
integrity sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==
|
||||
|
||||
"@esbuild/darwin-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz#7751d236dfe6ce136cce343dce69f52d76b7f6cb"
|
||||
integrity sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==
|
||||
|
||||
"@esbuild/freebsd-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz#cacd171665dd1d500f45c167d50c6b7e539d5fd2"
|
||||
integrity sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==
|
||||
|
||||
"@esbuild/freebsd-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz#0769456eee2a08b8d925d7c00b79e861cb3162e4"
|
||||
integrity sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==
|
||||
|
||||
"@esbuild/linux-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz#38e162ecb723862c6be1c27d6389f48960b68edb"
|
||||
integrity sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==
|
||||
|
||||
"@esbuild/linux-arm@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz#1a2cd399c50040184a805174a6d89097d9d1559a"
|
||||
integrity sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==
|
||||
|
||||
"@esbuild/linux-ia32@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz#e28c25266b036ce1cabca3c30155222841dc035a"
|
||||
integrity sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==
|
||||
|
||||
"@esbuild/linux-loong64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz#0f887b8bb3f90658d1a0117283e55dbd4c9dcf72"
|
||||
integrity sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==
|
||||
|
||||
"@esbuild/linux-mips64el@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz#f5d2a0b8047ea9a5d9f592a178ea054053a70289"
|
||||
integrity sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==
|
||||
|
||||
"@esbuild/linux-ppc64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz#876590e3acbd9fa7f57a2c7d86f83717dbbac8c7"
|
||||
integrity sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==
|
||||
|
||||
"@esbuild/linux-riscv64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz#7f49373df463cd9f41dc34f9b2262d771688bf09"
|
||||
integrity sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==
|
||||
|
||||
"@esbuild/linux-s390x@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz#e2afd1afcaf63afe2c7d9ceacd28ec57c77f8829"
|
||||
integrity sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==
|
||||
|
||||
"@esbuild/linux-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz#8a0e9738b1635f0c53389e515ae83826dec22aa4"
|
||||
integrity sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==
|
||||
|
||||
"@esbuild/netbsd-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz#c29fb2453c6b7ddef9a35e2c18b37bda1ae5c462"
|
||||
integrity sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==
|
||||
|
||||
"@esbuild/openbsd-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz#95e75a391403cb10297280d524d66ce04c920691"
|
||||
integrity sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==
|
||||
|
||||
"@esbuild/sunos-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz#722eaf057b83c2575937d3ffe5aeb16540da7273"
|
||||
integrity sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==
|
||||
|
||||
"@esbuild/win32-arm64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz#9aa9dc074399288bdcdd283443e9aeb6b9552b6f"
|
||||
integrity sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==
|
||||
|
||||
"@esbuild/win32-ia32@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz#95ad43c62ad62485e210f6299c7b2571e48d2b03"
|
||||
integrity sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==
|
||||
|
||||
"@esbuild/win32-x64@0.17.19":
|
||||
version "0.17.19"
|
||||
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz"
|
||||
@ -258,18 +363,6 @@
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.2.7"
|
||||
|
||||
"@hapi/hoek@^9.0.0":
|
||||
version "9.3.0"
|
||||
resolved "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz"
|
||||
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
|
||||
|
||||
"@hapi/topo@^5.0.0":
|
||||
version "5.1.0"
|
||||
resolved "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz"
|
||||
integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
|
||||
version "0.3.3"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz"
|
||||
@ -289,16 +382,16 @@
|
||||
resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz"
|
||||
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
|
||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||
|
||||
"@jridgewell/sourcemap-codec@1.4.14":
|
||||
version "1.4.14"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz"
|
||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.13":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
|
||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||
|
||||
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
|
||||
version "0.3.18"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz"
|
||||
@ -315,7 +408,7 @@
|
||||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
|
||||
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
|
||||
@ -609,6 +702,34 @@
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
|
||||
"@radix-ui/react-select@^1.2.2":
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-1.2.2.tgz#caa981fa0d672cf3c1b2a5240135524e69b32181"
|
||||
integrity sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/number" "1.0.1"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-collection" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.4"
|
||||
"@radix-ui/react-focus-guards" "1.0.1"
|
||||
"@radix-ui/react-focus-scope" "1.0.3"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-popper" "1.1.2"
|
||||
"@radix-ui/react-portal" "1.0.3"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-use-previous" "1.0.1"
|
||||
"@radix-ui/react-visually-hidden" "1.0.3"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-separator@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.0.3.tgz"
|
||||
@ -651,7 +772,7 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
|
||||
"@radix-ui/react-toggle-group@^1.0.4", "@radix-ui/react-toggle-group@1.0.4":
|
||||
"@radix-ui/react-toggle-group@1.0.4", "@radix-ui/react-toggle-group@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.0.4.tgz"
|
||||
integrity sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==
|
||||
@ -749,6 +870,14 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-visually-hidden@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac"
|
||||
integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
|
||||
"@radix-ui/rect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz"
|
||||
@ -756,28 +885,51 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@sideway/address@^4.1.3":
|
||||
version "4.1.4"
|
||||
resolved "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz"
|
||||
integrity sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
|
||||
"@sideway/formula@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz"
|
||||
integrity sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==
|
||||
|
||||
"@sideway/pinpoint@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz"
|
||||
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
|
||||
|
||||
"@tauri-apps/api@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.npmjs.org/@tauri-apps/api/-/api-1.3.0.tgz"
|
||||
integrity sha512-AH+3FonkKZNtfRtGrObY38PrzEj4d+1emCbwNGu0V2ENbXjlLHMZQlUh+Bhu/CRmjaIwZMGJ3yFvWaZZgTHoog==
|
||||
|
||||
"@tauri-apps/cli-darwin-arm64@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-1.3.1.tgz#ef0fe290e0a6e3e53fa2cc4f1a72a0c87921427c"
|
||||
integrity sha512-QlepYVPgOgspcwA/u4kGG4ZUijlXfdRtno00zEy+LxinN/IRXtk+6ErVtsmoLi1ZC9WbuMwzAcsRvqsD+RtNAg==
|
||||
|
||||
"@tauri-apps/cli-darwin-x64@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-1.3.1.tgz#4c84ea0f08a5b636b067943d637a38e091a4aad3"
|
||||
integrity sha512-fKcAUPVFO3jfDKXCSDGY0MhZFF/wDtx3rgFnogWYu4knk38o9RaqRkvMvqJhLYPuWaEM5h6/z1dRrr9KKCbrVg==
|
||||
|
||||
"@tauri-apps/cli-linux-arm-gnueabihf@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-1.3.1.tgz#a4f1b237189e4f8f89cc890e1dc2eec76d4345be"
|
||||
integrity sha512-+4H0dv8ltJHYu/Ma1h9ixUPUWka9EjaYa8nJfiMsdCI4LJLNE6cPveE7RmhZ59v9GW1XB108/k083JUC/OtGvA==
|
||||
|
||||
"@tauri-apps/cli-linux-arm64-gnu@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-1.3.1.tgz#e2391326b64dfe13c7442bdcc13c4988ce5e6df9"
|
||||
integrity sha512-Pj3odVO1JAxLjYmoXKxcrpj/tPxcA8UP8N06finhNtBtBaxAjrjjxKjO4968KB0BUH7AASIss9EL4Tr0FGnDuw==
|
||||
|
||||
"@tauri-apps/cli-linux-arm64-musl@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.3.1.tgz#49354349f80f879ffc6950c0c03c0aea1395efa5"
|
||||
integrity sha512-tA0JdDLPFaj42UDIVcF2t8V0tSha40rppcmAR/MfQpTCxih6399iMjwihz9kZE1n4b5O4KTq9GliYo50a8zYlQ==
|
||||
|
||||
"@tauri-apps/cli-linux-x64-gnu@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-1.3.1.tgz#9a33ffe9e0d9b1b3825db57cbcfcddeb773682c6"
|
||||
integrity sha512-FDU+Mnvk6NLkqQimcNojdKpMN4Y3W51+SQl+NqG9AFCWprCcSg62yRb84751ujZuf2MGT8HQOfmd0i77F4Q3tQ==
|
||||
|
||||
"@tauri-apps/cli-linux-x64-musl@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-1.3.1.tgz#5283731e894c17bc070c499e73145cfe2633ef21"
|
||||
integrity sha512-MpO3akXFmK8lZYEbyQRDfhdxz1JkTBhonVuz5rRqxwA7gnGWHa1aF1+/2zsy7ahjB2tQ9x8DDFDMdVE20o9HrA==
|
||||
|
||||
"@tauri-apps/cli-win32-ia32-msvc@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-1.3.1.tgz#f31538abfd94f27ade1f17d01f30da6be1660c6f"
|
||||
integrity sha512-9Boeo3K5sOrSBAZBuYyGkpV2RfnGQz3ZhGJt4hE6P+HxRd62lS6+qDKAiw1GmkZ0l1drc2INWrNeT50gwOKwIQ==
|
||||
|
||||
"@tauri-apps/cli-win32-x64-msvc@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-1.3.1.tgz"
|
||||
@ -815,7 +967,7 @@
|
||||
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz"
|
||||
integrity sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==
|
||||
|
||||
"@types/node@^18.7.10", "@types/node@>= 14":
|
||||
"@types/node@^18.7.10":
|
||||
version "18.16.16"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-18.16.16.tgz"
|
||||
integrity sha512-NpaM49IGQQAUlBhHMF82QH80J08os4ZmyF9MkpCzWAGuOHqE4gTEbhzd7L3l5LmWuZ6E0OiC1FweQ4tsiW35+g==
|
||||
@ -825,14 +977,14 @@
|
||||
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz"
|
||||
integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==
|
||||
|
||||
"@types/react-dom@*", "@types/react-dom@^18.0.6":
|
||||
"@types/react-dom@^18.0.6":
|
||||
version "18.2.4"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.4.tgz"
|
||||
integrity sha512-G2mHoTMTL4yoydITgOGwWdWMVd8sNgyEP85xVmMKAPUBwQWm9wBPQUmvbeF4V3WBY1P7mmL4BkjQ0SqUpf1snw==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@^16.8.0 || ^17.0.0 || ^18.0.0", "@types/react@^16.9.0 || ^17.0.0 || ^18.0.0", "@types/react@^18.0.15":
|
||||
"@types/react@*", "@types/react@^18.0.15":
|
||||
version "18.2.7"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-18.2.7.tgz"
|
||||
integrity sha512-ojrXpSH2XFCmHm7Jy3q44nXDyN54+EYKP2lBhJ2bqfyPj6cIUW/FZW/Csdia34NQgq7KYcAlHi5184m4X88+yw==
|
||||
@ -938,7 +1090,7 @@ braces@^3.0.2, braces@~3.0.2:
|
||||
dependencies:
|
||||
fill-range "^7.0.1"
|
||||
|
||||
browserslist@^4.21.3, browserslist@^4.21.5, "browserslist@>= 4.21.0":
|
||||
browserslist@^4.21.3, browserslist@^4.21.5:
|
||||
version "4.21.7"
|
||||
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.21.7.tgz"
|
||||
integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==
|
||||
@ -994,7 +1146,7 @@ class-variance-authority@^0.6.0:
|
||||
dependencies:
|
||||
clsx "1.2.1"
|
||||
|
||||
clsx@^1.2.1, clsx@1.2.1:
|
||||
clsx@1.2.1, clsx@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz"
|
||||
integrity sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==
|
||||
@ -1011,16 +1163,16 @@ color-convert@^1.9.0:
|
||||
dependencies:
|
||||
color-name "1.1.3"
|
||||
|
||||
color-name@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
color-name@1.1.3:
|
||||
version "1.1.3"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz"
|
||||
integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
|
||||
|
||||
color-name@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
commander@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
|
||||
@ -1169,6 +1321,11 @@ fs.realpath@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
|
||||
|
||||
fsevents@~2.3.2:
|
||||
version "2.3.2"
|
||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz"
|
||||
@ -1232,7 +1389,7 @@ has@^1.0.3:
|
||||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
immer@^10.0.2, immer@>=9.0:
|
||||
immer@^10.0.2:
|
||||
version "10.0.2"
|
||||
resolved "https://registry.npmjs.org/immer/-/immer-10.0.2.tgz"
|
||||
integrity sha512-Rx3CqeqQ19sxUtYV9CU911Vhy8/721wRFnJv3REVGWUmoAcIwzifTsdmJte/MV+0/XpM35LZdQMBGkRIoLPwQA==
|
||||
@ -1293,17 +1450,6 @@ jiti@^1.18.2:
|
||||
resolved "https://registry.npmjs.org/jiti/-/jiti-1.18.2.tgz"
|
||||
integrity sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==
|
||||
|
||||
joi@^17.2.1:
|
||||
version "17.9.2"
|
||||
resolved "https://registry.npmjs.org/joi/-/joi-17.9.2.tgz"
|
||||
integrity sha512-Itk/r+V4Dx0V3c7RLFdRh12IOjySm2/WGPMubBT92cQvRfYZhPM2W0hZlctjj72iES8jsRCwp7S/cRmWBnJ4nw==
|
||||
dependencies:
|
||||
"@hapi/hoek" "^9.0.0"
|
||||
"@hapi/topo" "^5.0.0"
|
||||
"@sideway/address" "^4.1.3"
|
||||
"@sideway/formula" "^3.0.1"
|
||||
"@sideway/pinpoint" "^2.0.0"
|
||||
|
||||
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz"
|
||||
@ -1505,7 +1651,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0:
|
||||
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz"
|
||||
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
|
||||
|
||||
postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.23, postcss@>=8.0.9:
|
||||
postcss@^8.4.23:
|
||||
version "8.4.24"
|
||||
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.24.tgz"
|
||||
integrity sha512-M0RzbcI0sO/XJNucsGjvWU9ERWxb/ytp1w6dKtxTKgixdtQDq4rmx/g8W1hnaheq9jgwL/oyEdH5Bc4WwJKMqg==
|
||||
@ -1519,7 +1665,7 @@ queue-microtask@^1.2.2:
|
||||
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
"react-dom@^16.8 || ^17.0 || ^18.0", react-dom@^18.0.0, react-dom@^18.2.0, react-dom@>=16.8.0:
|
||||
react-dom@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz"
|
||||
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
|
||||
@ -1560,7 +1706,7 @@ react-style-singleton@^2.2.1:
|
||||
invariant "^2.2.4"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"react@^16.5.1 || ^17.0.0 || ^18.0.0", "react@^16.8 || ^17.0 || ^18.0", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^16.x || ^17.x || ^18.x", react@^18.0.0, react@^18.2.0, react@>=16.8, react@>=16.8.0:
|
||||
react@^18.2.0:
|
||||
version "18.2.0"
|
||||
resolved "https://registry.npmjs.org/react/-/react-18.2.0.tgz"
|
||||
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
|
||||
@ -1614,13 +1760,6 @@ run-parallel@^1.1.9:
|
||||
dependencies:
|
||||
queue-microtask "^1.2.2"
|
||||
|
||||
rxjs@^6.5.x:
|
||||
version "6.6.7"
|
||||
resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz"
|
||||
integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==
|
||||
dependencies:
|
||||
tslib "^1.9.0"
|
||||
|
||||
scheduler@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz"
|
||||
@ -1673,7 +1812,7 @@ tailwindcss-animate@^1.0.5:
|
||||
resolved "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.5.tgz"
|
||||
integrity sha512-UU3qrOJ4lFQABY+MVADmBm+0KW3xZyhMdRvejwtXqYOL7YjHYxmuREFAZdmVG5LPe5E9CAst846SLC4j5I3dcw==
|
||||
|
||||
tailwindcss@^3.3.2, "tailwindcss@>=3.0.0 || insiders":
|
||||
tailwindcss@^3.3.2:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz"
|
||||
integrity sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==
|
||||
@ -1738,17 +1877,12 @@ tsconfck@^2.1.0:
|
||||
resolved "https://registry.npmjs.org/tsconfck/-/tsconfck-2.1.1.tgz"
|
||||
integrity sha512-ZPCkJBKASZBmBUNqGHmRhdhM8pJYDdOXp4nRgj/O0JwUwsMq50lCDRQP/M5GBNAA0elPrq4gAeu4dkaVCuKWww==
|
||||
|
||||
tslib@^1.9.0:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0:
|
||||
version "2.5.2"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz"
|
||||
integrity sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==
|
||||
|
||||
"typescript@^4.3.5 || ^5.0.0", typescript@^4.9.5, "typescript@>= 4.5.5 < 6":
|
||||
typescript@^4.9.5:
|
||||
version "4.9.5"
|
||||
resolved "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz"
|
||||
integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
|
||||
@ -1800,7 +1934,7 @@ vite-tsconfig-paths@^4.2.0:
|
||||
globrex "^0.1.2"
|
||||
tsconfck "^2.1.0"
|
||||
|
||||
vite@*, vite@^4.1.0-beta.0, vite@^4.2.1:
|
||||
vite@^4.2.1:
|
||||
version "4.3.9"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-4.3.9.tgz"
|
||||
integrity sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==
|
||||
@ -1826,7 +1960,7 @@ yaml@^2.1.1:
|
||||
resolved "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz"
|
||||
integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==
|
||||
|
||||
zod@^3.20.6, zod@^3.21.4:
|
||||
zod@^3.21.4:
|
||||
version "3.21.4"
|
||||
resolved "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz"
|
||||
integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==
|
||||
|
Loading…
x
Reference in New Issue
Block a user