mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 09:28:11 +00:00
Merge pull request #154 from storytold/bug/load-characters
Bug/load characters
This commit is contained in:
36 files changed
+926
-719
No files matched your search
@@ -10,15 +10,18 @@ import {
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { deleteInferenceJob, inferenceJobs } from "~/pages/PageEnigma/store/inferenceJobs";
|
||||
import {
|
||||
deleteInferenceJob,
|
||||
inferenceJobs,
|
||||
} from "~/pages/PageEnigma/store/inferenceJobs";
|
||||
import { InferenceJob } from "~/pages/PageEnigma/models";
|
||||
|
||||
import { ButtonDialogue } from "../ButtonDialogue";
|
||||
import { ButtonIcon, H4,H6,P } from "~/components";
|
||||
import { ButtonIcon, H4, H6, P } from "~/components";
|
||||
import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager";
|
||||
|
||||
const printJobType = (jobType : string)=>{
|
||||
switch (jobType){
|
||||
const printJobType = (jobType: string) => {
|
||||
switch (jobType) {
|
||||
case "generic":
|
||||
return "Text to Speech";
|
||||
default:
|
||||
@@ -26,23 +29,23 @@ const printJobType = (jobType : string)=>{
|
||||
}
|
||||
};
|
||||
|
||||
const printJobStatus = (jobStatus : string)=>{
|
||||
switch (jobStatus){
|
||||
const printJobStatus = (jobStatus: string) => {
|
||||
switch (jobStatus) {
|
||||
case JobState.PENDING:
|
||||
return "Adding to the queue.";
|
||||
case JobState.STARTED:
|
||||
return "Processing, please wait...";
|
||||
case JobState.COMPLETE_SUCCESS:
|
||||
return "Completed! See Audio Panel for the result. file."
|
||||
return "Completed! See Audio Panel for the result. file.";
|
||||
default:
|
||||
return "Unknown Status";
|
||||
}
|
||||
};
|
||||
|
||||
const JobStatusIcon = ({jobStatus} : {jobStatus:string})=>{
|
||||
switch (jobStatus){
|
||||
case JobState.PENDING:{
|
||||
return(
|
||||
const JobStatusIcon = ({ jobStatus }: { jobStatus: string }) => {
|
||||
switch (jobStatus) {
|
||||
case JobState.PENDING: {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
className="col-span-1 animate-spin"
|
||||
icon={faSpinner}
|
||||
@@ -50,17 +53,17 @@ const JobStatusIcon = ({jobStatus} : {jobStatus:string})=>{
|
||||
/>
|
||||
);
|
||||
}
|
||||
case JobState.STARTED:{
|
||||
return(
|
||||
<FontAwesomeIcon
|
||||
case JobState.STARTED: {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
className="col-span-1 animate-spin"
|
||||
icon={faLoader}
|
||||
size="2xl"
|
||||
/>
|
||||
);
|
||||
}
|
||||
case JobState.COMPLETE_SUCCESS:{
|
||||
return(
|
||||
case JobState.COMPLETE_SUCCESS: {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
className="col-span-1"
|
||||
icon={faCircleCheck}
|
||||
@@ -68,8 +71,8 @@ const JobStatusIcon = ({jobStatus} : {jobStatus:string})=>{
|
||||
/>
|
||||
);
|
||||
}
|
||||
default:{
|
||||
return(
|
||||
default: {
|
||||
return (
|
||||
<FontAwesomeIcon
|
||||
className="col-span-1"
|
||||
icon={faCircleExclamation}
|
||||
@@ -80,62 +83,56 @@ const JobStatusIcon = ({jobStatus} : {jobStatus:string})=>{
|
||||
}
|
||||
};
|
||||
|
||||
const InferenceItem = ({job}:{job:InferenceJob}) => {
|
||||
// console.log(job);
|
||||
return(
|
||||
<li
|
||||
className="grid grid-cols-12 gap-2 items-center bg-brand-secondary rounded-md py-2 px-4"
|
||||
>
|
||||
const InferenceItem = ({ job }: { job: InferenceJob }) => {
|
||||
return (
|
||||
<li className="grid grid-cols-12 items-center gap-2 rounded-md bg-brand-secondary px-4 py-2">
|
||||
<JobStatusIcon jobStatus={job.job_status} />
|
||||
<div className="col-span-10 flex flex-col">
|
||||
<H4>{printJobType(job.job_type)}</H4>
|
||||
<H6>{printJobStatus(job.job_status)} </H6>
|
||||
<H4>{printJobType(job.job_type)}</H4>
|
||||
<H6>{printJobStatus(job.job_status)} </H6>
|
||||
</div>
|
||||
{job.job_status === JobState.COMPLETE_SUCCESS &&
|
||||
{job.job_status === JobState.COMPLETE_SUCCESS && (
|
||||
<div className="col-span-1 flex justify-end">
|
||||
<ButtonIcon
|
||||
icon={faSquareXmark}
|
||||
size="2xl"
|
||||
onClick={()=>{
|
||||
onClick={() => {
|
||||
deleteInferenceJob(job);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
};
|
||||
|
||||
export const DialogueInference = () =>{
|
||||
export const DialogueInference = () => {
|
||||
useSignals();
|
||||
|
||||
const buttonIcon = inferenceJobs.value.length > 0
|
||||
? faClipboardList : faClipboard;
|
||||
return(
|
||||
const buttonIcon =
|
||||
inferenceJobs.value.length > 0 ? faClipboardList : faClipboard;
|
||||
return (
|
||||
<ButtonDialogue
|
||||
title="My Jobs"
|
||||
dialogProps={{
|
||||
className:"w-1/2 max-w-full"
|
||||
className: "w-1/2 max-w-full",
|
||||
}}
|
||||
buttonProps={{
|
||||
icon: buttonIcon,
|
||||
variant: "secondary",
|
||||
label: "My Jobs",
|
||||
className:( inferenceJobs.value.length === 0 ? "invisible" : "visible")
|
||||
}}
|
||||
>
|
||||
{inferenceJobs.value.length > 0 &&
|
||||
className: inferenceJobs.value.length === 0 ? "invisible" : "visible",
|
||||
}}>
|
||||
{inferenceJobs.value.length > 0 && (
|
||||
<ul className="flex flex-col gap-2">
|
||||
{inferenceJobs.value.map(job=>
|
||||
{inferenceJobs.value.map((job) => (
|
||||
<InferenceItem key={job.job_id} job={job} />
|
||||
)}
|
||||
))}
|
||||
</ul>
|
||||
}
|
||||
{
|
||||
inferenceJobs.value.length === 0 &&
|
||||
)}
|
||||
{inferenceJobs.value.length === 0 && (
|
||||
<P>You currently have no running proccesses.</P>
|
||||
}
|
||||
)}
|
||||
</ButtonDialogue>
|
||||
);
|
||||
}
|
||||
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, UIEvent } from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import { useMouseEventsTimeline } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsTimeline";
|
||||
import {
|
||||
currentScroll,
|
||||
timelineScrollX,
|
||||
currentTime,
|
||||
dndTimelineHeight,
|
||||
overTimeline,
|
||||
@@ -29,7 +29,7 @@ export const LowerPanel = ({ children, onStyle }: LowerPanelPropsI) => {
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0) {
|
||||
const newTime = Math.round(
|
||||
(event.clientX + currentScroll.value - 92) / 4 / scale.value,
|
||||
(event.clientX + timelineScrollX.value - 92) / 4 / scale.value,
|
||||
);
|
||||
if (newTime < 0) {
|
||||
return;
|
||||
@@ -45,10 +45,6 @@ export const LowerPanel = ({ children, onStyle }: LowerPanelPropsI) => {
|
||||
[],
|
||||
);
|
||||
|
||||
const onScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
currentScroll.value = event.currentTarget.scrollLeft;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{!onStyle && (
|
||||
@@ -59,19 +55,13 @@ export const LowerPanel = ({ children, onStyle }: LowerPanelPropsI) => {
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
className={[
|
||||
"absolute bottom-0",
|
||||
"w-screen",
|
||||
onStyle ? "overflow-x-auto overflow-y-hidden" : "overflow-auto",
|
||||
"bg-ui-panel",
|
||||
].join(" ")}
|
||||
className={["absolute bottom-0", "w-screen", "bg-ui-panel"].join(" ")}
|
||||
style={{ height: onStyle ? 80 : displayHeight }}
|
||||
onPointerOver={() => {
|
||||
overTimeline.value = true;
|
||||
}}
|
||||
onPointerLeave={() => (overTimeline.value = false)}
|
||||
onPointerDown={onTimelineClick}
|
||||
onScroll={onScroll}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { useContext } from "react";
|
||||
import { useParams } from "@remix-run/react";
|
||||
|
||||
import { LoadingBar, LoadingDots } from "~/components";
|
||||
import { SidePanel } from "~/modules/SidePanel";
|
||||
@@ -57,8 +56,7 @@ export const PageEditor = ({ setPage }: Props) => {
|
||||
<TopBar pageName="Edit Scene" />
|
||||
<div
|
||||
className="relative flex w-screen"
|
||||
style={{ height: "calc(100vh - 68px)" }}
|
||||
>
|
||||
style={{ height: "calc(100vh - 68px)" }}>
|
||||
{/* Engine section/side panel */}
|
||||
<div
|
||||
id="engine-n-panels-wrapper"
|
||||
@@ -66,23 +64,20 @@ export const PageEditor = ({ setPage }: Props) => {
|
||||
style={{
|
||||
height,
|
||||
width,
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<div className="relative w-full overflow-hidden bg-transparent">
|
||||
<div
|
||||
className={
|
||||
appUiState.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "invisible"
|
||||
: ""
|
||||
}
|
||||
>
|
||||
}>
|
||||
<canvas id="video-scene" width="1280px" height="720px" />
|
||||
|
||||
{/* Top controls */}
|
||||
<div
|
||||
className="absolute left-0 top-0 w-full"
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
onClick={handleOverlayClick}>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<ControlsTopButtons />
|
||||
<Controls3D setPage={setPage} />
|
||||
@@ -98,8 +93,7 @@ export const PageEditor = ({ setPage }: Props) => {
|
||||
(sidePanelVisible.value ? sidePanelWidth.value : 0) -
|
||||
84,
|
||||
}}
|
||||
onClick={handleOverlayClick}
|
||||
>
|
||||
onClick={handleOverlayClick}>
|
||||
<PreviewEngineCamera />
|
||||
<ControlsVideo />
|
||||
<ControlPanelSceneObject />
|
||||
|
||||
@@ -7,7 +7,6 @@ import { PageEditor } from "~/pages/PageEnigma/PageEditor";
|
||||
import { PageStyling } from "~/pages/PageEnigma/PageStyling";
|
||||
import { useParams } from "@remix-run/react";
|
||||
|
||||
|
||||
export const PageEnigmaComponent = () => {
|
||||
useSignals();
|
||||
|
||||
@@ -15,16 +14,19 @@ export const PageEnigmaComponent = () => {
|
||||
const editor = useContext(EngineContext);
|
||||
const [page, setPage] = useState("edit");
|
||||
const params = useParams();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (editor && editor.can_initialize && dispatchAppUiState !== null) {
|
||||
console.log("initializing Editor");
|
||||
|
||||
const sceneToken = params["sceneToken"]
|
||||
|
||||
editor.initialize({
|
||||
dispatchAppUiState
|
||||
},sceneToken);
|
||||
|
||||
const sceneToken = params["sceneToken"];
|
||||
|
||||
editor.initialize(
|
||||
{
|
||||
dispatchAppUiState,
|
||||
},
|
||||
sceneToken,
|
||||
);
|
||||
}
|
||||
}, [editor, dispatchAppUiState]);
|
||||
|
||||
|
||||
@@ -5,15 +5,23 @@ import {
|
||||
faArrowsRotate,
|
||||
faFilm,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button,LoadingBar } from "~/components";
|
||||
import { Button, LoadingBar } from "~/components";
|
||||
import { StyleSelection } from "~/pages/PageEnigma/comps/StyleSelection";
|
||||
import { LowerPanel } from "~/modules/LowerPanel";
|
||||
import { TimerGrid } from "~/pages/PageEnigma/comps/TimerGrid/TimerGrid";
|
||||
import { TimerGrid } from "~/pages/PageEnigma/comps/Timeline/TimerGrid";
|
||||
import { Scrubber } from "~/pages/PageEnigma/comps/Timeline/Scrubber";
|
||||
import { PreviewImages } from "~/pages/PageEnigma/comps/PreviewImages";
|
||||
import { TopBar } from "~/modules/TopBar";
|
||||
import { useContext } from "react";
|
||||
import React, { UIEvent, useCallback, useContext } from "react";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import {
|
||||
currentTime,
|
||||
filmLength,
|
||||
scale,
|
||||
timelineScrollX,
|
||||
} 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";
|
||||
|
||||
interface Props {
|
||||
setPage: (page: string) => void;
|
||||
@@ -37,6 +45,30 @@ export const PageStyling = ({ setPage }: Props) => {
|
||||
setPage("edit");
|
||||
};
|
||||
|
||||
const onScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
timelineScrollX.value = event.currentTarget.scrollLeft;
|
||||
}, []);
|
||||
|
||||
const onTimelineClick = useCallback(
|
||||
(event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (event.button === 0) {
|
||||
const newTime = Math.round(
|
||||
(event.clientX + timelineScrollX.value - 92) / 4 / scale.value,
|
||||
);
|
||||
if (newTime < 0) {
|
||||
return;
|
||||
}
|
||||
currentTime.value = newTime;
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_TIME,
|
||||
data: { currentTime: Math.round(newTime) },
|
||||
});
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="w-screen">
|
||||
<TopBar pageName="Stylization" />
|
||||
@@ -56,8 +88,7 @@ export const PageStyling = ({ setPage }: Props) => {
|
||||
<Button
|
||||
icon={faArrowsRotate}
|
||||
variant="action"
|
||||
onClick={generateFrame}
|
||||
>
|
||||
onClick={generateFrame}>
|
||||
Update Preview
|
||||
</Button>
|
||||
<Button icon={faFilm} variant="primary" onClick={generateMovie}>
|
||||
@@ -68,21 +99,28 @@ export const PageStyling = ({ setPage }: Props) => {
|
||||
</div>
|
||||
<div className="fixed bottom-0 left-0 w-full">
|
||||
<div className="relative flex h-[80px] w-full gap-5 border-t border-t-action-600 bg-ui-panel">
|
||||
<LowerPanel onStyle>
|
||||
<TimerGrid />
|
||||
<Scrubber />
|
||||
</LowerPanel>
|
||||
<div className="w-screen overflow-x-auto" onScroll={onScroll}>
|
||||
<div
|
||||
style={{
|
||||
width: filmLength.value * 60 * 4 * scale.value,
|
||||
height: 60,
|
||||
}}
|
||||
onClick={onTimelineClick}>
|
||||
<TimerGrid />
|
||||
<Scrubber />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<LoadingBar
|
||||
id="editor-loading-bar"
|
||||
wrapperClassName="absolute top-0 left-0"
|
||||
innerWrapperClassName="max-w-screen-sm"
|
||||
isShowing={appUiState.showEditorLoadingBar.isShowing}
|
||||
message={appUiState.showEditorLoadingBar.message}
|
||||
label={appUiState.showEditorLoadingBar.label}
|
||||
progress={appUiState.showEditorLoadingBar.progress}
|
||||
/>
|
||||
id="editor-loading-bar"
|
||||
wrapperClassName="absolute top-0 left-0"
|
||||
innerWrapperClassName="max-w-screen-sm"
|
||||
isShowing={appUiState.showEditorLoadingBar.isShowing}
|
||||
message={appUiState.showEditorLoadingBar.message}
|
||||
label={appUiState.showEditorLoadingBar.label}
|
||||
progress={appUiState.showEditorLoadingBar.progress}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -7,8 +7,8 @@ import {
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
import { AppUiContext } from "../../../../contexts/AppUiContext";
|
||||
import { EngineContext } from "../../../../contexts/EngineContext";
|
||||
import { AppUiContext } from "~/contexts/AppUiContext";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { Button, H5, InputVector } from "~/components";
|
||||
|
||||
import { XYZ } from "../../datastructures/common";
|
||||
@@ -33,6 +33,9 @@ export const ControlPanelSceneObject = () => {
|
||||
|
||||
useEffect(() => {
|
||||
// TODO this causes a subtle bug because it renders way too many times.
|
||||
if (!appUiState.controlPanel.currentSceneObject) {
|
||||
return;
|
||||
}
|
||||
const vectors = appUiState.controlPanel.currentSceneObject.objectVectors;
|
||||
|
||||
editorEngine?.setSelectedObject(
|
||||
@@ -176,8 +179,7 @@ export const ControlPanelSceneObject = () => {
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
leaveTo="opacity-0">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<FontAwesomeIcon icon={faCube} />
|
||||
@@ -227,8 +229,7 @@ export const ControlPanelSceneObject = () => {
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="grow"
|
||||
onClick={handleOnAddKeyFrame}
|
||||
>
|
||||
onClick={handleOnAddKeyFrame}>
|
||||
Add Keyframe (K)
|
||||
</Button>
|
||||
<Button
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useContext } from "react";
|
||||
import { Button, } from "~/components";
|
||||
import { Button } from "~/components";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models/track";
|
||||
import { APPUI_ACTION_TYPES } from "~/reducers";
|
||||
import { AppUiContext } from "~/contexts/AppUiContext";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
|
||||
export const TestFeaturesButtons = ({debug}:{debug:boolean})=>{
|
||||
export const TestFeaturesButtons = ({ debug }: { debug: boolean }) => {
|
||||
if (!debug) return null;
|
||||
|
||||
|
||||
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
@@ -34,10 +34,10 @@ export const TestFeaturesButtons = ({debug}:{debug:boolean})=>{
|
||||
}
|
||||
};
|
||||
|
||||
const testTTSPanel = () =>{
|
||||
const testTTSPanel = () => {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.OPEN_DIALOGUE_TTS
|
||||
})
|
||||
type: APPUI_ACTION_TYPES.OPEN_DIALOGUE_TTS,
|
||||
});
|
||||
};
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
@@ -45,7 +45,7 @@ export const TestFeaturesButtons = ({debug}:{debug:boolean})=>{
|
||||
};
|
||||
const testStylizeRequest = () => {
|
||||
// editorEngine?.testStylizeRequest();
|
||||
console.log('editorEnging does not have testStylizeRequest')
|
||||
console.log("editorEnging does not have testStylizeRequest");
|
||||
};
|
||||
const handleButtonRender = () => {
|
||||
editorEngine?.generateVideo();
|
||||
@@ -53,7 +53,7 @@ export const TestFeaturesButtons = ({debug}:{debug:boolean})=>{
|
||||
|
||||
const handleButtonTakeFrame = () => {
|
||||
// editorEngine?.take_timeline_cam_clip();
|
||||
console.log('editorEnging does not have take_timeline_cam_clip')
|
||||
console.log("editorEnging does not have take_timeline_cam_clip");
|
||||
};
|
||||
|
||||
const handleButtonSingleFrame = () => {
|
||||
@@ -64,39 +64,45 @@ export const TestFeaturesButtons = ({debug}:{debug:boolean})=>{
|
||||
};
|
||||
|
||||
const smallButtons = "text-xs p-2 h-6 ";
|
||||
const tertiaryColor = "bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary ";
|
||||
|
||||
return(
|
||||
const tertiaryColor =
|
||||
"bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary ";
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex gap-1">
|
||||
<Button variant="secondary" onClick={handleButtonCameraView} className={smallButtons}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={handleButtonCameraView}
|
||||
className={smallButtons}>
|
||||
Toggle Camera View
|
||||
</Button>
|
||||
<Button
|
||||
onClick={testObjectPanel}
|
||||
className={smallButtons}
|
||||
>
|
||||
<Button onClick={testObjectPanel} className={smallButtons}>
|
||||
Object Panel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={testTTSPanel}
|
||||
className={tertiaryColor + smallButtons}
|
||||
>
|
||||
<Button onClick={testTTSPanel} className={tertiaryColor + smallButtons}>
|
||||
TTS Panel
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
<Button onClick={handleButtonSingleFrame} className={smallButtons}>Render Single Frame</Button>
|
||||
<Button onClick={handleButtonTakeFrame} className={smallButtons}>Take Frame</Button>
|
||||
<Button onClick={handleButtonRender} className={smallButtons}>Render</Button>
|
||||
<Button onClick={handleButtonSingleFrame} className={smallButtons}>
|
||||
Render Single Frame
|
||||
</Button>
|
||||
<Button onClick={handleButtonTakeFrame} className={smallButtons}>
|
||||
Take Frame
|
||||
</Button>
|
||||
<Button onClick={handleButtonRender} className={smallButtons}>
|
||||
Render
|
||||
</Button>
|
||||
<Button
|
||||
onClick={testStylizeRequest}
|
||||
className={"bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary " +smallButtons}
|
||||
style={{ zIndex: 9001 }}
|
||||
>
|
||||
className={
|
||||
"hover:bg-brand-teriary-400 bg-brand-tertiary focus-visible:outline-brand-tertiary " +
|
||||
smallButtons
|
||||
}
|
||||
style={{ zIndex: 9001 }}>
|
||||
Test Stylize
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
@@ -1,10 +1,5 @@
|
||||
import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips";
|
||||
import {
|
||||
audioGroup,
|
||||
fullWidth,
|
||||
toggleAudioMute,
|
||||
updateAudio,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { audioGroup, fullWidth, updateAudio } from "~/pages/PageEnigma/store";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models";
|
||||
|
||||
export const Audio = () => {
|
||||
@@ -12,21 +7,16 @@ export const Audio = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="bg-global_audio-groupBg block rounded-lg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="block rounded-r-lg bg-global_audio-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Global Audio
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 pt-4">
|
||||
<TrackClips
|
||||
id={audioGroup.value.id}
|
||||
clips={clips}
|
||||
title="Global Audio TrackClips"
|
||||
updateClip={updateAudio}
|
||||
group={ClipGroup.GLOBAL_AUDIO}
|
||||
muted={audioGroup.value.muted}
|
||||
toggleMute={toggleAudioMute}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,11 +9,10 @@ export const Camera = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="block rounded-lg bg-camera-groupBg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="block rounded-r-lg bg-camera-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="mb-5 pt-2 text-xs font-medium text-white">Camera</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="pt-4">
|
||||
<TrackKeyFrames
|
||||
id={cameraGroup.value.id}
|
||||
keyframes={keyframes}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { TrackClips } from "~/pages/PageEnigma/comps/Timeline/TrackClips";
|
||||
import {
|
||||
characterGroups,
|
||||
fullWidth,
|
||||
toggleLipSyncMute,
|
||||
updateCharacters,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
import { ClipGroup, ClipType } from "~/pages/PageEnigma/models";
|
||||
import { CharacterTrack, ClipGroup, ClipType } from "~/pages/PageEnigma/models";
|
||||
|
||||
function buildUpdaters(
|
||||
updateCharacters: (options: {
|
||||
@@ -37,35 +36,23 @@ function buildUpdaters(
|
||||
return { updateClipLipSync, updateClipPosition, updateClipAnimations };
|
||||
}
|
||||
interface Props {
|
||||
characterId: string;
|
||||
character: CharacterTrack;
|
||||
}
|
||||
|
||||
export const Character = ({ characterId }: Props) => {
|
||||
const character = characterGroups.value.find((row) => (row.id = characterId));
|
||||
|
||||
export const Character = ({ character }: Props) => {
|
||||
const { updateClipLipSync, updateClipPosition, updateClipAnimations } =
|
||||
useMemo(() => buildUpdaters(updateCharacters), []);
|
||||
|
||||
const toggleCharacterLipSyncMute = useCallback(() => {
|
||||
toggleLipSyncMute(character?.id ?? "");
|
||||
}, [character?.id]);
|
||||
|
||||
if (!character) {
|
||||
return false;
|
||||
}
|
||||
const { animationClips, positionKeyframes, lipSyncClips } = character;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="block rounded-lg bg-character-groupBg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="block rounded-r-lg bg-character-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 16 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Character
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4 pt-4">
|
||||
<TrackClips
|
||||
id={character.id}
|
||||
id={character.object_uuid}
|
||||
clips={animationClips}
|
||||
title="Animation"
|
||||
updateClip={updateClipAnimations}
|
||||
@@ -73,19 +60,17 @@ export const Character = ({ characterId }: Props) => {
|
||||
type={ClipType.ANIMATION}
|
||||
/>
|
||||
<TrackKeyFrames
|
||||
id={character.id}
|
||||
id={character.object_uuid}
|
||||
keyframes={positionKeyframes}
|
||||
title="Character Position/Rotation"
|
||||
updateKeyframe={updateClipPosition}
|
||||
group={ClipGroup.CHARACTER}
|
||||
/>
|
||||
<TrackClips
|
||||
id={character.id}
|
||||
id={character.object_uuid}
|
||||
clips={lipSyncClips}
|
||||
title="Lipsync Audio TrackClips"
|
||||
updateClip={updateClipLipSync}
|
||||
muted={character.muted}
|
||||
toggleMute={toggleCharacterLipSyncMute}
|
||||
group={ClipGroup.CHARACTER}
|
||||
type={ClipType.AUDIO}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { characterGroups } from "~/pages/PageEnigma/store";
|
||||
import { characterGroup } from "~/pages/PageEnigma/store";
|
||||
import { Character } from "~/pages/PageEnigma/comps/Timeline/Character";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
@@ -6,8 +6,10 @@ export const Characters = () => {
|
||||
useSignals();
|
||||
return (
|
||||
<>
|
||||
{characterGroups.value.map((character) => (
|
||||
<Character key={character.id} characterId={character.id} />
|
||||
{characterGroup.value.characters.map((character) => (
|
||||
<div key={character.object_uuid} className="pb-4 pr-4">
|
||||
<Character character={character} />
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { objectGroup } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ObjectTrack } from "~/pages/PageEnigma/comps/Timeline/ObjectTrack";
|
||||
import { ObjectTrackComponent } from "~/pages/PageEnigma/comps/Timeline/ObjectTrack";
|
||||
|
||||
export const ObjectGroups = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<>
|
||||
{objectGroup.value.objects.length > 0 && (
|
||||
<div className="p-4">
|
||||
<ObjectTrack />
|
||||
{objectGroup.value.objects.map((object) => (
|
||||
<div key={object.object_uuid} className="pb-4 pr-4">
|
||||
<ObjectTrackComponent object={object} />
|
||||
</div>
|
||||
)}
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,27 +1,26 @@
|
||||
import { fullWidth, objectGroup, updateObject } from "~/pages/PageEnigma/store";
|
||||
import { fullWidth, updateObject } from "~/pages/PageEnigma/store";
|
||||
import { TrackKeyFrames } from "~/pages/PageEnigma/comps/Timeline/TrackKeyFrames";
|
||||
import { ClipGroup } from "~/pages/PageEnigma/models";
|
||||
import { ClipGroup, ObjectTrack } from "~/pages/PageEnigma/models";
|
||||
|
||||
export const ObjectTrack = () => {
|
||||
interface Props {
|
||||
object: ObjectTrack;
|
||||
}
|
||||
|
||||
export const ObjectTrackComponent = ({ object }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className="bg-object-groupBg block rounded-lg pb-5 pl-2 pr-4"
|
||||
style={{ width: fullWidth.value + 90 }}
|
||||
className="mr-4 block rounded-r-lg bg-object-groupBg pb-5 pr-4"
|
||||
style={{ width: fullWidth.value + 32 }}
|
||||
>
|
||||
<div className="prevent-select mb-5 pt-2 text-xs font-medium text-white">
|
||||
Objects
|
||||
<div key={object.object_uuid} className="pt-4">
|
||||
<TrackKeyFrames
|
||||
id={object.object_uuid}
|
||||
keyframes={object.keyframes}
|
||||
title={`${object.name} Position/Rotation`}
|
||||
updateKeyframe={updateObject}
|
||||
group={ClipGroup.OBJECT}
|
||||
/>
|
||||
</div>
|
||||
{objectGroup.value.objects.map((object) => (
|
||||
<div key={object.object_uuid} className="flex flex-col gap-4">
|
||||
<TrackKeyFrames
|
||||
id={object.object_uuid}
|
||||
keyframes={object.keyframes}
|
||||
title={`${object.name} Position/Rotation`}
|
||||
updateKeyframe={updateObject}
|
||||
group={ClipGroup.OBJECT}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
audioGroup,
|
||||
characterGroup,
|
||||
objectGroup,
|
||||
timelineScrollY,
|
||||
toggleAudioMute,
|
||||
toggleLipSyncMute,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faVolume, faVolumeSlash } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const RowHeaders = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div
|
||||
className="absolute mt-2"
|
||||
style={{ top: timelineScrollY.value * -1 - 8 }}
|
||||
>
|
||||
{characterGroup.value.characters.map((character) => (
|
||||
<div className="mb-4 ml-6" key={character.object_uuid}>
|
||||
<div className="relative block h-[224px] w-[64px] overflow-x-hidden rounded-l-lg bg-character-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Character
|
||||
</div>
|
||||
<div className="absolute">
|
||||
<button
|
||||
className="text-md absolute text-white transition-colors duration-100 hover:text-white/80"
|
||||
style={{ top: 174, left: 34 }}
|
||||
onClick={() => toggleLipSyncMute(character.object_uuid)}
|
||||
>
|
||||
{character.muted ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faVolumeSlash}
|
||||
className="text-brand-primary transition-colors duration-100 hover:text-brand-primary/80"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faVolume} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="mb-4 ml-6">
|
||||
<div className="relative block h-[88px] w-[64px] rounded-l-lg bg-camera-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Camera
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4 ml-6">
|
||||
<div className="relative block h-[88px] w-[64px] rounded-l-lg bg-global_audio-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Global Audio
|
||||
</div>
|
||||
<div className="absolute">
|
||||
<button
|
||||
className="text-md absolute text-white transition-colors duration-100 hover:text-white/80"
|
||||
style={{ top: 40, left: 34 }}
|
||||
onClick={() => toggleAudioMute()}
|
||||
>
|
||||
{audioGroup.value.muted ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faVolumeSlash}
|
||||
className="text-brand-primary transition-colors duration-100 hover:text-brand-primary/80"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faVolume} />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{objectGroup.value.objects.map((obj) => (
|
||||
<div key={obj.object_uuid} className="mb-4 ml-6">
|
||||
<div className="relative block h-[88px] w-[64px] rounded-l-lg bg-object-groupBg">
|
||||
<div className="absolute left-2 top-2 text-xs font-medium text-white">
|
||||
Object
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,9 @@
|
||||
import { currentTime, fullHeight, scale } from "~/pages/PageEnigma/store";
|
||||
import {
|
||||
timelineScrollX,
|
||||
currentTime,
|
||||
fullHeight,
|
||||
scale,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faSortDown } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { useMouseEventsScrubber } from "~/pages/PageEnigma/comps/Timeline/utils/useMouseEventsScrubber";
|
||||
@@ -9,10 +14,16 @@ export const Scrubber = () => {
|
||||
const { onPointerDown, time } = useMouseEventsScrubber();
|
||||
const displayTime = time === -1 ? currentTime.value : time;
|
||||
|
||||
if (displayTime * 4 * scale.value + 84 - timelineScrollX.value < 84) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className="absolute flex cursor-ew-resize flex-col items-center text-brand-primary"
|
||||
style={{ top: 8, left: displayTime * 4 * scale.value + 84 }}
|
||||
style={{
|
||||
top: 8,
|
||||
left: displayTime * 4 * scale.value + 84 - timelineScrollX.value,
|
||||
}}
|
||||
onPointerDown={onPointerDown}
|
||||
>
|
||||
<div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { UIEvent, useCallback, useEffect, useState } from "react";
|
||||
import { LowerPanel } from "~/modules/LowerPanel";
|
||||
|
||||
import { Camera } from "./Camera";
|
||||
@@ -10,15 +10,21 @@ import {
|
||||
deleteCharacterClip,
|
||||
selectedItem,
|
||||
ignoreDelete,
|
||||
timelineScrollX,
|
||||
timelineScrollY,
|
||||
filmLength,
|
||||
scale,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { useQueueHandler } from "~/pages/PageEnigma/comps/Timeline/utils/useQueueHandler";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { TimerGrid } from "~/pages/PageEnigma/comps/TimerGrid/TimerGrid";
|
||||
import { TimerGrid } from "~/pages/PageEnigma/comps/Timeline/TimerGrid";
|
||||
import { Scrubber } from "~/pages/PageEnigma/comps/Timeline/Scrubber";
|
||||
import { Characters } from "~/pages/PageEnigma/comps/Timeline/Characters";
|
||||
import { ObjectGroups } from "~/pages/PageEnigma/comps/Timeline/ObjectGroups";
|
||||
import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe";
|
||||
import { Clip, Keyframe } from "~/pages/PageEnigma/models";
|
||||
import { RowHeaders } from "~/pages/PageEnigma/comps/Timeline/RowHeaders";
|
||||
import { pageWidth } from "~/store";
|
||||
|
||||
function getItemType(item: Clip | Keyframe | null) {
|
||||
if (!item) {
|
||||
@@ -32,6 +38,11 @@ export const Timeline = () => {
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const { deleteKeyframe } = useUpdateKeyframe();
|
||||
|
||||
const onScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
timelineScrollX.value = event.currentTarget.scrollLeft;
|
||||
timelineScrollY.value = event.currentTarget.scrollTop;
|
||||
}, []);
|
||||
|
||||
// implement the code to handle incoming messages from the Engine
|
||||
useQueueHandler();
|
||||
|
||||
@@ -75,17 +86,39 @@ export const Timeline = () => {
|
||||
<>
|
||||
<LowerPanel>
|
||||
<TimerGrid />
|
||||
<div className="p-4">
|
||||
<Characters />
|
||||
<div className="flex">
|
||||
<div
|
||||
className="mt-2 overflow-hidden"
|
||||
style={{
|
||||
width: 88,
|
||||
height: timelineHeight.value - 54,
|
||||
}}
|
||||
>
|
||||
<RowHeaders />
|
||||
</div>
|
||||
<div
|
||||
className="mt-2 overflow-auto"
|
||||
onScroll={onScroll}
|
||||
style={{
|
||||
width: pageWidth.value - 90,
|
||||
height: timelineHeight.value - 54,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ width: filmLength.value * 60 * 4 * scale.value + 32 }}
|
||||
>
|
||||
<Characters />
|
||||
<div className="pb-4 pr-8">
|
||||
<Camera />
|
||||
</div>
|
||||
<div className="pb-4 pr-8">
|
||||
<Audio />
|
||||
</div>
|
||||
<ObjectGroups />
|
||||
</div>
|
||||
</div>
|
||||
<Scrubber />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Camera />
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<Audio />
|
||||
</div>
|
||||
<ObjectGroups />
|
||||
<Scrubber />
|
||||
</LowerPanel>
|
||||
<ConfirmationModal
|
||||
title="Delete Clip"
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
timelineScrollX,
|
||||
filmLength,
|
||||
fullHeight,
|
||||
scale,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { Fragment } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const TimerGrid = () => {
|
||||
useSignals();
|
||||
const sectionWidth = 60 * 4 * scale.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"prevent-select ml-[88px] mt-4",
|
||||
"relative flex h-7 overflow-hidden",
|
||||
"border-t border-t-ui-panel-border",
|
||||
"text-xs text-white opacity-75",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="absolute" style={{ left: timelineScrollX.value * -1 }}>
|
||||
{Array(filmLength.value)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
<div
|
||||
className="absolute ps-1 pt-2"
|
||||
style={{ left: index * sectionWidth + 4 }}
|
||||
>
|
||||
00:{index < 10 ? "0" + index.toString() : index.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: index * sectionWidth,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
{Array(9)
|
||||
.fill(0)
|
||||
.map((_, ind) => (
|
||||
<div
|
||||
key={ind}
|
||||
className="absolute block h-2 bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
top: 0,
|
||||
left:
|
||||
index * sectionWidth + (sectionWidth / 10) * (ind + 1),
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
<div
|
||||
className="absolute pt-2"
|
||||
style={{ left: filmLength.value * sectionWidth + 4 }}
|
||||
>
|
||||
00:
|
||||
{filmLength.value < 10
|
||||
? "0" + filmLength.value.toString()
|
||||
: filmLength.value.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block h-full bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: filmLength.value * sectionWidth,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -23,8 +23,6 @@ interface Props {
|
||||
title: string;
|
||||
group: ClipGroup;
|
||||
type?: ClipType;
|
||||
toggleMute?: () => void;
|
||||
muted?: boolean;
|
||||
updateClip: (options: { id: string; length: number; offset: number }) => void;
|
||||
}
|
||||
|
||||
@@ -56,9 +54,7 @@ function getCanBuild({
|
||||
export const TrackClips = ({
|
||||
id,
|
||||
clips,
|
||||
toggleMute,
|
||||
updateClip,
|
||||
muted,
|
||||
title,
|
||||
group,
|
||||
type,
|
||||
@@ -81,7 +77,7 @@ export const TrackClips = ({
|
||||
return;
|
||||
}
|
||||
|
||||
// Now check the the clip fits
|
||||
// Now check if the clip fits
|
||||
const position = track.getBoundingClientRect();
|
||||
const clipOffset = (event.clientX - position.x) / 4 / scale.value;
|
||||
|
||||
@@ -114,49 +110,31 @@ export const TrackClips = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="pl-16">
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
onPointerOver={onPointerOver}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
{clips.map((clip, index) => (
|
||||
<TrackClip
|
||||
key={clip.clip_uuid}
|
||||
min={
|
||||
index > 0 ? clips[index - 1].offset + clips[index - 1].length : 0
|
||||
}
|
||||
max={
|
||||
index < clips.length - 1
|
||||
? clips[index + 1].offset
|
||||
: filmLength.value * 60
|
||||
}
|
||||
group={group}
|
||||
updateClip={updateClip}
|
||||
clip={clip}
|
||||
/>
|
||||
))}
|
||||
<div className="prevent-select absolute ps-2 pt-1 text-xs font-medium text-white">
|
||||
{title}
|
||||
</div>
|
||||
{!!toggleMute && (
|
||||
<button
|
||||
className="text-md absolute text-white transition-colors duration-100 hover:text-white/80"
|
||||
style={{ top: 6, left: -28 }}
|
||||
onClick={toggleMute}
|
||||
>
|
||||
{muted ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faVolumeSlash}
|
||||
className="text-brand-primary transition-colors duration-100 hover:text-brand-primary/80"
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon icon={faVolume} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<div
|
||||
id={`track-${trackType}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
onPointerOver={onPointerOver}
|
||||
onPointerLeave={onPointerLeave}
|
||||
onPointerMove={onPointerMove}
|
||||
>
|
||||
{clips.map((clip, index) => (
|
||||
<TrackClip
|
||||
key={clip.clip_uuid}
|
||||
min={
|
||||
index > 0 ? clips[index - 1].offset + clips[index - 1].length : 0
|
||||
}
|
||||
max={
|
||||
index < clips.length - 1
|
||||
? clips[index + 1].offset
|
||||
: filmLength.value * 60
|
||||
}
|
||||
group={group}
|
||||
updateClip={updateClip}
|
||||
clip={clip}
|
||||
/>
|
||||
))}
|
||||
<div className="prevent-select absolute ps-2 pt-1 text-xs font-medium text-white">
|
||||
{title}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -24,7 +24,7 @@ export const TrackKeyFrames = ({
|
||||
group,
|
||||
}: Props) => {
|
||||
return (
|
||||
<div className="pl-16">
|
||||
<div>
|
||||
<div
|
||||
id={`track-${group}-${id}`}
|
||||
className={`relative mt-4 block h-9 w-full rounded-lg bg-${group}-unselected`}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
currentScroll,
|
||||
timelineScrollX,
|
||||
currentTime,
|
||||
filmLength,
|
||||
scale,
|
||||
@@ -26,7 +26,7 @@ export const useMouseEventsScrubber = () => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_TIME,
|
||||
data: { currentTime: Math.round(time) + currentScroll.value },
|
||||
data: { currentTime: Math.round(time) + timelineScrollX.value },
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -46,7 +46,7 @@ export const useMouseEventsScrubber = () => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_TIME,
|
||||
data: { currentTime: delta + currentScroll.value },
|
||||
data: { currentTime: delta + timelineScrollX.value },
|
||||
});
|
||||
}
|
||||
return delta;
|
||||
|
||||
@@ -40,31 +40,34 @@ export function useQueueHandler() {
|
||||
const { addKeyframe, clearExistingData, deleteObjectOrCharacter } =
|
||||
useContext(TrackContext);
|
||||
|
||||
const handleFromEngineActions = useCallback(({ action, data }: Arguments) => {
|
||||
console.log("FROM ENGINE", action, data);
|
||||
switch (action) {
|
||||
case fromEngineActions.UPDATE_TIME:
|
||||
currentTime.value = (data as UpdateTime).currentTime;
|
||||
break;
|
||||
case fromEngineActions.UPDATE_TIME_LINE:
|
||||
clearExistingData();
|
||||
(data as ClipUI[]).forEach((item) => {
|
||||
LOADING_FUNCTIONS[item.group](item);
|
||||
});
|
||||
break;
|
||||
case fromEngineActions.UPDATE_CHARACTER_ID: {
|
||||
addNewCharacter(data as MediaItem);
|
||||
break;
|
||||
const handleFromEngineActions = useCallback(
|
||||
({ action, data }: Arguments) => {
|
||||
console.log("FROM ENGINE", action, data);
|
||||
switch (action) {
|
||||
case fromEngineActions.UPDATE_TIME:
|
||||
currentTime.value = (data as UpdateTime).currentTime;
|
||||
break;
|
||||
case fromEngineActions.UPDATE_TIME_LINE:
|
||||
clearExistingData();
|
||||
(data as ClipUI[]).forEach((item) => {
|
||||
LOADING_FUNCTIONS[item.group](item);
|
||||
});
|
||||
break;
|
||||
case fromEngineActions.UPDATE_CHARACTER_ID: {
|
||||
addNewCharacter(data as MediaItem);
|
||||
break;
|
||||
}
|
||||
case fromEngineActions.DELETE_OBJECT: {
|
||||
// this could be an object or character
|
||||
deleteObjectOrCharacter(data as MediaItem);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown action ${action}`);
|
||||
}
|
||||
case fromEngineActions.DELETE_OBJECT: {
|
||||
// this could be an object or character
|
||||
deleteObjectOrCharacter(data as MediaItem);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unknown action ${action}`);
|
||||
}
|
||||
}, []);
|
||||
},
|
||||
[clearExistingData, deleteObjectOrCharacter],
|
||||
);
|
||||
|
||||
const handleToTimelineActions = useCallback(
|
||||
({ action, data }: Arguments) => {
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
import { filmLength, fullHeight, scale } from "~/pages/PageEnigma/store";
|
||||
import { Fragment } from "react";
|
||||
|
||||
export const TimerGrid = () => {
|
||||
const sectionWidth = 60 * 4 * scale.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
"prevent-select mt-4",
|
||||
"flex h-3",
|
||||
"border-t border-t-ui-panel-border",
|
||||
"text-xs text-white opacity-75",
|
||||
].join(" ")}
|
||||
>
|
||||
{Array(filmLength.value)
|
||||
.fill(0)
|
||||
.map((_, index) => (
|
||||
<Fragment key={index}>
|
||||
<div
|
||||
className="absolute ps-1 pt-1"
|
||||
style={{ left: index * sectionWidth + 92 }}
|
||||
>
|
||||
00:{index < 10 ? "0" + index.toString() : index.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block h-full bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: index * sectionWidth + 88,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
<div
|
||||
className="absolute"
|
||||
style={{ left: filmLength.value * sectionWidth + 92 }}
|
||||
>
|
||||
00:
|
||||
{filmLength.value < 10
|
||||
? "0" + filmLength.value.toString()
|
||||
: filmLength.value.toString()}
|
||||
</div>
|
||||
<div
|
||||
className="absolute block h-full bg-ui-divider"
|
||||
style={{
|
||||
width: 1,
|
||||
left: filmLength.value * sectionWidth + 88,
|
||||
height: fullHeight.value,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
dropId,
|
||||
dropOffset,
|
||||
addObject,
|
||||
characterGroups,
|
||||
characterGroup,
|
||||
cameraGroup,
|
||||
audioGroup,
|
||||
objectGroup,
|
||||
@@ -75,7 +75,10 @@ export const TrackProvider = ({ children }: Props) => {
|
||||
}, [endDrag]);
|
||||
|
||||
const clearExistingData = useCallback(() => {
|
||||
characterGroups.value = [];
|
||||
characterGroup.value = {
|
||||
id: "ChG1",
|
||||
characters: [],
|
||||
};
|
||||
cameraGroup.value = {
|
||||
id: "CG1",
|
||||
keyframes: [],
|
||||
|
||||
@@ -219,11 +219,12 @@ class Editor {
|
||||
}
|
||||
|
||||
isEmpty(value: string) {
|
||||
return (value == null || (typeof value === "string" && value.trim().length === 0));
|
||||
return (
|
||||
value == null || (typeof value === "string" && value.trim().length === 0)
|
||||
);
|
||||
}
|
||||
|
||||
initialize(config: any, sceneToken) {
|
||||
|
||||
//setup reactland Callbacks
|
||||
this.dispatchAppUiState = config.dispatchAppUiState;
|
||||
|
||||
@@ -358,7 +359,7 @@ class Editor {
|
||||
this.updateLoop();
|
||||
|
||||
if (this.isEmpty(sceneToken) == false) {
|
||||
this.loadScene(sceneToken)
|
||||
this.loadScene(sceneToken);
|
||||
}
|
||||
|
||||
this.dispatchAppUiState({
|
||||
@@ -384,7 +385,7 @@ class Editor {
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
public async testTestTimelineEvents() { }
|
||||
public async testTestTimelineEvents() {}
|
||||
|
||||
public async loadScene(scene_media_token: string) {
|
||||
this.dispatchAppUiState({
|
||||
@@ -427,7 +428,7 @@ class Editor {
|
||||
// TO UPDATE selected objects in the scene might want to add to the scene ...
|
||||
async setSelectedObject(position: XYZ, rotation: XYZ, scale: XYZ) {
|
||||
if (this.selected != undefined || this.selected != null) {
|
||||
//console.log(`triggering setSelectedObject`)
|
||||
console.log(`triggering setSelectedObject`);
|
||||
this.selected.position.x = position.x;
|
||||
this.selected.position.y = position.y;
|
||||
this.selected.position.z = position.z;
|
||||
@@ -528,6 +529,7 @@ class Editor {
|
||||
|
||||
this.removeTransformControls();
|
||||
this.selected = this.cam_obj;
|
||||
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
});
|
||||
@@ -574,7 +576,7 @@ class Editor {
|
||||
payload: {
|
||||
showEditorLoadingBar: {
|
||||
progress: progress,
|
||||
message: message
|
||||
message: message,
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -719,7 +721,9 @@ class Editor {
|
||||
this.rawRenderer.setSize(1024, 576);
|
||||
this.render_camera.aspect = 1024 / 576;
|
||||
this.record_stream = this.rawRenderer.domElement.captureStream(60); // Capture at 60 FPS
|
||||
this.recorder = new MediaRecorder(this.record_stream, { mimeType: 'video/webm' });
|
||||
this.recorder = new MediaRecorder(this.record_stream, {
|
||||
mimeType: "video/webm",
|
||||
});
|
||||
this.recorder.ondataavailable = (event) => {
|
||||
if (event.data.size > 0) {
|
||||
this.frame_buffer.push(event.data);
|
||||
@@ -729,7 +733,7 @@ class Editor {
|
||||
};
|
||||
this.recorder.onstop = () => {
|
||||
this.stopPlayback();
|
||||
}
|
||||
};
|
||||
this.recorder.start();
|
||||
}
|
||||
|
||||
@@ -797,7 +801,7 @@ class Editor {
|
||||
if (changeView) {
|
||||
this.switchCameraView();
|
||||
}
|
||||
} else if (this.last_scrub == this.timeline.scrubber_frame_position) {
|
||||
} else if (this.last_scrub !== this.timeline.scrubber_frame_position) {
|
||||
this.updateSelectedUI();
|
||||
}
|
||||
|
||||
@@ -864,10 +868,10 @@ class Editor {
|
||||
audioSegment,
|
||||
"-filter_complex",
|
||||
"[1:a]adelay=" +
|
||||
startTime * 1000 +
|
||||
"|" +
|
||||
startTime * 1000 +
|
||||
"[a1];[0:a][a1]amix=inputs=2[a]",
|
||||
startTime * 1000 +
|
||||
"|" +
|
||||
startTime * 1000 +
|
||||
"[a1];[0:a][a1]amix=inputs=2[a]",
|
||||
"-map",
|
||||
"[a]",
|
||||
`${itteration}final_tmp.wav`,
|
||||
@@ -895,9 +899,9 @@ class Editor {
|
||||
async _debugDownloadVideo(videoURL: string) {
|
||||
// DEBUG ONLY to download the video
|
||||
|
||||
let a = document.createElement('a');
|
||||
const a = document.createElement("a");
|
||||
a.href = videoURL;
|
||||
a.download = 'video.mp4'; // Name of the downloaded file
|
||||
a.download = "video.mp4"; // Name of the downloaded file
|
||||
document.body.appendChild(a);
|
||||
a.click(); // Trigger the download
|
||||
}
|
||||
@@ -906,18 +910,17 @@ class Editor {
|
||||
//let video_fps = Math.floor(this.frames * (this.cap_fps / this.timeline.timeline_limit));
|
||||
//console.log("Video FPS:", video_fps)
|
||||
this.rendering = false;
|
||||
const videoBlob = new Blob(this.frame_buffer, { type: 'video/webm' });
|
||||
const videoBlob = new Blob(this.frame_buffer, { type: "video/webm" });
|
||||
const videoURL = URL.createObjectURL(videoBlob);
|
||||
|
||||
this.generating_preview = true;
|
||||
const ffmpeg = createFFmpeg({ log: true });
|
||||
await ffmpeg.load();
|
||||
|
||||
this.updateLoad(50, "Processing ...")
|
||||
this.updateLoad(50, "Processing ...");
|
||||
|
||||
// Write the Uint8Array to the FFmpeg file system
|
||||
ffmpeg.FS('writeFile', 'input.webm', await fetchFile(videoURL));
|
||||
|
||||
ffmpeg.FS("writeFile", "input.webm", await fetchFile(videoURL));
|
||||
|
||||
await ffmpeg.run(
|
||||
"-i",
|
||||
@@ -1106,13 +1109,12 @@ class Editor {
|
||||
|
||||
// This initializes the generation of a video render scene is where the core work happens
|
||||
generateVideo() {
|
||||
|
||||
console.log("Generating video...", this.frame_buffer);
|
||||
if (this.rendering) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.showLoading()
|
||||
this.showLoading();
|
||||
|
||||
this.rendering = true; // has to go first to debounce
|
||||
this.startPlayback();
|
||||
@@ -1127,8 +1129,7 @@ class Editor {
|
||||
}
|
||||
|
||||
startPlayback() {
|
||||
|
||||
this.updateLoad(25, "Starting Processing")
|
||||
this.updateLoad(25, "Starting Processing");
|
||||
|
||||
this.timeline.is_playing = true;
|
||||
this.timeline.scrubber_frame_position = 0;
|
||||
@@ -1143,7 +1144,7 @@ class Editor {
|
||||
}
|
||||
|
||||
updateSelectedUI() {
|
||||
if (this.selected == undefined) {
|
||||
if (this.selected === undefined || this.timeline.is_playing) {
|
||||
return;
|
||||
}
|
||||
const pos = this.selected.position;
|
||||
|
||||
@@ -41,6 +41,12 @@ export interface Keyframe {
|
||||
|
||||
export interface CharacterGroup {
|
||||
id: string;
|
||||
characters: CharacterTrack[];
|
||||
}
|
||||
|
||||
export interface CharacterTrack {
|
||||
object_uuid: string;
|
||||
media_id: string;
|
||||
name: string;
|
||||
muted: boolean;
|
||||
animationClips: Clip[];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { CharacterGroup, MediaItem } from "~/pages/PageEnigma/models";
|
||||
import { CharacterTrack, 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";
|
||||
import { characterGroup } from "~/pages/PageEnigma/store";
|
||||
|
||||
export function addCharacter(character: MediaItem) {
|
||||
Queue.publish({
|
||||
@@ -13,16 +13,25 @@ export function addCharacter(character: MediaItem) {
|
||||
}
|
||||
|
||||
export function addNewCharacter(data: MediaItem) {
|
||||
const newCharacterGroups = {
|
||||
...characterGroup.value,
|
||||
characters: [...characterGroup.value.characters],
|
||||
};
|
||||
|
||||
const newCharacter = {
|
||||
id: data.object_uuid,
|
||||
object_uuid: data.object_uuid,
|
||||
name: data.name,
|
||||
media_id: data.media_id,
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionKeyframes: [],
|
||||
lipSyncClips: [],
|
||||
} as CharacterGroup;
|
||||
} as CharacterTrack;
|
||||
|
||||
characterGroups.value = [...characterGroups.value, newCharacter].sort(
|
||||
(charA, charB) => (charA.id < charB.id ? -1 : 1),
|
||||
newCharacterGroups.characters.push(newCharacter);
|
||||
newCharacterGroups.characters.sort((charA, charB) =>
|
||||
charA.object_uuid < charB.object_uuid ? -1 : 1,
|
||||
);
|
||||
|
||||
characterGroup.value = newCharacterGroups;
|
||||
}
|
||||
@@ -0,0 +1,276 @@
|
||||
import {
|
||||
CharacterGroup,
|
||||
Clip,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
Keyframe,
|
||||
MediaItem,
|
||||
QueueKeyframe,
|
||||
} from "~/pages/PageEnigma/models";
|
||||
import { signal } from "@preact/signals-core";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
export const characterGroup = signal<CharacterGroup>({
|
||||
id: "CG1",
|
||||
characters: [],
|
||||
});
|
||||
|
||||
export function addCharacterAnimation({
|
||||
dragItem,
|
||||
characterId,
|
||||
offset,
|
||||
}: {
|
||||
dragItem: MediaItem;
|
||||
characterId: string;
|
||||
offset: number;
|
||||
}) {
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
version: 1,
|
||||
media_id: dragItem.media_id,
|
||||
group: ClipGroup.CHARACTER,
|
||||
type: ClipType.ANIMATION,
|
||||
offset,
|
||||
length: dragItem.length,
|
||||
clip_uuid,
|
||||
name: dragItem.name,
|
||||
object_uuid: characterId,
|
||||
} as Clip;
|
||||
|
||||
const oldCharacterGroup = characterGroup.value;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.map((character) => {
|
||||
if (character.object_uuid !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
animationClips: [...character.animationClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function addCharacterAudio({
|
||||
dragItem,
|
||||
characterId,
|
||||
offset,
|
||||
}: {
|
||||
dragItem: MediaItem;
|
||||
characterId: string;
|
||||
offset: number;
|
||||
}) {
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
version: 1,
|
||||
media_id: dragItem.media_id,
|
||||
group: ClipGroup.CHARACTER,
|
||||
type: ClipType.AUDIO,
|
||||
offset,
|
||||
length: dragItem.length,
|
||||
clip_uuid,
|
||||
object_uuid: characterId,
|
||||
name: dragItem.name,
|
||||
} as Clip;
|
||||
|
||||
const oldCharacterGroup = characterGroup.value;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.map((character) => {
|
||||
if (character.object_uuid !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: [...character.lipSyncClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function addCharacterKeyframe(
|
||||
keyframe: QueueKeyframe,
|
||||
offset: number,
|
||||
addToast: (type: "error" | "warning" | "success", message: string) => void,
|
||||
) {
|
||||
const oldCharacterGroup = characterGroup.value;
|
||||
|
||||
if (
|
||||
oldCharacterGroup.characters.some((characterTrack) => {
|
||||
if (characterTrack.object_uuid !== keyframe.object_uuid) {
|
||||
return;
|
||||
}
|
||||
return characterTrack.positionKeyframes.some(
|
||||
(row) => row.offset === offset,
|
||||
);
|
||||
})
|
||||
) {
|
||||
addToast("warning", "There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
|
||||
const newKeyframe = {
|
||||
version: keyframe.version,
|
||||
keyframe_uuid: uuid.v4(),
|
||||
group: keyframe.group,
|
||||
object_uuid: keyframe.object_uuid,
|
||||
offset,
|
||||
position: keyframe.position,
|
||||
rotation: keyframe.rotation,
|
||||
scale: keyframe.scale,
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.map((character) => {
|
||||
if (character.object_uuid !== keyframe.object_uuid) {
|
||||
return { ...character };
|
||||
}
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newKeyframe,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
positionKeyframes: [...character.positionKeyframes, newKeyframe].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function toggleLipSyncMute(characterId: string) {
|
||||
const oldCharacterGroup = characterGroup.value;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.map((character) => {
|
||||
if (character.object_uuid === characterId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: character?.muted
|
||||
? toEngineActions.UNMUTE
|
||||
: toEngineActions.MUTE,
|
||||
data: {
|
||||
version: 1,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.CHARACTER,
|
||||
object_uuid: characterId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...character,
|
||||
muted:
|
||||
character.object_uuid === characterId
|
||||
? !character.muted
|
||||
: character.muted,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
export function selectCharacterClip(clipId: string) {
|
||||
const oldCharacterGroup = characterGroup.value;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
positionClips: character.positionKeyframes.map((keyframe) => ({
|
||||
...keyframe,
|
||||
selected:
|
||||
keyframe.keyframe_uuid === clipId
|
||||
? !keyframe.selected
|
||||
: keyframe.selected,
|
||||
})),
|
||||
lipSyncClips: character.lipSyncClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteCharacterClip(clip: Clip) {
|
||||
const oldCharacterGroup = characterGroup.value;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.filter((row) => {
|
||||
if (row.clip_uuid === clip.clip_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
lipSyncClips: character.lipSyncClips.filter((row) => {
|
||||
if (row.clip_uuid === clip.clip_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function deleteCharacterKeyframe(keyframe: Keyframe) {
|
||||
const oldCharacterGroup = characterGroup.value;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.map((character) => ({
|
||||
...character,
|
||||
positionKeyframes: character.positionKeyframes.filter((row) => {
|
||||
if (row.keyframe_uuid === keyframe.keyframe_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_KEYFRAME,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
})),
|
||||
};
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
import {
|
||||
CharacterGroup,
|
||||
Clip,
|
||||
ClipGroup,
|
||||
ClipType,
|
||||
Keyframe,
|
||||
MediaItem,
|
||||
QueueKeyframe,
|
||||
} from "~/pages/PageEnigma/models";
|
||||
import { signal } from "@preact/signals-core";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import * as uuid from "uuid";
|
||||
|
||||
export const characterGroups = signal<CharacterGroup[]>([]);
|
||||
|
||||
export function addCharacterAnimation({
|
||||
dragItem,
|
||||
characterId,
|
||||
offset,
|
||||
}: {
|
||||
dragItem: MediaItem;
|
||||
characterId: string;
|
||||
offset: number;
|
||||
}) {
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
version: 1,
|
||||
media_id: dragItem.media_id,
|
||||
group: ClipGroup.CHARACTER,
|
||||
type: ClipType.ANIMATION,
|
||||
offset,
|
||||
length: dragItem.length,
|
||||
clip_uuid,
|
||||
name: dragItem.name,
|
||||
object_uuid: characterId,
|
||||
} as Clip;
|
||||
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
animationClips: [...character.animationClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
}
|
||||
|
||||
export function addCharacterAudio({
|
||||
dragItem,
|
||||
characterId,
|
||||
offset,
|
||||
}: {
|
||||
dragItem: MediaItem;
|
||||
characterId: string;
|
||||
offset: number;
|
||||
}) {
|
||||
const clip_uuid = uuid.v4();
|
||||
const newClip = {
|
||||
version: 1,
|
||||
media_id: dragItem.media_id,
|
||||
group: ClipGroup.CHARACTER,
|
||||
type: ClipType.AUDIO,
|
||||
offset,
|
||||
length: dragItem.length,
|
||||
clip_uuid,
|
||||
object_uuid: characterId,
|
||||
name: dragItem.name,
|
||||
} as Clip;
|
||||
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id !== characterId) {
|
||||
return { ...character };
|
||||
}
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_CLIP,
|
||||
data: newClip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: [...character.lipSyncClips, newClip].sort(
|
||||
(clipA, clipB) => clipA.offset - clipB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function addCharacterKeyframe(
|
||||
keyframe: QueueKeyframe,
|
||||
offset: number,
|
||||
addToast: (type: "error" | "warning" | "success", message: string) => void,
|
||||
) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
|
||||
if (
|
||||
oldCharacterGroups.some((characterGroup) => {
|
||||
return characterGroup.positionKeyframes.some(
|
||||
(row) => row.offset === offset,
|
||||
);
|
||||
})
|
||||
) {
|
||||
addToast("warning", "There can only be one keyframe at this offset.");
|
||||
return;
|
||||
}
|
||||
|
||||
const newKeyframe = {
|
||||
version: keyframe.version,
|
||||
keyframe_uuid: uuid.v4(),
|
||||
group: keyframe.group,
|
||||
object_uuid: keyframe.object_uuid,
|
||||
offset,
|
||||
position: keyframe.position,
|
||||
rotation: keyframe.rotation,
|
||||
scale: keyframe.scale,
|
||||
selected: false,
|
||||
} as Keyframe;
|
||||
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id !== keyframe.object_uuid) {
|
||||
return { ...character };
|
||||
}
|
||||
return {
|
||||
...character,
|
||||
positionKeyframes: [...character.positionKeyframes, newKeyframe].sort(
|
||||
(keyFrameA, keyframeB) => keyFrameA.offset - keyframeB.offset,
|
||||
),
|
||||
};
|
||||
});
|
||||
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ADD_KEYFRAME,
|
||||
data: newKeyframe,
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleLipSyncMute(characterId: string) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => {
|
||||
if (character.id === characterId) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: character?.muted
|
||||
? toEngineActions.UNMUTE
|
||||
: toEngineActions.MUTE,
|
||||
data: {
|
||||
version: 1,
|
||||
type: ClipType.AUDIO,
|
||||
group: ClipGroup.CHARACTER,
|
||||
object_uuid: characterId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...character,
|
||||
muted: character.id === characterId ? !character.muted : character.muted,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function selectCharacterClip(clipId: string) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
positionClips: character.positionKeyframes.map((keyframe) => ({
|
||||
...keyframe,
|
||||
selected:
|
||||
keyframe.keyframe_uuid === clipId
|
||||
? !keyframe.selected
|
||||
: keyframe.selected,
|
||||
})),
|
||||
lipSyncClips: character.lipSyncClips.map((clip) => ({
|
||||
...clip,
|
||||
selected: clip.clip_uuid === clipId ? !clip.selected : clip.selected,
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
export function deleteCharacterClip(clip: Clip) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => ({
|
||||
...character,
|
||||
animationClips: character.animationClips.filter((row) => {
|
||||
if (row.clip_uuid === clip.clip_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
lipSyncClips: character.lipSyncClips.filter((row) => {
|
||||
if (row.clip_uuid === clip.clip_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_CLIP,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
}));
|
||||
}
|
||||
|
||||
export function deleteCharacterKeyframe(keyframe: Keyframe) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
characterGroups.value = oldCharacterGroups.map((character) => ({
|
||||
...character,
|
||||
positionKeyframes: character.positionKeyframes.filter((row) => {
|
||||
|
||||
if (row.keyframe_uuid === keyframe.keyframe_uuid) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.DELETE_KEYFRAME,
|
||||
data: row,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}),
|
||||
}));
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { MediaItem } from "~/pages/PageEnigma/models";
|
||||
import { characterGroups } from "~/pages/PageEnigma/store";
|
||||
import { characterGroup } from "~/pages/PageEnigma/store";
|
||||
|
||||
export function deleteCharacter(item: MediaItem) {
|
||||
characterGroups.value = characterGroups.value.filter(
|
||||
(character) => character.id !== item.object_uuid,
|
||||
);
|
||||
characterGroup.value = {
|
||||
...characterGroup.value,
|
||||
characters: characterGroup.value.characters.filter(
|
||||
(character) => character.object_uuid !== item.object_uuid,
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export * from "./addCharacter";
|
||||
export * from "./characterGroups";
|
||||
export * from "./characterGroup";
|
||||
export * from "./deleteCharacter";
|
||||
export * from "./loadCharacterData";
|
||||
export * from "./updateCharacters";
|
||||
@@ -1,15 +1,15 @@
|
||||
import { ClipUI } from "~/pages/PageEnigma/datastructures/clips/clip_ui";
|
||||
import {
|
||||
CharacterGroup,
|
||||
CharacterTrack,
|
||||
Clip,
|
||||
ClipType,
|
||||
Keyframe,
|
||||
} from "~/pages/PageEnigma/models";
|
||||
import { characterGroups } from "~/pages/PageEnigma/store";
|
||||
import { characterGroup } from "~/pages/PageEnigma/store";
|
||||
|
||||
function getAddCharacter(item: ClipUI) {
|
||||
const existingCharacter = characterGroups.value.find(
|
||||
(character) => character.id === item.object_uuid,
|
||||
const existingCharacter = characterGroup.value.characters.find(
|
||||
(character) => character.object_uuid === item.object_uuid,
|
||||
);
|
||||
|
||||
if (existingCharacter) {
|
||||
@@ -17,24 +17,28 @@ function getAddCharacter(item: ClipUI) {
|
||||
}
|
||||
|
||||
const newCharacter = {
|
||||
id: item.object_uuid,
|
||||
object_uuid: item.object_uuid,
|
||||
name: item.object_name,
|
||||
media_id: item.media_id,
|
||||
muted: false,
|
||||
animationClips: [],
|
||||
positionKeyframes: [],
|
||||
lipSyncClips: [],
|
||||
} as CharacterGroup;
|
||||
} as CharacterTrack;
|
||||
|
||||
characterGroups.value = [
|
||||
...characterGroups.value.filter(
|
||||
(character) => character.id !== item.object_uuid,
|
||||
),
|
||||
newCharacter,
|
||||
].sort((charA, charB) => (charA.id < charB.id ? -1 : 1));
|
||||
characterGroup.value = {
|
||||
...characterGroup.value,
|
||||
characters: [
|
||||
...characterGroup.value.characters.filter(
|
||||
(character) => character.object_uuid !== item.object_uuid,
|
||||
),
|
||||
newCharacter,
|
||||
].sort((charA, charB) => (charA.object_uuid < charB.object_uuid ? -1 : 1)),
|
||||
};
|
||||
|
||||
return characterGroups.value.find(
|
||||
(character) => character.id === item.object_uuid,
|
||||
) as CharacterGroup;
|
||||
return characterGroup.value.characters.find(
|
||||
(character) => character.object_uuid === item.object_uuid,
|
||||
) as CharacterTrack;
|
||||
}
|
||||
|
||||
export function loadCharacterData(item: ClipUI) {
|
||||
|
||||
@@ -2,7 +2,7 @@ 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";
|
||||
import { characterGroup } from "~/pages/PageEnigma/store";
|
||||
|
||||
export function updateCharacters({
|
||||
type,
|
||||
@@ -15,80 +15,89 @@ export function updateCharacters({
|
||||
length?: number;
|
||||
offset: number;
|
||||
}) {
|
||||
const oldCharacterGroups = characterGroups.value;
|
||||
const oldCharacterGroup = characterGroup.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!;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.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,
|
||||
});
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
animationClips: newAnimationClips,
|
||||
};
|
||||
});
|
||||
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;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.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_KEYFRAME,
|
||||
data: keyframe,
|
||||
});
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_KEYFRAME,
|
||||
data: keyframe,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
positionKeyframes: newPositionKeyframes,
|
||||
};
|
||||
});
|
||||
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!;
|
||||
characterGroup.value = {
|
||||
...oldCharacterGroup,
|
||||
characters: oldCharacterGroup.characters.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,
|
||||
});
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.UPDATE_CLIP,
|
||||
data: clip,
|
||||
});
|
||||
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: newLipSyncClips,
|
||||
};
|
||||
});
|
||||
return {
|
||||
...character,
|
||||
lipSyncClips: newLipSyncClips,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -73,7 +73,7 @@ export function deleteObjectKeyframe(keyframe: Keyframe) {
|
||||
const obj = oldObjectGroup.objects.find(
|
||||
(row) => row.object_uuid === keyframe.object_uuid,
|
||||
);
|
||||
console.log("delete", obj, keyframe.keyframe_uuid);
|
||||
|
||||
if (!obj) {
|
||||
return oldObjectGroup;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { signal, computed } from "@preact/signals-core";
|
||||
import { characterGroups } from "~/pages/PageEnigma/store/characterGroups";
|
||||
import { characterGroup } from "~/pages/PageEnigma/store/characterGroups";
|
||||
import { objectGroup } from "~/pages/PageEnigma/store/objectGroup";
|
||||
|
||||
// timeline
|
||||
@@ -9,7 +9,7 @@ export const timelineHeight = signal(0);
|
||||
|
||||
export const fullHeight = computed(() => {
|
||||
return (
|
||||
characterGroups.value.length * 268 +
|
||||
characterGroup.value.characters.length * 268 +
|
||||
objectGroup.value.objects.length * 60 +
|
||||
300 +
|
||||
96
|
||||
|
||||
@@ -2,4 +2,5 @@ import { signal } from "@preact/signals-core";
|
||||
|
||||
export const currentTime = signal(0);
|
||||
|
||||
export const currentScroll = signal(0);
|
||||
export const timelineScrollX = signal(0);
|
||||
export const timelineScrollY = signal(0);
|
||||
@@ -2,21 +2,21 @@ import { State, Action, ACTION_TYPES } from "./types";
|
||||
|
||||
export function reducer(state: State, action: Action): State {
|
||||
switch (action.type) {
|
||||
case ACTION_TYPES.OPEN_DIALOGUE_TTS:{
|
||||
return{
|
||||
case ACTION_TYPES.OPEN_DIALOGUE_TTS: {
|
||||
return {
|
||||
...state,
|
||||
diagloueTts:{
|
||||
diagloueTts: {
|
||||
isOpen: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.CLOSE_DIALOGUE_TTS:{
|
||||
return{
|
||||
case ACTION_TYPES.CLOSE_DIALOGUE_TTS: {
|
||||
return {
|
||||
...state,
|
||||
diagloueTts:{
|
||||
diagloueTts: {
|
||||
isOpen: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
case ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT: {
|
||||
// console.log("pay", action.payload);
|
||||
|
||||
Reference in new issue
Block a user