merge main
No files matched your search
@@ -5,7 +5,7 @@ import { twMerge } from "tailwind-merge";
|
||||
export interface ButtonPropsI
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
icon?: IconDefinition;
|
||||
variant?: "primary" | "secondary";
|
||||
variant?: "primary" | "secondary" | "action";
|
||||
}
|
||||
|
||||
export const Button = ({
|
||||
@@ -25,6 +25,9 @@ export const Button = ({
|
||||
case "secondary": {
|
||||
return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
|
||||
}
|
||||
case "action": {
|
||||
return " bg-action hover:bg-action-500 text-white focus-visible:outline-action";
|
||||
}
|
||||
case "primary":
|
||||
default: {
|
||||
return " bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import {
|
||||
lastSelectedTab,
|
||||
selectedTab,
|
||||
sidePanelHeight,
|
||||
sidePanelVisible,
|
||||
@@ -18,7 +17,7 @@ export const SidePanelMenu = () => {
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"bg-assets-background fixed",
|
||||
"fixed bg-assets-background",
|
||||
"px-2 py-4",
|
||||
"overflow-y-auto",
|
||||
].join(" ")}
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import {
|
||||
dndSidePanelWidth,
|
||||
selectedTab,
|
||||
sidePanelHeight,
|
||||
sidePanelWidth,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { selectedTab, sidePanelHeight } from "~/pages/PageEnigma/store";
|
||||
import { tabList } from "~/pages/PageEnigma/comps/SidePanelTabs/tabList";
|
||||
import { useMouseEventsSidePanel } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsSidePanel";
|
||||
|
||||
@@ -18,7 +13,9 @@ export const SidePanelTabs = () => {
|
||||
{tabList.map((tab) => (
|
||||
<div
|
||||
key={tab.value}
|
||||
className={tab.value === selectedTab.value?.value ? "" : "hidden"}
|
||||
className={
|
||||
tab.value === selectedTab.value?.value ? "h-full" : "hidden"
|
||||
}
|
||||
>
|
||||
{tab.component}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
import { Button, ItemPicker, Label, H4, Textarea } from "~/components";
|
||||
import { Button, ItemPicker, Label, Textarea } from "~/components";
|
||||
|
||||
import { AppUiContext } from "../../contexts/AppUiContext";
|
||||
import { APPUI_ACTION_TYPES, APPUI_VIEW_MODES } from "../../reducers";
|
||||
@@ -60,9 +60,9 @@ export const TabStylization = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col gap-3">
|
||||
<div className="flex h-full w-full flex-col gap-3 p-4">
|
||||
<div className="flex flex-col">
|
||||
<H4>Select Base Style</H4>
|
||||
<Label>Select Base Style</Label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<ItemPicker
|
||||
label="Anime"
|
||||
|
||||
@@ -2,6 +2,9 @@ import { animationFilter, animationItems } from "~/pages/PageEnigma/store";
|
||||
import { AssetFilterOption } from "~/pages/PageEnigma/models";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
|
||||
import { Button } from "~/components";
|
||||
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const AnimationTab = () => {
|
||||
useSignals();
|
||||
@@ -9,39 +12,36 @@ export const AnimationTab = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mb-4 mt-2 flex justify-start gap-2 px-2">
|
||||
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
animationFilter.value === AssetFilterOption.ALL
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
animationFilter.value === AssetFilterOption.ALL ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (animationFilter.value = AssetFilterOption.ALL)}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background text-nowrap rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
animationFilter.value === AssetFilterOption.MINE
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
animationFilter.value === AssetFilterOption.MINE ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (animationFilter.value = AssetFilterOption.MINE)}
|
||||
disabled={!animationItems.value.some((item) => item.isMine)}
|
||||
>
|
||||
My Animations
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
animationFilter.value === AssetFilterOption.BOOKMARKED
|
||||
? "border-2 border-brand-primary"
|
||||
? "active"
|
||||
: "",
|
||||
].join(" ")}
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() =>
|
||||
(animationFilter.value = AssetFilterOption.BOOKMARKED)
|
||||
}
|
||||
@@ -51,12 +51,16 @@ export const AnimationTab = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full px-2">
|
||||
<button className="bg-assets-background w-full rounded-lg py-2">
|
||||
<div className="w-full px-4">
|
||||
<Button
|
||||
icon={faCirclePlus}
|
||||
variant="action"
|
||||
className="w-full py-3 text-sm font-medium"
|
||||
>
|
||||
Upload Animation
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full overflow-y-auto px-2 pt-2">
|
||||
<div className="h-full w-full overflow-y-auto px-4 pt-4">
|
||||
<ItemElements
|
||||
items={animationItems.value}
|
||||
assetFilter={animationFilter.value}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { audioFilter, audioItems } from "~/pages/PageEnigma/store";
|
||||
import { AssetFilterOption } from "~/pages/PageEnigma/models";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
|
||||
import { Button } from "~/components";
|
||||
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const AudioTab = () => {
|
||||
useSignals();
|
||||
@@ -9,39 +12,36 @@ export const AudioTab = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mb-4 mt-2 flex justify-start gap-2 px-2">
|
||||
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
audioFilter.value === AssetFilterOption.ALL
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
audioFilter.value === AssetFilterOption.ALL ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (audioFilter.value = AssetFilterOption.ALL)}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background text-nowrap rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
audioFilter.value === AssetFilterOption.MINE
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
audioFilter.value === AssetFilterOption.MINE ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (audioFilter.value = AssetFilterOption.MINE)}
|
||||
disabled={!audioItems.value.some((item) => item.isMine)}
|
||||
>
|
||||
My Audios
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
audioFilter.value === AssetFilterOption.BOOKMARKED
|
||||
? "border-2 border-brand-primary"
|
||||
? "active"
|
||||
: "",
|
||||
].join(" ")}
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (audioFilter.value = AssetFilterOption.BOOKMARKED)}
|
||||
disabled={!audioItems.value.some((item) => item.isBookmarked)}
|
||||
>
|
||||
@@ -49,12 +49,16 @@ export const AudioTab = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<button className="bg-assets-background w-full rounded-lg py-2">
|
||||
Upload Audio
|
||||
</button>
|
||||
<div className="w-full px-4">
|
||||
<Button
|
||||
icon={faCirclePlus}
|
||||
variant="action"
|
||||
className="w-full py-3 text-sm font-medium"
|
||||
>
|
||||
Generate Audio
|
||||
</Button>
|
||||
</div>
|
||||
<div className="overflow-y-auto px-2 pt-2">
|
||||
<div className="h-full w-full overflow-y-auto px-4 pt-4">
|
||||
<ItemElements
|
||||
items={audioItems.value}
|
||||
assetFilter={audioFilter.value}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { cameraFilter, cameraItems } from "~/pages/PageEnigma/store";
|
||||
import { AssetFilterOption } from "~/pages/PageEnigma/models";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
|
||||
import { Button } from "~/components";
|
||||
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export const CameraTab = () => {
|
||||
useSignals();
|
||||
@@ -9,39 +12,36 @@ export const CameraTab = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mb-4 mt-2 flex justify-start gap-2 px-2">
|
||||
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
cameraFilter.value === AssetFilterOption.ALL
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
cameraFilter.value === AssetFilterOption.ALL ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (cameraFilter.value = AssetFilterOption.ALL)}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background text-nowrap rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
cameraFilter.value === AssetFilterOption.MINE
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
cameraFilter.value === AssetFilterOption.MINE ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (cameraFilter.value = AssetFilterOption.MINE)}
|
||||
disabled={!cameraItems.value.some((item) => item.isMine)}
|
||||
>
|
||||
My Cameras
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
cameraFilter.value === AssetFilterOption.BOOKMARKED
|
||||
? "border-2 border-brand-primary"
|
||||
? "active"
|
||||
: "",
|
||||
].join(" ")}
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (cameraFilter.value = AssetFilterOption.BOOKMARKED)}
|
||||
disabled={!cameraItems.value.some((item) => item.isBookmarked)}
|
||||
>
|
||||
@@ -49,12 +49,16 @@ export const CameraTab = () => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<button className="bg-assets-background w-full rounded-lg py-2">
|
||||
Create Camera
|
||||
</button>
|
||||
<div className="w-full px-4">
|
||||
<Button
|
||||
icon={faCirclePlus}
|
||||
variant="action"
|
||||
className="w-full py-3 text-sm font-medium"
|
||||
>
|
||||
Create Camera Movement
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 overflow-y-auto px-2 pt-2">
|
||||
<div className="h-full w-full overflow-y-auto px-4 pt-4">
|
||||
<ItemElements
|
||||
items={cameraItems.value}
|
||||
assetFilter={cameraFilter.value}
|
||||
|
||||
@@ -63,11 +63,7 @@ export const ItemElement = ({ item }: Props) => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative rounded-lg"
|
||||
style={{ width: 91, height: 114 }}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div className="relative w-full rounded-lg" onPointerDown={onPointerDown}>
|
||||
<img src={item.thumbnail} alt={item.name} className="rounded-t-lg" />
|
||||
<div
|
||||
className="w-full rounded-b-lg py-1 text-center text-sm"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ItemElement } from "./ItemElement";
|
||||
import { AssetFilterOption, MediaItem } from "~/pages/PageEnigma/models";
|
||||
import { dndSidePanelWidth, sidePanelWidth } from "~/pages/PageEnigma/store";
|
||||
|
||||
interface Props {
|
||||
items: MediaItem[];
|
||||
@@ -7,6 +8,11 @@ interface Props {
|
||||
}
|
||||
|
||||
export const ItemElements = ({ items, assetFilter }: Props) => {
|
||||
const displayWidth =
|
||||
dndSidePanelWidth.value > -1
|
||||
? dndSidePanelWidth.value
|
||||
: sidePanelWidth.value;
|
||||
|
||||
const displayItems = items.filter((item) => {
|
||||
if (assetFilter === AssetFilterOption.ALL) {
|
||||
return true;
|
||||
@@ -16,8 +22,23 @@ export const ItemElements = ({ items, assetFilter }: Props) => {
|
||||
}
|
||||
return item.isBookmarked;
|
||||
});
|
||||
|
||||
function getGridColumnsClass(displayWidth: number): string {
|
||||
if (displayWidth <= 280) {
|
||||
return "grid-cols-2";
|
||||
} else if (displayWidth <= 360) {
|
||||
return "grid-cols-3";
|
||||
} else if (displayWidth <= 440) {
|
||||
return "grid-cols-4";
|
||||
} else {
|
||||
return "grid-cols-4";
|
||||
}
|
||||
}
|
||||
|
||||
const gridColumnsClass = getGridColumnsClass(displayWidth);
|
||||
|
||||
return (
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<div className={`grid ${gridColumnsClass} gap-3`}>
|
||||
{displayItems.map((item) => (
|
||||
<ItemElement key={item.media_id} item={item} />
|
||||
))}
|
||||
|
||||
@@ -7,6 +7,9 @@ import {
|
||||
objectItems,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
|
||||
import { Button } from "~/components";
|
||||
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface Props {
|
||||
type: AssetType;
|
||||
@@ -22,39 +25,36 @@ export const ObjectsTab = ({ type }: Props) => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mb-4 mt-2 flex justify-start gap-2 px-2">
|
||||
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
assetFilter.value === AssetFilterOption.ALL
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
assetFilter.value === AssetFilterOption.ALL ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (assetFilter.value = AssetFilterOption.ALL)}
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background text-nowrap rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
assetFilter.value === AssetFilterOption.MINE
|
||||
? "border-2 border-brand-primary"
|
||||
: "",
|
||||
].join(" ")}
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
assetFilter.value === AssetFilterOption.MINE ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (assetFilter.value = AssetFilterOption.MINE)}
|
||||
disabled={!items.value.some((item) => item.isMine)}
|
||||
>
|
||||
My {type === AssetType.CHARACTER ? "Characters" : "Objects"}
|
||||
</button>
|
||||
<button
|
||||
className={[
|
||||
"bg-assets-background rounded-lg px-3 py-1 text-sm",
|
||||
"disabled:text-gray-500",
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
assetFilter.value === AssetFilterOption.BOOKMARKED
|
||||
? "border-2 border-brand-primary"
|
||||
? "active"
|
||||
: "",
|
||||
].join(" ")}
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (assetFilter.value = AssetFilterOption.BOOKMARKED)}
|
||||
disabled={!items.value.some((item) => item.isBookmarked)}
|
||||
>
|
||||
@@ -62,12 +62,16 @@ export const ObjectsTab = ({ type }: Props) => {
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-2">
|
||||
<button className="bg-assets-background w-full rounded-lg py-2">
|
||||
<div className="w-full px-4">
|
||||
<Button
|
||||
icon={faCirclePlus}
|
||||
variant="action"
|
||||
className="w-full py-3 text-sm font-medium"
|
||||
>
|
||||
Upload {type === AssetType.CHARACTER ? "Character" : "Object"}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="mt-2 overflow-y-auto px-2 pt-2">
|
||||
<div className="h-full w-full overflow-y-auto px-4 pt-4">
|
||||
<ItemElements items={items.value} assetFilter={assetFilter.value} />
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -7,8 +7,8 @@ export const ShapeTab = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="p-2 text-base font-bold">Shapes</div>
|
||||
<div className="mt-2 w-full overflow-y-auto px-2 pt-2">
|
||||
<div className="px-4 pt-4 text-base font-bold">Shapes</div>
|
||||
<div className="w-full overflow-y-auto px-4 pt-4">
|
||||
<ItemElements
|
||||
items={shapeItems.value}
|
||||
assetFilter={shapeFilter.value}
|
||||
|
||||
@@ -43,9 +43,9 @@ export const tabList = [
|
||||
component: <AudioTab />,
|
||||
},
|
||||
{
|
||||
icon: "resources/icons/characters.png",
|
||||
icon: "resources/icons/objects.png",
|
||||
title: "Style (Temporary being moved)",
|
||||
value: AssetType.STYLE,
|
||||
component: <TabStylization/>,
|
||||
}
|
||||
component: <TabStylization />,
|
||||
},
|
||||
];
|
||||
@@ -2,23 +2,19 @@ import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { useCallback, useContext, useEffect } from "react";
|
||||
import {
|
||||
audioGroup,
|
||||
cameraGroup,
|
||||
characterGroups,
|
||||
addNewCharacter,
|
||||
currentTime,
|
||||
objectGroup,
|
||||
loadAudioData,
|
||||
loadCameraData,
|
||||
loadCharacterData,
|
||||
loadObjectData,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import {
|
||||
CharacterGroup,
|
||||
Clip,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
Keyframe,
|
||||
MediaItem,
|
||||
ObjectTrack,
|
||||
QueueClip,
|
||||
QueueKeyframe,
|
||||
UpdateTime,
|
||||
@@ -32,69 +28,16 @@ interface Arguments {
|
||||
data: QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem;
|
||||
}
|
||||
|
||||
function addCharacter(item: ClipUI) {
|
||||
const existingCharacter = characterGroups.value.find(
|
||||
(character) => character.id === item.object_uuid,
|
||||
);
|
||||
|
||||
if (existingCharacter) {
|
||||
return existingCharacter;
|
||||
}
|
||||
|
||||
const newCharacter = {
|
||||
id: item.object_uuid,
|
||||
name: item.object_name,
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionKeyframes: [],
|
||||
lipSyncClips: [],
|
||||
} as CharacterGroup;
|
||||
|
||||
characterGroups.value = [
|
||||
...characterGroups.value.filter(
|
||||
(character) => character.id !== item.object_uuid,
|
||||
),
|
||||
newCharacter,
|
||||
].sort((charA, charB) => (charA.id < charB.id ? -1 : 1));
|
||||
|
||||
return characterGroups.value.find(
|
||||
(character) => character.id === item.object_uuid,
|
||||
) as CharacterGroup;
|
||||
}
|
||||
|
||||
function addObject(item: ClipUI) {
|
||||
const existingObject = objectGroup.value.objects.find(
|
||||
(obj) => obj.object_uuid === item.object_uuid,
|
||||
);
|
||||
|
||||
if (existingObject) {
|
||||
return existingObject;
|
||||
}
|
||||
|
||||
const newObject = {
|
||||
object_uuid: item.object_uuid,
|
||||
name: item.object_name,
|
||||
keyframes: [] as Keyframe[],
|
||||
} as ObjectTrack;
|
||||
|
||||
objectGroup.value = {
|
||||
id: "OB1",
|
||||
objects: [
|
||||
...objectGroup.value.objects.filter(
|
||||
(obj) => obj.object_uuid !== item.object_uuid,
|
||||
),
|
||||
newObject,
|
||||
].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)),
|
||||
};
|
||||
|
||||
return objectGroup.value.objects.find(
|
||||
(obj) => obj.object_uuid === item.object_uuid,
|
||||
) as ObjectTrack;
|
||||
}
|
||||
const LOADING_FUNCTIONS: Record<ClipGroup, (item: ClipUI) => void> = {
|
||||
[ClipGroup.CHARACTER]: loadCharacterData,
|
||||
[ClipGroup.OBJECT]: loadObjectData,
|
||||
[ClipGroup.CAMERA]: loadCameraData,
|
||||
[ClipGroup.GLOBAL_AUDIO]: loadAudioData,
|
||||
};
|
||||
|
||||
export function useQueueHandler() {
|
||||
useSignals();
|
||||
const { addKeyframe } = useContext(TrackContext);
|
||||
const { addKeyframe, clearExistingData } = useContext(TrackContext);
|
||||
|
||||
const handleFromEngineActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log("FROM ENGINE", action, data);
|
||||
@@ -103,120 +46,13 @@ export function useQueueHandler() {
|
||||
currentTime.value = (data as UpdateTime).currentTime;
|
||||
break;
|
||||
case fromEngineActions.UPDATE_TIME_LINE:
|
||||
console.log(data);
|
||||
clearExistingData();
|
||||
(data as ClipUI[]).forEach((item) => {
|
||||
if (item.group === ClipGroup.CHARACTER) {
|
||||
const existingCharacter = addCharacter(item);
|
||||
if (item.type === ClipType.ANIMATION) {
|
||||
const newItem = {
|
||||
version: item.version,
|
||||
clip_uuid: item.clip_uuid,
|
||||
type: item.type,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
media_id: item.media_id,
|
||||
name: item.name,
|
||||
offset: item.offset,
|
||||
length: item.length,
|
||||
} as Clip;
|
||||
existingCharacter.animationClips.push(newItem);
|
||||
existingCharacter.animationClips.sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
);
|
||||
}
|
||||
if (item.type === ClipType.TRANSFORM) {
|
||||
const newKeyframe = {
|
||||
version: item.version,
|
||||
keyframe_uuid: item.clip_uuid,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
offset: item.keyframe_offset,
|
||||
} as Keyframe;
|
||||
existingCharacter.positionKeyframes.push(newKeyframe);
|
||||
existingCharacter.positionKeyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
}
|
||||
if (item.type === ClipType.AUDIO) {
|
||||
const newItem = {
|
||||
version: item.version,
|
||||
clip_uuid: item.clip_uuid,
|
||||
type: item.type,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
media_id: item.media_id,
|
||||
name: item.name,
|
||||
offset: item.offset,
|
||||
length: item.length,
|
||||
} as Clip;
|
||||
existingCharacter.lipSyncClips.push(newItem);
|
||||
existingCharacter.lipSyncClips.sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (item.group === ClipGroup.OBJECT) {
|
||||
const existingObject = addObject(item);
|
||||
const newKeyframe = {
|
||||
version: item.version,
|
||||
keyframe_uuid: item.clip_uuid,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
offset: item.keyframe_offset,
|
||||
} as Keyframe;
|
||||
existingObject.keyframes.push(newKeyframe);
|
||||
existingObject.keyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
}
|
||||
if (item.group === ClipGroup.CAMERA) {
|
||||
const existingCamera = cameraGroup.value;
|
||||
const newKeyframe = {
|
||||
version: item.version,
|
||||
keyframe_uuid: item.clip_uuid,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
offset: item.keyframe_offset,
|
||||
} as Keyframe;
|
||||
existingCamera.keyframes.push(newKeyframe);
|
||||
existingCamera.keyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
// cameraGroup.value = { ...cameraGroup.value };
|
||||
}
|
||||
if (item.group === ClipGroup.GLOBAL_AUDIO) {
|
||||
const existingAudio = audioGroup.value;
|
||||
const newItem = {
|
||||
version: item.version,
|
||||
clip_uuid: item.clip_uuid,
|
||||
type: item.type,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
media_id: item.media_id,
|
||||
name: item.name,
|
||||
offset: item.offset,
|
||||
length: item.length,
|
||||
} as Clip;
|
||||
existingAudio.clips.push(newItem);
|
||||
existingAudio.clips.sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
);
|
||||
}
|
||||
LOADING_FUNCTIONS[item.group](item);
|
||||
});
|
||||
break;
|
||||
case fromEngineActions.UPDATE_CHARACTER_ID: {
|
||||
const newCharacter = {
|
||||
id: (data as MediaItem).object_uuid,
|
||||
name: (data as MediaItem).name,
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionKeyframes: [],
|
||||
lipSyncClips: [],
|
||||
} as CharacterGroup;
|
||||
|
||||
characterGroups.value = [...characterGroups.value, newCharacter].sort(
|
||||
(charA, charB) => (charA.id < charB.id ? -1 : 1),
|
||||
);
|
||||
addNewCharacter(data as MediaItem);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -224,16 +60,19 @@ export function useQueueHandler() {
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleToTimelineActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log("TO TIMELINE", action, data);
|
||||
switch (action) {
|
||||
case toTimelineActions.ADD_KEYFRAME:
|
||||
addKeyframe(data as QueueKeyframe, currentTime.value);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action ${action}`);
|
||||
}
|
||||
}, []);
|
||||
const handleToTimelineActions = useCallback(
|
||||
({ action, data }: Arguments) => {
|
||||
console.log("TO TIMELINE", action, data);
|
||||
switch (action) {
|
||||
case toTimelineActions.ADD_KEYFRAME:
|
||||
addKeyframe(data as QueueKeyframe, currentTime.value);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown action ${action}`);
|
||||
}
|
||||
},
|
||||
[addKeyframe],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
Queue.subscribe(QueueNames.FROM_ENGINE, handleFromEngineActions);
|
||||
|
||||
@@ -6,6 +6,9 @@ export const TrackContext = createContext<{
|
||||
addKeyframe: (keyframe: QueueKeyframe, offset: number) => void;
|
||||
deleteKeyframe: (keyframe: Keyframe) => void;
|
||||
|
||||
// misc
|
||||
clearExistingData: () => void;
|
||||
|
||||
// drag and drop
|
||||
startDrag: (item: MediaItem) => void;
|
||||
endDrag: () => void;
|
||||
@@ -13,6 +16,8 @@ export const TrackContext = createContext<{
|
||||
addKeyframe: () => {},
|
||||
deleteKeyframe: () => {},
|
||||
|
||||
clearExistingData: () => {},
|
||||
|
||||
startDrag: () => {},
|
||||
endDrag: () => {},
|
||||
});
|
||||
@@ -12,6 +12,10 @@ import {
|
||||
dropId,
|
||||
dropOffset,
|
||||
addObject,
|
||||
characterGroups,
|
||||
cameraGroup,
|
||||
audioGroup,
|
||||
objectGroup,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe";
|
||||
|
||||
@@ -26,12 +30,9 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
|
||||
// cross group functions
|
||||
const dropClip = useCallback(() => {
|
||||
console.log(`${JSON.stringify(dragItem)}`);
|
||||
|
||||
if (dragItem.value != null && dragItem != null) {
|
||||
// should be able to drag into the timeline ... TODO FIX?
|
||||
|
||||
if (dragItem.value.type === AssetType.CHARACTER) {
|
||||
if (dragItem.value) {
|
||||
const mediaItem = dragItem.value;
|
||||
if (mediaItem.type === AssetType.CHARACTER) {
|
||||
addCharacter(dragItem.value);
|
||||
}
|
||||
// if (dragItem.value.type === AssetType.CAMERA) {
|
||||
@@ -48,7 +49,7 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
if (canDrop.value && dragItem.value) {
|
||||
if (dragItem.value.type === AssetType.ANIMATION) {
|
||||
addCharacterAnimation({
|
||||
dragItem: dragItem.value!,
|
||||
dragItem: dragItem.value,
|
||||
characterId: dropId.value,
|
||||
offset: dropOffset.value,
|
||||
});
|
||||
@@ -56,12 +57,12 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
if (dragItem.value.type === AssetType.AUDIO) {
|
||||
console.log("add audio", dropId.value);
|
||||
addCharacterAudio({
|
||||
dragItem: dragItem.value!,
|
||||
dragItem: dragItem.value,
|
||||
characterId: dropId.value,
|
||||
offset: dropOffset.value,
|
||||
});
|
||||
addGlobalAudio({
|
||||
dragItem: dragItem.value!,
|
||||
dragItem: dragItem.value,
|
||||
audioId: dropId.value,
|
||||
offset: dropOffset.value,
|
||||
});
|
||||
@@ -70,14 +71,34 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
endDrag();
|
||||
}, [endDrag]);
|
||||
|
||||
const clearExistingData = useCallback(() => {
|
||||
characterGroups.value = [];
|
||||
cameraGroup.value = {
|
||||
id: "CG1",
|
||||
keyframes: [],
|
||||
};
|
||||
audioGroup.value = {
|
||||
id: "AG-1",
|
||||
clips: [],
|
||||
muted: false,
|
||||
};
|
||||
objectGroup.value = {
|
||||
id: "OG1",
|
||||
objects: [],
|
||||
};
|
||||
}, []);
|
||||
|
||||
const values = useMemo(() => {
|
||||
return {
|
||||
...keyframes,
|
||||
|
||||
clearExistingData,
|
||||
|
||||
...dragDrop,
|
||||
endDrag: dropClip,
|
||||
};
|
||||
}, [keyframes, dragDrop, dropClip]);
|
||||
}, [keyframes, dragDrop, dropClip, clearExistingData]);
|
||||
|
||||
return (
|
||||
<TrackContext.Provider value={values}>{children}</TrackContext.Provider>
|
||||
);
|
||||
|
||||
@@ -311,7 +311,7 @@ class Editor {
|
||||
|
||||
this.timeline.scene = this.activeScene;
|
||||
|
||||
//this._test_demo();
|
||||
this._test_demo();
|
||||
|
||||
this.renderer.domElement.addEventListener(
|
||||
"mousedown",
|
||||
@@ -1076,6 +1076,7 @@ class Editor {
|
||||
this.raycaster.setFromCamera(this.mouse, this.camera);
|
||||
const interactable: any[] = [];
|
||||
this.activeScene.scene.children.forEach((child: THREE.Object3D) => {
|
||||
// console.log(child);
|
||||
if (child.name != "") {
|
||||
if (child.type == "Mesh" || child.type == "Object3D" || child.type == "Group") {
|
||||
interactable.push(child);
|
||||
|
||||
@@ -10,6 +10,7 @@ import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
import { signal } from "@preact/signals-core";
|
||||
import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui";
|
||||
|
||||
export const audioGroup = signal<AudioGroup>({
|
||||
id: "AG-1",
|
||||
@@ -140,3 +141,20 @@ export function deleteAudioClip(clip: Clip) {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function loadAudioData(item: ClipUI) {
|
||||
const existingAudio = audioGroup.value;
|
||||
const newItem = {
|
||||
version: item.version,
|
||||
clip_uuid: item.clip_uuid,
|
||||
type: item.type,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
media_id: item.media_id,
|
||||
name: item.name,
|
||||
offset: item.offset,
|
||||
length: item.length,
|
||||
} as Clip;
|
||||
existingAudio.clips.push(newItem);
|
||||
existingAudio.clips.sort((clipA, clipB) => clipA.offset - clipB.offset);
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
import { signal } from "@preact/signals-core";
|
||||
import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui";
|
||||
|
||||
export const cameraGroup = signal<CameraGroup>({ id: "CG1", keyframes: [] });
|
||||
|
||||
@@ -50,10 +51,14 @@ export function updateCamera({
|
||||
};
|
||||
}
|
||||
|
||||
export function addCameraKeyframe(keyframe: QueueKeyframe, offset: number) {
|
||||
export function addCameraKeyframe(
|
||||
keyframe: QueueKeyframe,
|
||||
offset: number,
|
||||
addToast: (type: "error" | "warning" | "success", message: string) => void,
|
||||
) {
|
||||
const oldCameraGroup = cameraGroup.value;
|
||||
if (oldCameraGroup.keyframes.some((row) => row.offset === offset)) {
|
||||
//toast.error("There can only be one keyframe at this offset.");
|
||||
addToast("warning", "There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
const newKeyframe = {
|
||||
@@ -119,3 +124,18 @@ export function deleteCameraKeyframe(deleteKeyframe: Keyframe) {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function loadCameraData(item: ClipUI) {
|
||||
const existingCamera = cameraGroup.value;
|
||||
const newKeyframe = {
|
||||
version: item.version,
|
||||
keyframe_uuid: item.clip_uuid,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
offset: item.keyframe_offset,
|
||||
} as Keyframe;
|
||||
existingCamera.keyframes.push(newKeyframe);
|
||||
existingCamera.keyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { CharacterGroup, MediaItem } from "~/pages/PageEnigma/models";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { characterGroups } from "~/pages/PageEnigma/store";
|
||||
|
||||
export function addCharacter(character: MediaItem) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CHARACTER,
|
||||
data: character,
|
||||
});
|
||||
}
|
||||
|
||||
export function addNewCharacter(data: MediaItem) {
|
||||
const newCharacter = {
|
||||
id: data.object_uuid,
|
||||
name: data.name,
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionKeyframes: [],
|
||||
lipSyncClips: [],
|
||||
} as CharacterGroup;
|
||||
|
||||
characterGroups.value = [...characterGroups.value, newCharacter].sort(
|
||||
(charA, charB) => (charA.id < charB.id ? -1 : 1),
|
||||
);
|
||||
}
|
||||
@@ -15,95 +15,6 @@ import * as uuid from "uuid";
|
||||
|
||||
export const characterGroups = signal<CharacterGroup[]>([]);
|
||||
|
||||
export function updateCharacters({
|
||||
type,
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
}: {
|
||||
type: ClipType;
|
||||
id: string;
|
||||
length?: number;
|
||||
offset: number;
|
||||
}) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
if (type === ClipType.ANIMATION) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newAnimationClips = [...character.animationClips];
|
||||
const clipIndex = newAnimationClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newAnimationClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length!;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
animationClips: newAnimationClips,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (type === ClipType.TRANSFORM) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newPositionKeyframes = [...character.positionKeyframes];
|
||||
const keyframeIndex = newPositionKeyframes.findIndex(
|
||||
(row) => row.keyframe_uuid === id,
|
||||
);
|
||||
if (keyframeIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const keyframe = newPositionKeyframes[keyframeIndex];
|
||||
keyframe.offset = offset;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: keyframe,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
positionKeyframes: newPositionKeyframes,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (type === ClipType.AUDIO) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newLipSyncClips = [...character.lipSyncClips];
|
||||
const clipIndex = newLipSyncClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newLipSyncClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length!;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: newLipSyncClips,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function addCharacterAnimation({
|
||||
dragItem,
|
||||
characterId,
|
||||
@@ -331,25 +242,3 @@ export function deleteCharacterKeyframe(keyframe: Keyframe) {
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
// probably not to much to send imo
|
||||
export function addCharacter(character: MediaItem) {
|
||||
//{"version":1,"media_id":"m_r7w1tmkx2jg8nznr3hyzj4k6zhfh7d ",
|
||||
// "type":"character","name":"Female Doll",
|
||||
// "thumbnail":"resources/characters/img03.png"}
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CHARACTER,
|
||||
data: character,
|
||||
});
|
||||
}
|
||||
export function addObject(object: MediaItem) {
|
||||
//{"version":1,"media_id":"m_r7w1tmkx2jg8nznr3hyzj4k6zhfh7d ",
|
||||
// "type":"character","name":"Female Doll",
|
||||
// "thumbnail":"resources/characters/img03.png"}
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_OBJECT,
|
||||
data: object,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./addCharacter";
|
||||
export * from "./characterGroups";
|
||||
export * from "./loadCharacterData";
|
||||
export * from "./updateCharacters";
|
||||
@@ -0,0 +1,89 @@
|
||||
import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui";
|
||||
import {
|
||||
CharacterGroup,
|
||||
Clip,
|
||||
ClipType,
|
||||
Keyframe,
|
||||
} from "~/pages/PageEnigma/models";
|
||||
import { characterGroups } from "~/pages/PageEnigma/store";
|
||||
|
||||
function getAddCharacter(item: ClipUI) {
|
||||
const existingCharacter = characterGroups.value.find(
|
||||
(character) => character.id === item.object_uuid,
|
||||
);
|
||||
|
||||
if (existingCharacter) {
|
||||
return existingCharacter;
|
||||
}
|
||||
|
||||
const newCharacter = {
|
||||
id: item.object_uuid,
|
||||
name: item.object_name,
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionKeyframes: [],
|
||||
lipSyncClips: [],
|
||||
} as CharacterGroup;
|
||||
|
||||
characterGroups.value = [
|
||||
...characterGroups.value.filter(
|
||||
(character) => character.id !== item.object_uuid,
|
||||
),
|
||||
newCharacter,
|
||||
].sort((charA, charB) => (charA.id < charB.id ? -1 : 1));
|
||||
|
||||
return characterGroups.value.find(
|
||||
(character) => character.id === item.object_uuid,
|
||||
) as CharacterGroup;
|
||||
}
|
||||
|
||||
export function loadCharacterData(item: ClipUI) {
|
||||
const existingCharacter = getAddCharacter(item);
|
||||
if (item.type === ClipType.ANIMATION) {
|
||||
const newItem = {
|
||||
version: item.version,
|
||||
clip_uuid: item.clip_uuid,
|
||||
type: item.type,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
media_id: item.media_id,
|
||||
name: item.name,
|
||||
offset: item.offset,
|
||||
length: item.length,
|
||||
} as Clip;
|
||||
existingCharacter.animationClips.push(newItem);
|
||||
existingCharacter.animationClips.sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
);
|
||||
}
|
||||
if (item.type === ClipType.TRANSFORM) {
|
||||
const newKeyframe = {
|
||||
version: item.version,
|
||||
keyframe_uuid: item.clip_uuid,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
offset: item.keyframe_offset,
|
||||
} as Keyframe;
|
||||
existingCharacter.positionKeyframes.push(newKeyframe);
|
||||
existingCharacter.positionKeyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
}
|
||||
if (item.type === ClipType.AUDIO) {
|
||||
const newItem = {
|
||||
version: item.version,
|
||||
clip_uuid: item.clip_uuid,
|
||||
type: item.type,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
media_id: item.media_id,
|
||||
name: item.name,
|
||||
offset: item.offset,
|
||||
length: item.length,
|
||||
} as Clip;
|
||||
existingCharacter.lipSyncClips.push(newItem);
|
||||
existingCharacter.lipSyncClips.sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import { ClipType } from "~/pages/PageEnigma/models";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { characterGroups } from "~/pages/PageEnigma/store";
|
||||
|
||||
export function updateCharacters({
|
||||
type,
|
||||
id,
|
||||
offset,
|
||||
length,
|
||||
}: {
|
||||
type: ClipType;
|
||||
id: string;
|
||||
length?: number;
|
||||
offset: number;
|
||||
}) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
if (type === ClipType.ANIMATION) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newAnimationClips = [...character.animationClips];
|
||||
const clipIndex = newAnimationClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newAnimationClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length!;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
animationClips: newAnimationClips,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
if (type === ClipType.TRANSFORM) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newPositionKeyframes = [...character.positionKeyframes];
|
||||
const keyframeIndex = newPositionKeyframes.findIndex(
|
||||
(row) => row.keyframe_uuid === id,
|
||||
);
|
||||
if (keyframeIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const keyframe = newPositionKeyframes[keyframeIndex];
|
||||
keyframe.offset = offset;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: keyframe,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
positionKeyframes: newPositionKeyframes,
|
||||
};
|
||||
});
|
||||
}
|
||||
if (type === ClipType.AUDIO) {
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
const newLipSyncClips = [...character.lipSyncClips];
|
||||
const clipIndex = newLipSyncClips.findIndex(
|
||||
(row) => row.clip_uuid === id,
|
||||
);
|
||||
if (clipIndex === -1) {
|
||||
return { ...character };
|
||||
}
|
||||
const clip = newLipSyncClips[clipIndex];
|
||||
clip.offset = offset;
|
||||
clip.length = length!;
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: newLipSyncClips,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from "./loadObjectData";
|
||||
export * from "./objectGroup";
|
||||
export * from "./updateObjectGroup";
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui";
|
||||
import { Keyframe, ObjectTrack } from "~/pages/PageEnigma/models";
|
||||
import { objectGroup } from "~/pages/PageEnigma/store";
|
||||
|
||||
function getAddObject(item: ClipUI) {
|
||||
const existingObject = objectGroup.value.objects.find(
|
||||
(obj) => obj.object_uuid === item.object_uuid,
|
||||
);
|
||||
|
||||
if (existingObject) {
|
||||
return existingObject;
|
||||
}
|
||||
|
||||
const newObject = {
|
||||
object_uuid: item.object_uuid,
|
||||
name: item.object_name,
|
||||
keyframes: [] as Keyframe[],
|
||||
} as ObjectTrack;
|
||||
|
||||
objectGroup.value = {
|
||||
id: "OB1",
|
||||
objects: [
|
||||
...objectGroup.value.objects.filter(
|
||||
(obj) => obj.object_uuid !== item.object_uuid,
|
||||
),
|
||||
newObject,
|
||||
].sort((objA, objB) => (objA.object_uuid < objB.object_uuid ? -1 : 1)),
|
||||
};
|
||||
|
||||
return objectGroup.value.objects.find(
|
||||
(obj) => obj.object_uuid === item.object_uuid,
|
||||
) as ObjectTrack;
|
||||
}
|
||||
|
||||
export function loadObjectData(item: ClipUI) {
|
||||
const existingObject = getAddObject(item);
|
||||
const newKeyframe = {
|
||||
version: item.version,
|
||||
keyframe_uuid: item.clip_uuid,
|
||||
group: item.group,
|
||||
object_uuid: item.object_uuid,
|
||||
offset: item.keyframe_offset,
|
||||
} as Keyframe;
|
||||
existingObject.keyframes.push(newKeyframe);
|
||||
existingObject.keyframes.sort(
|
||||
(keyframeA, keyframeB) => keyframeA.offset - keyframeB.offset,
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
import {
|
||||
Keyframe,
|
||||
MediaItem,
|
||||
ObjectGroup,
|
||||
QueueKeyframe,
|
||||
} from "~/pages/PageEnigma/models";
|
||||
@@ -8,70 +9,12 @@ import * as uuid from "uuid";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
// import { toast } from "react-hot-toast";
|
||||
|
||||
export const objectGroup = signal<ObjectGroup>({
|
||||
id: "OB1",
|
||||
objects: [],
|
||||
});
|
||||
|
||||
export function updateObject({
|
||||
id,
|
||||
offset,
|
||||
addToast,
|
||||
}: {
|
||||
id: string;
|
||||
offset: number;
|
||||
addToast: (type: "error" | "warning" | "success", message: string) => void;
|
||||
}): void {
|
||||
const oldObjectGroup = objectGroup.value;
|
||||
const obj = oldObjectGroup.objects.find((objectTrack) =>
|
||||
objectTrack.keyframes.some((row) => row.keyframe_uuid === id),
|
||||
);
|
||||
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingKeyframe = obj.keyframes.some((row) => {
|
||||
return row.offset === offset && row.keyframe_uuid !== id;
|
||||
});
|
||||
|
||||
if (existingKeyframe) {
|
||||
addToast("warning", "There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
|
||||
objectGroup.value = {
|
||||
id: oldObjectGroup.id,
|
||||
objects: oldObjectGroup.objects.map((object) => ({
|
||||
object_uuid: object.object_uuid,
|
||||
name: object.name,
|
||||
keyframes: object.keyframes.map((keyframe) => {
|
||||
if (keyframe.keyframe_uuid !== id) {
|
||||
return {
|
||||
...keyframe,
|
||||
};
|
||||
}
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_KEYFRAME,
|
||||
data: {
|
||||
...keyframe,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...keyframe,
|
||||
offset,
|
||||
};
|
||||
}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function addObjectKeyframe(
|
||||
keyframe: QueueKeyframe,
|
||||
offset: number,
|
||||
@@ -172,3 +115,11 @@ export function deleteObjectKeyframe(keyframe: Keyframe) {
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function addObject(object: MediaItem) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_OBJECT,
|
||||
data: object,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { objectGroup } from "~/pages/PageEnigma/store";
|
||||
|
||||
export function updateObject({
|
||||
id,
|
||||
offset,
|
||||
}: {
|
||||
id: string;
|
||||
offset: number;
|
||||
}): void {
|
||||
const oldObjectGroup = objectGroup.value;
|
||||
const obj = oldObjectGroup.objects.find((objectTrack) =>
|
||||
objectTrack.keyframes.some((row) => row.keyframe_uuid === id),
|
||||
);
|
||||
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
|
||||
const existingKeyframe = obj.keyframes.some((row) => {
|
||||
return row.offset === offset && row.keyframe_uuid !== id;
|
||||
});
|
||||
|
||||
if (existingKeyframe) {
|
||||
//toast.error("There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
|
||||
objectGroup.value = {
|
||||
id: oldObjectGroup.id,
|
||||
objects: oldObjectGroup.objects.map((object) => ({
|
||||
object_uuid: object.object_uuid,
|
||||
name: object.name,
|
||||
keyframes: object.keyframes.map((keyframe) => {
|
||||
if (keyframe.keyframe_uuid !== id) {
|
||||
return {
|
||||
...keyframe,
|
||||
};
|
||||
}
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_KEYFRAME,
|
||||
data: {
|
||||
...keyframe,
|
||||
offset,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
...keyframe,
|
||||
offset,
|
||||
};
|
||||
}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -1,3 +1,17 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@layer components {
|
||||
.filter-tab {
|
||||
@apply text-nowrap rounded-lg border-2 border-assets-background bg-assets-background px-3 py-2 text-sm font-medium transition-all duration-150 hover:border-assets-background/0 hover:bg-assets-background/80;
|
||||
}
|
||||
|
||||
.filter-tab.active {
|
||||
@apply border-2 border-brand-primary hover:border-brand-primary hover:bg-assets-background;
|
||||
}
|
||||
|
||||
.filter-tab.disabled {
|
||||
@apply hover:bg-assets-background disabled:text-white/50;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"terser": "^5.30.3",
|
||||
"three": "^0.162.0",
|
||||
"uuid": "^9.0.1",
|
||||
"webm-writer": "^1.0.0"
|
||||
@@ -56,6 +57,7 @@
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.12",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"terser": "^5.30.3",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^5.0.0",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
@@ -1494,14 +1496,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz",
|
||||
"integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==",
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/set-array": "^1.0.1",
|
||||
"@jridgewell/set-array": "^1.2.1",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10",
|
||||
"@jridgewell/trace-mapping": "^0.3.9"
|
||||
"@jridgewell/trace-mapping": "^0.3.24"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
@@ -1517,14 +1519,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/set-array": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
|
||||
"integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/source-map": {
|
||||
"version": "0.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
|
||||
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/gen-mapping": "^0.3.5",
|
||||
"@jridgewell/trace-mapping": "^0.3.25"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.4.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
@@ -1532,9 +1544,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.22",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz",
|
||||
"integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==",
|
||||
"version": "0.3.25",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@@ -11975,6 +11987,30 @@
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.30.3",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.30.3.tgz",
|
||||
"integrity": "sha512-STdUgOUx8rLbMGO9IOwHLpCqolkDITFFQSMYYwKE1N2lY6MVSaeoi10z/EhWxRc6ybqoVmKSkhKYH/XUpl7vSA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
"acorn": "^8.8.2",
|
||||
"commander": "^2.20.0",
|
||||
"source-map-support": "~0.5.20"
|
||||
},
|
||||
"bin": {
|
||||
"terser": "bin/terser"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/terser/node_modules/commander": {
|
||||
"version": "2.20.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
|
||||
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"scripts": {
|
||||
"build": "remix vite:build",
|
||||
"dev": "remix vite:dev",
|
||||
"staging-build": "remix vite:build",
|
||||
"lint": "eslint --ignore-path .gitignore --cache --cache-location ./node_modules/.cache/eslint .",
|
||||
"start": "netlify serve",
|
||||
"typecheck": "tsc",
|
||||
@@ -36,6 +37,7 @@
|
||||
"react-cookie": "^7.1.4",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwind-merge": "^2.2.2",
|
||||
"terser": "^5.30.3",
|
||||
"three": "^0.162.0",
|
||||
"uuid": "^9.0.1",
|
||||
"webm-writer": "^1.0.0"
|
||||
@@ -62,6 +64,7 @@
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-plugin-tailwindcss": "^0.5.12",
|
||||
"tailwindcss": "^3.4.1",
|
||||
"terser": "^5.30.3",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^5.0.0",
|
||||
"vite-tsconfig-paths": "^4.2.1"
|
||||
|
||||
|
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 657 B |
|
Before Width: | Height: | Size: 562 B After Width: | Height: | Size: 608 B |
|
Before Width: | Height: | Size: 617 B After Width: | Height: | Size: 666 B |
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 723 B |
|
Before Width: | Height: | Size: 610 B After Width: | Height: | Size: 631 B |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 542 B |
@@ -94,6 +94,22 @@ export default {
|
||||
"950": "#0c270f",
|
||||
},
|
||||
|
||||
action: {
|
||||
//comet
|
||||
DEFAULT: "#55556b", //600
|
||||
"50": "#f7f7f8",
|
||||
"100": "#eeedf1",
|
||||
"200": "#d8d8df",
|
||||
"300": "#b5b5c4",
|
||||
"400": "#8d8da3",
|
||||
"500": "#6f7088",
|
||||
"600": "#55556b",
|
||||
"700": "#49495b",
|
||||
"800": "#3f3f4d",
|
||||
"900": "#373743",
|
||||
"950": "#26252c",
|
||||
},
|
||||
|
||||
character: {
|
||||
unselected: "#46527C",
|
||||
selected: "#6384F4",
|
||||
|
||||
@@ -19,4 +19,12 @@ export default defineConfig({
|
||||
});
|
||||
},
|
||||
},],
|
||||
build: {
|
||||
minify: "terser",
|
||||
},
|
||||
terserOptions: {
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
},
|
||||
});
|
||||