mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-15 06:12:18 +00:00
Merge pull request #184 from storytold/feature/style-tab
Move styling to tab from page
This commit is contained in:
31 files changed
+785
-485
No files matched your search
@@ -6,6 +6,7 @@ import { twMerge } from "tailwind-merge";
|
||||
interface Option {
|
||||
value: string;
|
||||
icon: IconDefinition;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
interface ButtonIconSelectProps {
|
||||
@@ -30,18 +31,19 @@ export function ButtonIconSelect({
|
||||
|
||||
return (
|
||||
<div className="flex space-x-1">
|
||||
{options.map(({ value, icon }) => (
|
||||
{options.map(({ value, icon, text }) => (
|
||||
<button
|
||||
key={value}
|
||||
className={twMerge(
|
||||
`flex h-8 w-8 items-center justify-center rounded-lg border-2 transition-all duration-150`,
|
||||
`flex h-8 items-center justify-center rounded-lg border-2 text-sm transition-all duration-150`,
|
||||
text ? "h-auto w-auto gap-2 px-2.5 py-1" : "w-8",
|
||||
selectedOption === value
|
||||
? "border-brand-primary bg-ui-panel/[0.2]"
|
||||
? "border-brand-primary bg-ui-panel/[0.3]"
|
||||
: "border-transparent hover:bg-ui-panel/[0.4]",
|
||||
)}
|
||||
onClick={() => handleOptionChange(value)}
|
||||
>
|
||||
onClick={() => handleOptionChange(value)}>
|
||||
<FontAwesomeIcon icon={icon} />
|
||||
{text && <span className="text-sm font-medium">{text}</span>}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -9,41 +9,54 @@ interface TypoProps {
|
||||
const baseTypo = "text-white";
|
||||
|
||||
export const H1 = ({ className, children }: TypoProps) => (
|
||||
<h1 className={twMerge(baseTypo, "text-2xl font-medium", className)}>{children}</h1>
|
||||
<h1 className={twMerge(baseTypo, "text-2xl font-medium", className)}>
|
||||
{children}
|
||||
</h1>
|
||||
);
|
||||
|
||||
export const H2 = ({ className, children }: TypoProps) => (
|
||||
<h2 className={twMerge(baseTypo, "text-xl font-medium", className)}>{children}</h2>
|
||||
<h2 className={twMerge(baseTypo, "text-xl font-medium", className)}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
|
||||
export const H3 = ({ className, children }: TypoProps) => (
|
||||
<h3 className={twMerge(baseTypo, "text-lg font-medium", className)}>{children}</h3>
|
||||
<h3 className={twMerge(baseTypo, "text-lg font-medium", className)}>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
|
||||
export const H4 = ({ className, children }: TypoProps) => (
|
||||
<h4 className={twMerge(baseTypo, "text-base font-medium", className)}>{children}</h4>
|
||||
<h4 className={twMerge(baseTypo, "text-base font-medium", className)}>
|
||||
{children}
|
||||
</h4>
|
||||
);
|
||||
|
||||
export const Label = ({
|
||||
className, children, ...rest
|
||||
export const Label = ({
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: LabelHTMLAttributes<HTMLLabelElement>) => (
|
||||
<label
|
||||
className={twMerge(baseTypo, "mb-2 text-base font-medium", className)}
|
||||
{...rest}
|
||||
>
|
||||
{children}
|
||||
className={twMerge(baseTypo, "mb-2 text-sm font-medium", className)}
|
||||
{...rest}>
|
||||
{children}
|
||||
</label>
|
||||
);
|
||||
|
||||
export const H5 = ({ className, children }: TypoProps) => (
|
||||
<h5 className={twMerge(baseTypo, "text-sm font-medium", className)}>{children}</h5>
|
||||
<h5 className={twMerge(baseTypo, "text-sm font-medium", className)}>
|
||||
{children}
|
||||
</h5>
|
||||
);
|
||||
|
||||
export const H6 = ({ className, children }: TypoProps) => (
|
||||
<h6 className={twMerge(baseTypo, "text-sm font-light", className)}>{children}</h6>
|
||||
<h6 className={twMerge(baseTypo, "text-sm font-light", className)}>
|
||||
{children}
|
||||
</h6>
|
||||
);
|
||||
|
||||
export const Link = ({className, ...rest}:NavLinkProps)=>
|
||||
export const Link = ({ className, ...rest }: NavLinkProps) => (
|
||||
<NavLink
|
||||
className={twMerge(
|
||||
"text-brand-primary hover:text-brand-primary-400",
|
||||
@@ -51,6 +64,7 @@ export const Link = ({className, ...rest}:NavLinkProps)=>
|
||||
)}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
|
||||
export const P = ({ className, children }: TypoProps) => (
|
||||
<p className={twMerge(baseTypo, className)}> {children} </p>
|
||||
|
||||
@@ -1,13 +1,5 @@
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faChevronLeft,
|
||||
faChevronRight,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import {
|
||||
dndSidePanelWidth,
|
||||
lastSelectedTab,
|
||||
selectedTab,
|
||||
sidePanelHeight,
|
||||
sidePanelVisible,
|
||||
sidePanelWidth,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
@@ -36,26 +28,7 @@ export const SidePanel = () => {
|
||||
top: 68,
|
||||
right: 84,
|
||||
width: sidePanelVisible.value ? displayWidth : 0,
|
||||
}}
|
||||
>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (sidePanelVisible.value) {
|
||||
lastSelectedTab.value = selectedTab.value;
|
||||
selectedTab.value = null;
|
||||
sidePanelVisible.value = false;
|
||||
} else {
|
||||
selectedTab.value = lastSelectedTab.value;
|
||||
sidePanelVisible.value = true;
|
||||
}
|
||||
}}
|
||||
className="absolute left-[-25px] flex rounded-l-lg bg-ui-controls px-2 py-3 align-middle text-sm text-white hover:bg-ui-controls-button"
|
||||
style={{ top: sidePanelHeight.value / 2 - 10 }}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={sidePanelVisible.value ? faChevronRight : faChevronLeft}
|
||||
/>
|
||||
</button>
|
||||
}}>
|
||||
<div className="relative block h-full w-full bg-ui-panel">
|
||||
<SidePanelTabs />
|
||||
</div>
|
||||
|
||||
@@ -32,7 +32,7 @@ interface Props {
|
||||
setPage: (page: Pages) => void;
|
||||
}
|
||||
|
||||
export const PageEditor = ({ setPage }: Props) => {
|
||||
export const PageEditor = () => {
|
||||
useSignals();
|
||||
|
||||
const [appUiState] = useContext(AppUiContext);
|
||||
@@ -47,8 +47,8 @@ export const PageEditor = ({ setPage }: Props) => {
|
||||
? dndSidePanelWidth.value
|
||||
: sidePanelWidth.value;
|
||||
const width = sidePanelVisible.value
|
||||
? pageWidth.value - dndWidth - 66
|
||||
: pageWidth.value - 66;
|
||||
? pageWidth.value - dndWidth - 84
|
||||
: pageWidth.value - 84;
|
||||
const height =
|
||||
dndTimelineHeight.value > -1
|
||||
? pageHeight.value - dndTimelineHeight.value - 68
|
||||
@@ -115,7 +115,7 @@ export const PageEditor = ({ setPage }: Props) => {
|
||||
onClick={handleOverlayClick}>
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<ControlsTopButtons />
|
||||
<Controls3D setPage={setPage} />
|
||||
<Controls3D />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { useContext, useEffect } from "react";
|
||||
|
||||
import { AppUiContext } from "~/contexts/AppUiContext";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { PageEditor } from "~/pages/PageEnigma/PageEditor";
|
||||
import { PageStyling } from "~/pages/PageEnigma/PageStyling";
|
||||
import { useParams } from "@remix-run/react";
|
||||
import { Pages } from "~/pages/PageEnigma/constants/page";
|
||||
|
||||
export const PageEnigmaComponent = () => {
|
||||
const [, dispatchAppUiState] = useContext(AppUiContext);
|
||||
const editor = useContext(EngineContext);
|
||||
const [page, setPage] = useState<Pages>(Pages.EDIT);
|
||||
const params = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -26,14 +23,5 @@ export const PageEnigmaComponent = () => {
|
||||
}
|
||||
}, [editor, dispatchAppUiState, params]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={page === Pages.EDIT ? "visible" : "hidden"}>
|
||||
<PageEditor setPage={setPage} />
|
||||
</div>
|
||||
<div className={page === Pages.STYLE ? "visible" : "hidden"}>
|
||||
<PageStyling setPage={setPage} />
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
return <PageEditor />;
|
||||
};
|
||||
@@ -1,100 +0,0 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import {
|
||||
faAngleLeft,
|
||||
faArrowsRotate,
|
||||
faFilm,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button } from "~/components";
|
||||
import { StyleSelection } from "~/pages/PageEnigma/comps/StyleSelection";
|
||||
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 { UIEvent, useCallback, useContext } from "react";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { filmLength, scale, stylizeScrollX } from "~/pages/PageEnigma/store";
|
||||
import { pageWidth } from "~/store";
|
||||
import useTimelineClick from "~/pages/PageEnigma/comps/Timeline/utils/useTimelineClick";
|
||||
import { Pages } from "~/pages/PageEnigma/constants/page";
|
||||
|
||||
interface Props {
|
||||
setPage: (page: Pages) => void;
|
||||
}
|
||||
|
||||
export const PageStyling = ({ setPage }: Props) => {
|
||||
useSignals();
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const generateFrame = async () => {
|
||||
await editorEngine?.generateFrame();
|
||||
};
|
||||
|
||||
const generateMovie = async () => {
|
||||
await editorEngine?.generateVideo();
|
||||
};
|
||||
|
||||
const switchEdit = () => {
|
||||
editorEngine?.switchEdit();
|
||||
setPage(Pages.EDIT);
|
||||
};
|
||||
|
||||
const onScroll = useCallback((event: UIEvent<HTMLDivElement>) => {
|
||||
stylizeScrollX.value = event.currentTarget.scrollLeft;
|
||||
}, []);
|
||||
|
||||
const onTimelineClick = useTimelineClick(Pages.STYLE);
|
||||
|
||||
return (
|
||||
<div className="w-screen">
|
||||
<TopBar pageName="Stylization" />
|
||||
<div className="flex w-full justify-center">
|
||||
<div className="rounded-b-lg bg-ui-controls p-2">
|
||||
<Button variant="action" onClick={() => switchEdit()}>
|
||||
<FontAwesomeIcon icon={faAngleLeft} />
|
||||
Back to Scene
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5 flex flex-col items-center gap-6">
|
||||
<PreviewImages />
|
||||
<div className="flex flex-col items-center">
|
||||
<StyleSelection />
|
||||
<div className="flex w-full justify-center gap-4 rounded-b-lg bg-ui-panel pb-5">
|
||||
<Button
|
||||
icon={faArrowsRotate}
|
||||
variant="action"
|
||||
onClick={generateFrame}>
|
||||
Update Preview
|
||||
</Button>
|
||||
<Button icon={faFilm} variant="primary" onClick={generateMovie}>
|
||||
Generate Movie
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="fixed bottom-0 left-0 w-full border-t border-t-action-600 bg-ui-panel"
|
||||
onClick={onTimelineClick}>
|
||||
<TimerGrid page={Pages.STYLE} />
|
||||
<div className="flex w-full ">
|
||||
<div className="block h-[30px] w-[204px]" />
|
||||
<div
|
||||
className="h-[30px] overflow-x-auto overflow-y-hidden"
|
||||
onScroll={onScroll}
|
||||
style={{
|
||||
width: pageWidth.value - 264,
|
||||
}}>
|
||||
<div
|
||||
style={{
|
||||
width: filmLength.value * 60 * 4 * scale.value + 42,
|
||||
height: 60,
|
||||
}}>
|
||||
<Scrubber page={Pages.STYLE} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -5,18 +5,27 @@ import {
|
||||
QueueClip,
|
||||
QueueKeyframe,
|
||||
UpdateTime,
|
||||
MediaItem,
|
||||
} from "~/pages/PageEnigma/models";
|
||||
import { toTimelineActions } from "./toTimelineActions";
|
||||
|
||||
type UnionedActionTypes = fromEngineActions | toEngineActions | toTimelineActions;
|
||||
type UnionedDataTypes = QueueClip | UpdateTime | QueueKeyframe | ClipUI[] | MediaItem;
|
||||
type UnionedActionTypes =
|
||||
| fromEngineActions
|
||||
| toEngineActions
|
||||
| toTimelineActions;
|
||||
type UnionedDataTypes =
|
||||
| QueueClip
|
||||
| UpdateTime
|
||||
| QueueKeyframe
|
||||
| ClipUI[]
|
||||
| MediaItem
|
||||
| null;
|
||||
|
||||
export type QueueSubscribeType = {
|
||||
action: UnionedActionTypes;
|
||||
data: UnionedDataTypes;
|
||||
}
|
||||
action: UnionedActionTypes;
|
||||
data: UnionedDataTypes;
|
||||
};
|
||||
import { ClipUI } from "../datastructures/clips/clip_ui";
|
||||
import { MediaItem } from "~/pages/PageEnigma/models";
|
||||
class Queue {
|
||||
private _queue: Record<
|
||||
string,
|
||||
@@ -27,10 +36,7 @@ class Queue {
|
||||
> = {};
|
||||
private _subscribers: Record<
|
||||
string,
|
||||
(entry: {
|
||||
action: UnionedActionTypes;
|
||||
data: UnionedDataTypes;
|
||||
}) => void
|
||||
(entry: { action: UnionedActionTypes; data: UnionedDataTypes }) => void
|
||||
> = {};
|
||||
|
||||
public publish({
|
||||
@@ -40,7 +46,7 @@ class Queue {
|
||||
}: {
|
||||
queueName: string;
|
||||
action: UnionedActionTypes;
|
||||
data: UnionedDataTypes ;
|
||||
data: UnionedDataTypes;
|
||||
}) {
|
||||
if (!this._queue[queueName]) {
|
||||
this._queue[queueName] = [];
|
||||
|
||||
@@ -12,11 +12,19 @@ export enum toEngineActions {
|
||||
// data=QueueClip
|
||||
DELETE_KEYFRAME = "delete_keyframe",
|
||||
// data=QueueKeyframe
|
||||
ENTER_EDIT_STATE = "enter_edit",
|
||||
// data=null
|
||||
ENTER_PREVIEW_STATE = "enter_preview",
|
||||
// data=null
|
||||
GENERATE_VIDEO = "generate_video",
|
||||
// data=null
|
||||
MUTE = "mute",
|
||||
// data={version, type, group, object_uuid} if lipsync
|
||||
// data={version, type, group} if global_audio
|
||||
PLAY_CLIP = "play_clip",
|
||||
// data=QueueClip
|
||||
TOGGLE_CAMERA_STATE = "toggle_camera",
|
||||
// data=null
|
||||
UNMUTE = "unmute",
|
||||
// data={version, type, group, object_uuid} if lipsync
|
||||
// data={version, type, group} if global_audio
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
import {
|
||||
faAngleRight,
|
||||
faArrowsRotate,
|
||||
faArrowsUpDownLeftRight,
|
||||
faUpRightAndDownLeftFromCenter,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { Button, ButtonIconSelect } from "~/components";
|
||||
import { ButtonIconSelect } from "~/components";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { useContext } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Pages } from "~/pages/PageEnigma/constants/page";
|
||||
|
||||
interface Props {
|
||||
setPage: (page: Pages) => void;
|
||||
}
|
||||
|
||||
export const Controls3D = ({ setPage }: Props) => {
|
||||
export const Controls3D = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const handleMoveArrows = () => {
|
||||
@@ -52,32 +45,21 @@ export const Controls3D = ({ setPage }: Props) => {
|
||||
}
|
||||
};
|
||||
|
||||
const changeStylize = () => {
|
||||
editorEngine?.switchPreview();
|
||||
setPage(Pages.STYLE);
|
||||
};
|
||||
|
||||
const modes = [
|
||||
{ value: "move", icon: faArrowsUpDownLeftRight },
|
||||
{ value: "rotate", icon: faArrowsRotate },
|
||||
{ value: "scale", icon: faUpRightAndDownLeftFromCenter },
|
||||
{ value: "move", icon: faArrowsUpDownLeftRight, text: "Move" },
|
||||
{ value: "rotate", icon: faArrowsRotate, text: "Rotate" },
|
||||
{ value: "scale", icon: faUpRightAndDownLeftFromCenter, text: "Scale" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-center">
|
||||
<div className="rounded-b-lg border-x border-b border-ui-panel-border bg-ui-controls p-2 text-white">
|
||||
<div className="rounded-b-lg border-x border-b border-ui-panel-border bg-ui-controls p-1.5 text-white">
|
||||
<div className="flex items-center justify-center gap-3">
|
||||
<ButtonIconSelect
|
||||
options={modes}
|
||||
onOptionChange={handleModeChange}
|
||||
/>
|
||||
|
||||
<span className="h-6 w-0 border-l border-white/[0.2]" />
|
||||
|
||||
<Button variant="primary" onClick={() => changeStylize()}>
|
||||
Stylize <FontAwesomeIcon icon={faAngleRight} />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,27 +1,31 @@
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
import { EngineContext } from "../../../../contexts/EngineContext";
|
||||
import { Button, Input, LoadingDotsTyping } from "~/components";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button, LoadingDotsTyping } from "~/components";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faCameraViewfinder } from "@fortawesome/pro-solid-svg-icons";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
import { editorState, EditorStates } from "~/pages/PageEnigma/store/engine";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const PreviewEngineCamera = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
//take data from egine context
|
||||
|
||||
useSignals();
|
||||
const [showLoader, setShowLoader] = useState<boolean>(true);
|
||||
const [currentMessage, setCurrentMessage] =
|
||||
useState<string>("Enter Camera View");
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => setShowLoader(false), 1000);
|
||||
}, []);
|
||||
|
||||
if (editorState.value === EditorStates.PREVIEW) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
editorEngine?.switchCameraView();
|
||||
if (currentMessage === "Enter Camera View") {
|
||||
setCurrentMessage("Exit Camera View");
|
||||
} else {
|
||||
setCurrentMessage("Enter Camera View");
|
||||
}
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.TOGGLE_CAMERA_STATE,
|
||||
data: null,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -36,9 +40,10 @@ export const PreviewEngineCamera = () => {
|
||||
<Button
|
||||
variant="action"
|
||||
onClick={handleButtonCameraView}
|
||||
className="px-2.5 py-1 text-sm"
|
||||
>
|
||||
{currentMessage}
|
||||
className="px-2.5 py-1 text-sm">
|
||||
{editorState.value === EditorStates.EDIT
|
||||
? "Enter Camera View"
|
||||
: "Exit Camera View"}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="box relative overflow-hidden rounded-b-lg border border-gray-600">
|
||||
|
||||
@@ -10,6 +10,7 @@ import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager"
|
||||
import { Button } from "~/components";
|
||||
import { AudioItemElements } from "./audioItemElements";
|
||||
import { AudioTabPages } from "./types";
|
||||
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
|
||||
import { InferenceElement } from "./inferenceElement";
|
||||
|
||||
|
||||
@@ -37,8 +38,9 @@ export const PageLibrary = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="p-4 flex items-center justify-start gap-2 ">
|
||||
<div className="w-full overflow-x-auto px-4">
|
||||
<TabTitle title="Audio" />
|
||||
<div className="mb-4 flex items-center justify-start gap-2">
|
||||
<button
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
sidePanelVisible,
|
||||
} from "~/pages/PageEnigma/store/sidePanel";
|
||||
import { tabList } from "./tabList";
|
||||
import { editorState, EditorStates } from "~/pages/PageEnigma/store/engine";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
|
||||
export const SidePanelMenu = () => {
|
||||
useSignals();
|
||||
@@ -27,25 +31,30 @@ export const SidePanelMenu = () => {
|
||||
maxWidth: 84,
|
||||
right: 0,
|
||||
top: 70,
|
||||
}}
|
||||
>
|
||||
}}>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
{tabList.map((tab) => (
|
||||
<button
|
||||
key={tab.value}
|
||||
className={[
|
||||
"flex flex-col items-center rounded-lg px-2 py-3",
|
||||
"flex flex-col items-center rounded-lg px-2 py-3 transition-all duration-200 hover:bg-assets-selectedTab/70",
|
||||
tab.value === selectedTab?.value?.value
|
||||
? "bg-assets-selectedTab opacity-100"
|
||||
: "opacity-50",
|
||||
? "bg-assets-selectedTab opacity-100 hover:bg-assets-selectedTab"
|
||||
: "opacity-60",
|
||||
].join(" ")}
|
||||
onClick={() => {
|
||||
selectedTab.value = tab;
|
||||
if (!sidePanelVisible.value) {
|
||||
sidePanelVisible.value = true;
|
||||
}
|
||||
}}
|
||||
>
|
||||
if (editorState.value === EditorStates.PREVIEW) {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ENTER_EDIT_STATE,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<div>
|
||||
<img src={tab.icon} alt={tab.title} width={20} height={20} />
|
||||
</div>
|
||||
|
||||
@@ -1,123 +0,0 @@
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
import { Button, ItemPicker, Label, Textarea } from "~/components";
|
||||
|
||||
import { AppUiContext } from "~/contexts/AppUiContext";
|
||||
import { APPUI_ACTION_TYPES, APPUI_VIEW_MODES } from "~/reducers";
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { ArtStyle } from "../../js/api_manager";
|
||||
|
||||
export const TabStylization = () => {
|
||||
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
|
||||
const [selection, setSelection] = useState<ArtStyle>(ArtStyle.Anime2DFlat);
|
||||
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const handlePickingStylizer = (picked: ArtStyle) => {
|
||||
console.log(`Picked style: ${picked}`);
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.art_style = picked;
|
||||
setSelection(picked);
|
||||
};
|
||||
|
||||
const onChangeHandlerNegative = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.negative_prompt = e.target.value;
|
||||
console.log(e.target.value);
|
||||
};
|
||||
|
||||
const onChangeHandlerPositive = (
|
||||
e: React.ChangeEvent<HTMLTextAreaElement>,
|
||||
) => {
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.positive_prompt = e.target.value;
|
||||
console.log(e.target.value);
|
||||
};
|
||||
|
||||
const handleChangeViewMode = () => {
|
||||
if (dispatchAppUiState !== null) {
|
||||
dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.ON_CHANGE_VIEW_MODE,
|
||||
payload: {
|
||||
viewMode:
|
||||
appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? APPUI_VIEW_MODES.EDITOR
|
||||
: APPUI_VIEW_MODES.SIDE_BY_SIDE,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-full flex-col gap-3 p-4">
|
||||
<div className="flex flex-col">
|
||||
<Label>Select Base Style</Label>
|
||||
<div className="grid grid-cols-4 gap-2">
|
||||
<ItemPicker
|
||||
label="Anime"
|
||||
type={ArtStyle.Anime2DFlat}
|
||||
selected={selection === ArtStyle.Anime2DFlat}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/0.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Pixel"
|
||||
type={ArtStyle.PixelArt}
|
||||
selected={selection === ArtStyle.PixelArt}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/1.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Pixar"
|
||||
type={ArtStyle.Cartoon3D}
|
||||
selected={selection === ArtStyle.Cartoon3D}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/2.webp"
|
||||
/>
|
||||
<ItemPicker
|
||||
label="Stylized"
|
||||
type={ArtStyle.ComicBook}
|
||||
selected={selection === ArtStyle.ComicBook}
|
||||
onSelected={handlePickingStylizer}
|
||||
src="/resources/avatars/3.webp"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Textarea
|
||||
label="Positive Prompt"
|
||||
className="h-32 w-full"
|
||||
name="positive-prompt"
|
||||
placeholder="Type here to describe your scene"
|
||||
onChange={onChangeHandlerPositive}
|
||||
resize="none"
|
||||
/>
|
||||
<Textarea
|
||||
label="Negative Prompt"
|
||||
className="h-32 w-full"
|
||||
name="negative-prompt"
|
||||
placeholder="Type here to filter out the things you don't want in the scene"
|
||||
onChange={onChangeHandlerNegative}
|
||||
resize="none"
|
||||
/>
|
||||
<div className="mt-3 flex justify-center gap-2">
|
||||
<Button onClick={handleChangeViewMode}>
|
||||
{appUiState?.viewMode === APPUI_VIEW_MODES.SIDE_BY_SIDE
|
||||
? "Back to Scene"
|
||||
: "Preview Side by Side"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,42 @@
|
||||
import { faAngleLeft, faClose } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ButtonIcon } from "~/components";
|
||||
import {
|
||||
lastSelectedTab,
|
||||
selectedTab,
|
||||
sidePanelVisible,
|
||||
} from "~/pages/PageEnigma/store";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
onBack?: () => void;
|
||||
}
|
||||
|
||||
export function TabTitle({ title, onBack }: Props) {
|
||||
const onClose = () => {
|
||||
lastSelectedTab.value = selectedTab.value;
|
||||
selectedTab.value = null;
|
||||
sidePanelVisible.value = false;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between pb-3">
|
||||
{onBack ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={onBack}>
|
||||
<FontAwesomeIcon icon={faAngleLeft} />
|
||||
</button>
|
||||
<div className="align-middle text-base font-bold">{title}</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="align-middle text-base font-bold">{title}</div>
|
||||
)}
|
||||
<ButtonIcon
|
||||
onClick={onClose}
|
||||
icon={faClose}
|
||||
size="lg"
|
||||
className="h-auto w-auto opacity-75 hover:opacity-50"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,33 +5,31 @@ import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/It
|
||||
import { Button } from "~/components";
|
||||
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
|
||||
|
||||
export const AnimationTab = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
|
||||
<div className="w-full overflow-x-auto p-4 pb-0">
|
||||
<TabTitle title="Animation" />
|
||||
<div className="mb-4 flex justify-start gap-2">
|
||||
<button
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
animationFilter.value === AssetFilterOption.ALL ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (animationFilter.value = AssetFilterOption.ALL)}
|
||||
>
|
||||
onClick={() => (animationFilter.value = AssetFilterOption.ALL)}>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
animationFilter.value === AssetFilterOption.MINE ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (animationFilter.value = AssetFilterOption.MINE)}
|
||||
disabled={!animationItems.value.some((item) => item.isMine)}
|
||||
>
|
||||
disabled={!animationItems.value.some((item) => item.isMine)}>
|
||||
My Animations
|
||||
</button>
|
||||
<button
|
||||
@@ -40,13 +38,11 @@ export const AnimationTab = () => {
|
||||
animationFilter.value === AssetFilterOption.BOOKMARKED
|
||||
? "active"
|
||||
: "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() =>
|
||||
(animationFilter.value = AssetFilterOption.BOOKMARKED)
|
||||
}
|
||||
disabled={!animationItems.value.some((item) => item.isBookmarked)}
|
||||
>
|
||||
disabled={!animationItems.value.some((item) => item.isBookmarked)}>
|
||||
Bookmarked
|
||||
</button>
|
||||
</div>
|
||||
@@ -55,8 +51,7 @@ export const AnimationTab = () => {
|
||||
<Button
|
||||
icon={faCirclePlus}
|
||||
variant="action"
|
||||
className="w-full py-3 text-sm font-medium"
|
||||
>
|
||||
className="w-full py-3 text-sm font-medium">
|
||||
Upload Animation
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -5,33 +5,31 @@ import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/It
|
||||
import { Button } from "~/components";
|
||||
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
|
||||
|
||||
export const CameraTab = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
|
||||
<div className="w-full overflow-x-auto p-4 pb-0">
|
||||
<TabTitle title="Camera" />
|
||||
<div className="mb-4 flex justify-start gap-2">
|
||||
<button
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
cameraFilter.value === AssetFilterOption.ALL ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (cameraFilter.value = AssetFilterOption.ALL)}
|
||||
>
|
||||
onClick={() => (cameraFilter.value = AssetFilterOption.ALL)}>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
cameraFilter.value === AssetFilterOption.MINE ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (cameraFilter.value = AssetFilterOption.MINE)}
|
||||
disabled={!cameraItems.value.some((item) => item.isMine)}
|
||||
>
|
||||
disabled={!cameraItems.value.some((item) => item.isMine)}>
|
||||
My Cameras
|
||||
</button>
|
||||
<button
|
||||
@@ -40,11 +38,9 @@ export const CameraTab = () => {
|
||||
cameraFilter.value === AssetFilterOption.BOOKMARKED
|
||||
? "active"
|
||||
: "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (cameraFilter.value = AssetFilterOption.BOOKMARKED)}
|
||||
disabled={!cameraItems.value.some((item) => item.isBookmarked)}
|
||||
>
|
||||
disabled={!cameraItems.value.some((item) => item.isBookmarked)}>
|
||||
Bookmarked
|
||||
</button>
|
||||
</div>
|
||||
@@ -53,8 +49,7 @@ export const CameraTab = () => {
|
||||
<Button
|
||||
icon={faCirclePlus}
|
||||
variant="action"
|
||||
className="w-full py-3 text-sm font-medium"
|
||||
>
|
||||
className="w-full py-3 text-sm font-medium">
|
||||
Create Camera Movement
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/It
|
||||
import { Button } from "~/components";
|
||||
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
|
||||
import { shapeItems } from "~/pages/PageEnigma/store";
|
||||
import { GetMediaByUser, GetMediaListResponse } from "~/api/media_files/GetMediaByUser";
|
||||
import { AssetFilterOption, AssetType, MediaItem } from "~/pages/PageEnigma/models";
|
||||
@@ -79,27 +80,26 @@ export const ObjectsTab = ({ type }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full overflow-x-auto">
|
||||
<div className="mb-4 mt-4 flex justify-start gap-2 px-4">
|
||||
<div className="w-full overflow-x-auto p-4 pb-0">
|
||||
<TabTitle
|
||||
title={type === AssetType.CHARACTER ? "Characters" : "Objects"}
|
||||
/>
|
||||
<div className="mb-4 flex justify-start gap-2">
|
||||
<button
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
assetFilter.value === AssetFilterOption.ALL ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (assetFilter.value = AssetFilterOption.ALL)}
|
||||
>
|
||||
onClick={() => (assetFilter.value = AssetFilterOption.ALL)}>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
className={twMerge(
|
||||
"filter-tab",
|
||||
assetFilter.value === AssetFilterOption.MINE ? "active" : "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (assetFilter.value = AssetFilterOption.MINE)}
|
||||
disabled={!items.value.some((item) => item.isMine)}
|
||||
>
|
||||
disabled={!items.value.some((item) => item.isMine)}>
|
||||
My {type === AssetType.CHARACTER ? "Characters" : "Objects"}
|
||||
</button>
|
||||
<button
|
||||
@@ -108,11 +108,9 @@ export const ObjectsTab = ({ type }: Props) => {
|
||||
assetFilter.value === AssetFilterOption.BOOKMARKED
|
||||
? "active"
|
||||
: "",
|
||||
"disabled",
|
||||
)}
|
||||
onClick={() => (assetFilter.value = AssetFilterOption.BOOKMARKED)}
|
||||
disabled={!items.value.some((item) => item.isBookmarked)}
|
||||
>
|
||||
disabled={!items.value.some((item) => item.isBookmarked)}>
|
||||
Bookmarked
|
||||
</button>
|
||||
</div>
|
||||
@@ -121,8 +119,7 @@ export const ObjectsTab = ({ type }: Props) => {
|
||||
<Button
|
||||
icon={faCirclePlus}
|
||||
variant="action"
|
||||
className="w-full py-3 text-sm font-medium"
|
||||
>
|
||||
className="w-full py-3 text-sm font-medium">
|
||||
Upload {type === AssetType.CHARACTER ? "Character" : "Object"}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
|
||||
import { shapeFilter, shapeItems } from "~/pages/PageEnigma/store";
|
||||
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
|
||||
|
||||
export const ShapeTab = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="px-4 pt-4 text-base font-bold">Shapes</div>
|
||||
<div className="w-full overflow-y-auto px-4 pt-4">
|
||||
<div className="w-full overflow-x-auto p-4 pb-0">
|
||||
<TabTitle title="Shapes" />
|
||||
<div className="mb-4 flex justify-start gap-2">
|
||||
<ItemElements
|
||||
items={shapeItems.value}
|
||||
assetFilter={shapeFilter.value}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
|
||||
import { H4 } from "~/components";
|
||||
import { ImgHTMLAttributes } from "react";
|
||||
|
||||
interface ItemPickerProps extends ImgHTMLAttributes<HTMLImageElement> {
|
||||
label: string;
|
||||
type: ArtStyle;
|
||||
selected: boolean;
|
||||
onSelected: (picked: ArtStyle) => void;
|
||||
}
|
||||
|
||||
export const ItemPicker = ({
|
||||
label,
|
||||
type,
|
||||
selected = false,
|
||||
onSelected,
|
||||
width,
|
||||
height,
|
||||
...imgProps
|
||||
}: ItemPickerProps) => {
|
||||
const handleSelected = () => {
|
||||
onSelected(type);
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
className={twMerge(
|
||||
"relative cursor-pointer overflow-hidden rounded-lg border-2 transition-colors ease-in-out",
|
||||
selected ? "border-brand-primary" : "border-ui-border",
|
||||
)}
|
||||
style={{
|
||||
minWidth: (width as number) + 4,
|
||||
minHeight: (height as number) + 4,
|
||||
maxWidth: (width as number) + 4,
|
||||
maxHeight: (height as number) + 4,
|
||||
}}
|
||||
onClick={handleSelected}>
|
||||
<img
|
||||
className="object-fill"
|
||||
{...imgProps}
|
||||
alt="style"
|
||||
style={{
|
||||
minWidth: width,
|
||||
minHeight: height,
|
||||
maxWidth: width,
|
||||
maxHeight: height,
|
||||
}}
|
||||
/>
|
||||
<div className="absolute left-0 top-0 h-full w-full bg-gradient-to-t from-gray-700" />
|
||||
<H4 className="absolute bottom-[1px] left-[6px] text-start text-[13px] drop-shadow-md">
|
||||
{label}
|
||||
</H4>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
import { ChangeEvent, useContext, useState } from "react";
|
||||
|
||||
import { Textarea } from "~/components";
|
||||
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faRandom } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { RandomTexts } from "~/pages/PageEnigma/constants/RandomTexts";
|
||||
|
||||
export const Prompts = () => {
|
||||
const editorEngine = useContext(EngineContext);
|
||||
const [textBufferPositive, setTextBufferPositive] = useState("");
|
||||
const [textBufferNegative, setTextBufferNegative] = useState("");
|
||||
|
||||
const onChangeHandlerNegative = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.negative_prompt = event.target.value;
|
||||
setTextBufferNegative(event.target.value);
|
||||
};
|
||||
|
||||
const onChangeHandlerPositive = (event: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
if (editorEngine == null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.positive_prompt = event.target.value;
|
||||
setTextBufferPositive(event.target.value);
|
||||
};
|
||||
|
||||
const generateRandomTextPositive = () => {
|
||||
const randomIndex = Math.floor(Math.random() * RandomTexts.length);
|
||||
const randomText = RandomTexts[randomIndex];
|
||||
setTextBufferPositive(randomText);
|
||||
};
|
||||
|
||||
const generateRandomTextNegative = () => {
|
||||
const randomIndex = Math.floor(Math.random() * RandomTexts.length);
|
||||
const randomText = RandomTexts[randomIndex];
|
||||
setTextBufferNegative(randomText);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3 rounded-t-lg bg-ui-panel py-4">
|
||||
<div className="relative w-full">
|
||||
<Textarea
|
||||
label="Enter a Prompt"
|
||||
className="w-full"
|
||||
rows={3}
|
||||
name="positive-prompt"
|
||||
placeholder="Type here to describe your scene"
|
||||
onChange={onChangeHandlerPositive}
|
||||
resize="none"
|
||||
required
|
||||
value={textBufferPositive}
|
||||
/>
|
||||
<div className="absolute right-0 top-[2px]">
|
||||
<button
|
||||
className="flex items-center text-xs font-medium text-[#FC6B68] transition-colors duration-100 hover:text-[#FC6B68]/85 "
|
||||
onClick={generateRandomTextPositive}>
|
||||
<FontAwesomeIcon icon={faRandom} className="me-2" />
|
||||
Randomize
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative w-full">
|
||||
<Textarea
|
||||
label="Negative Prompt"
|
||||
className="w-full"
|
||||
rows={2}
|
||||
name="negative-prompt"
|
||||
placeholder="Type here to filter out the things you don't want in the scene"
|
||||
onChange={onChangeHandlerNegative}
|
||||
resize="none"
|
||||
value={textBufferNegative}
|
||||
/>
|
||||
<div className="absolute right-0 top-[2px]">
|
||||
<button
|
||||
className="flex items-center text-xs font-medium text-[#FC6B68] transition-colors duration-100 hover:text-[#FC6B68]/85"
|
||||
onClick={generateRandomTextNegative}>
|
||||
<FontAwesomeIcon icon={faRandom} className="me-2" />
|
||||
Randomize
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import { Button } from "~/components";
|
||||
import { faArrowsRotate, faFilm } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { editorState, EditorStates } from "~/pages/PageEnigma/store/engine";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import Queue from "~/pages/PageEnigma/Queue/Queue";
|
||||
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
|
||||
|
||||
export function StyleButtons() {
|
||||
useSignals();
|
||||
|
||||
const switchPreview = async () => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ENTER_PREVIEW_STATE,
|
||||
data: null,
|
||||
});
|
||||
};
|
||||
|
||||
const switchEdit = async () => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.ENTER_EDIT_STATE,
|
||||
data: null,
|
||||
});
|
||||
};
|
||||
|
||||
const generateMovie = async () => {
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.GENERATE_VIDEO,
|
||||
data: null,
|
||||
});
|
||||
};
|
||||
|
||||
console.log("state", editorState.value);
|
||||
|
||||
return (
|
||||
<div className="flex w-full flex-col justify-center gap-4 rounded-b-lg bg-ui-panel">
|
||||
<div className="w-full">
|
||||
{editorState.value === EditorStates.EDIT && (
|
||||
<>
|
||||
<div className="mb-2 text-sm font-medium">
|
||||
Render the current camera view with AI
|
||||
</div>
|
||||
<Button
|
||||
icon={faArrowsRotate}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={switchPreview}>
|
||||
Preview Frame
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{editorState.value === EditorStates.PREVIEW && (
|
||||
<Button
|
||||
icon={faArrowsRotate}
|
||||
variant="action"
|
||||
className="w-full"
|
||||
onClick={switchEdit}>
|
||||
Return to Edit
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<div className="text-sm font-medium">
|
||||
When you're done, render your entire animation with AI
|
||||
</div>
|
||||
<div className="mb-2 text-xs text-white/70">
|
||||
(This may take several minutes)
|
||||
</div>
|
||||
<Button
|
||||
icon={faFilm}
|
||||
variant="primary"
|
||||
className="w-full"
|
||||
onClick={generateMovie}>
|
||||
Generate Movie
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import { useContext, useState } from "react";
|
||||
|
||||
import { ButtonIcon } from "~/components";
|
||||
|
||||
import { EngineContext } from "~/contexts/EngineContext";
|
||||
import { ArtStyle } from "~/pages/PageEnigma/js/api_manager";
|
||||
import { faAngleLeft, faAngleRight } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { styleList } from "~/pages/PageEnigma/styleList";
|
||||
import { sidePanelHeight, sidePanelWidth } from "~/pages/PageEnigma/store";
|
||||
import { ItemPicker } from "./ItemPicker";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export const StyleSelection = () => {
|
||||
useSignals();
|
||||
const [selection, setSelection] = useState<ArtStyle>(styleList[0].type);
|
||||
const [scrollPosition, setScrollPosition] = useState(0);
|
||||
|
||||
const shownImageCount = Math.floor((sidePanelWidth.value - 32) / 98);
|
||||
const imageWidth =
|
||||
90 + (sidePanelWidth.value - 32 - shownImageCount * 98) / shownImageCount;
|
||||
const imageHeight = (54 * imageWidth) / 90;
|
||||
|
||||
const imageRows = Math.min(
|
||||
Math.max(Math.floor((sidePanelHeight.value - 520) / imageHeight), 2),
|
||||
4,
|
||||
);
|
||||
|
||||
const maxWidth = Math.ceil(styleList.length / imageRows);
|
||||
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const handlePickingStylizer = (picked: ArtStyle) => {
|
||||
setSelection(picked);
|
||||
if (editorEngine === null) {
|
||||
console.log("Editor is null");
|
||||
return;
|
||||
}
|
||||
editorEngine.art_style = picked;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 rounded-t-lg bg-ui-panel">
|
||||
<div className="flex flex-col">
|
||||
<div className="relative">
|
||||
<div
|
||||
className="relative overflow-hidden"
|
||||
style={{
|
||||
width: sidePanelWidth.value - 32,
|
||||
height: imageHeight * imageRows + 8 * imageRows,
|
||||
}}>
|
||||
<div
|
||||
className="absolute flex flex-col gap-1 transition-all duration-300 ease-in-out"
|
||||
style={{
|
||||
width: styleList.length * 54,
|
||||
left: scrollPosition * (imageWidth + 8) * -1,
|
||||
top: 0,
|
||||
}}>
|
||||
{[...Array(imageRows).keys()].map((_, rowCount) => (
|
||||
<div key={rowCount} className="flex gap-1">
|
||||
{styleList
|
||||
.filter((_, index) => index % imageRows === rowCount)
|
||||
.map((style) => (
|
||||
<ItemPicker
|
||||
key={style.type}
|
||||
label={style.label}
|
||||
type={style.type}
|
||||
selected={selection === style.type}
|
||||
onSelected={handlePickingStylizer}
|
||||
src={style.image}
|
||||
width={imageWidth}
|
||||
height={imageHeight}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
{scrollPosition > 0 && (
|
||||
<div className="pointer-events-none absolute left-[-10px] top-0 h-full w-12">
|
||||
<div className="flex h-full w-full items-center justify-start">
|
||||
<ButtonIcon
|
||||
icon={faAngleLeft}
|
||||
onClick={() =>
|
||||
setScrollPosition(
|
||||
Math.max(
|
||||
scrollPosition - Math.max(shownImageCount - 1, 1),
|
||||
0,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="pointer-events-auto h-6 w-6 rounded-full bg-white/80 text-gray-800/75 hover:bg-white/100 hover:text-gray-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{scrollPosition < maxWidth - shownImageCount && (
|
||||
<div className="pointer-events-none absolute right-[-32px] top-0 h-full w-12">
|
||||
<div className="flex h-full w-full items-center justify-end pr-6">
|
||||
<ButtonIcon
|
||||
icon={faAngleRight}
|
||||
onClick={() =>
|
||||
setScrollPosition(
|
||||
Math.min(
|
||||
scrollPosition + Math.max(shownImageCount - 1, 1),
|
||||
maxWidth - shownImageCount,
|
||||
),
|
||||
)
|
||||
}
|
||||
className="pointer-events-auto h-6 w-6 rounded-full bg-white/80 text-gray-800/75 hover:bg-white/100 hover:text-gray-800"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
|
||||
import { StyleSelection } from "./StyleSelection";
|
||||
import { Prompts } from "./Prompts";
|
||||
import { StyleButtons } from "./StyleButtons";
|
||||
import { sidePanelHeight } from "~/pages/PageEnigma/store";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
|
||||
export function StylizeTab() {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="overflow-y-auto overflow-x-hidden p-4"
|
||||
style={{ height: sidePanelHeight.value }}>
|
||||
<TabTitle title="Transform your animation with AI" />
|
||||
<StyleSelection />
|
||||
<Prompts />
|
||||
<StyleButtons />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -3,8 +3,8 @@ import { ObjectsTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/Obje
|
||||
import { ShapeTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ShapeTab";
|
||||
import { AnimationTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AnimationTab";
|
||||
import { AudioTab } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/AudioTab";
|
||||
import { TabStylization } from "~/pages/PageEnigma/comps/SidePanelTabs/TabStylization";
|
||||
import { CameraTab } from "./itemTabs/CameraTab";
|
||||
import { StylizeTab } from "./itemTabs/StylizeTab/StylizeTab";
|
||||
export const tabList = [
|
||||
{
|
||||
icon: "/resources/icons/characters.png",
|
||||
@@ -36,4 +36,10 @@ export const tabList = [
|
||||
value: AssetType.AUDIO,
|
||||
component: <AudioTab />,
|
||||
},
|
||||
{
|
||||
icon: "/resources/icons/audios.png",
|
||||
title: "AI Stylize",
|
||||
value: AssetType.STYLE,
|
||||
component: <StylizeTab />,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,86 @@
|
||||
export const RandomTexts = [
|
||||
"Hello, world!",
|
||||
"Welcome to the future.",
|
||||
"The quick brown fox jumps over the lazy dog.",
|
||||
"In a hole in the ground there lived a hobbit.",
|
||||
"Once upon a time in a land far, far away.",
|
||||
"Can't believe you've done this.",
|
||||
"Sometimes, I wish I was an octopus, so I could slap eight people at once.",
|
||||
"Explaining why one is right is not arguing, it's just clarifying.",
|
||||
"Wallets are like onions; opening them brings tears.",
|
||||
"I'm not lazy, I'm on energy-saving mode.",
|
||||
"When life gives you melons, dyslexia might be involved.",
|
||||
"Atoms are untrustworthy; they make up everything.",
|
||||
"Glue history books are impossible to put down.",
|
||||
"Time travel jokes are funny in any era, but you might have missed it.",
|
||||
"I would lose weight, but I hate losing.",
|
||||
"A messy room is just an open-air museum.",
|
||||
"Trying to fail and succeeding is a real conundrum.",
|
||||
"Procrastination is just engaging in numerous side quests.",
|
||||
"Being early for tomorrow is a new kind of punctuality.",
|
||||
"Adulthood is the longest test run that never ends.",
|
||||
"Beauty sleep's advertising is misleading.",
|
||||
"I'm on a whiskey diet. I've lost three days already.",
|
||||
"Magical beds remember all forgotten tasks.",
|
||||
"Pillows are the unsung heroes of hairstyling.",
|
||||
"I'm not saying I'm Batman, but have you ever seen me and Batman in the same room?",
|
||||
"Couches are the battlegrounds for avoiding problems.",
|
||||
"Life's brevity demands self-laughter or outsourcing it.",
|
||||
"Being a limited edition is far from weird.",
|
||||
"Office chairs that spin make the job worth it.",
|
||||
"Wake-up time defines personhood, not the time of day.",
|
||||
"Math problems multiply in silence.",
|
||||
"Physical conservatism is the new fitness trend.",
|
||||
"Extra fries misunderstood as exercise is a common error.",
|
||||
"I'm fluent in three languages: English, sarcasm, and profanity.",
|
||||
"Multitasking involves listening, ignoring, and forgetting simultaneously.",
|
||||
"Indecisiveness is a choice, sometimes.",
|
||||
"I'm really good at stuff until people watch me do that stuff.",
|
||||
"Correctness needs no argument, just explanation.",
|
||||
"Coffee relationships are the most committed ones.",
|
||||
"Rush hour is the greatest paradox.",
|
||||
"I'm not bossy. I just have better ideas.",
|
||||
"Silence and duct tape both have their merits.",
|
||||
"I'm not procrastinating. I'm under a lot of pressure and very well could be a diamond soon.",
|
||||
"Battery saver mode is essential for work avoidance.",
|
||||
"Exploration is just being lost with style.",
|
||||
"Pressing harder on a remote control seems like a valid effort when batteries are weak.",
|
||||
"I'm convinced that bugs in software are just features in disguise.",
|
||||
"Adulthood is getting excited about a new sponge for the kitchen, I've accepted this.",
|
||||
"Life's too short, I always smile while I still have all my teeth.",
|
||||
"In video games, I've learned that encountering enemies often means you're going in the right direction.",
|
||||
"I'm often on the hunt for motivation, but it's like searching for a 404 error.",
|
||||
"Debating with a computer, I've realized, is as effective as trying to solve algebra by chewing bubblegum.",
|
||||
"During rush hour, I remind myself that patience is indeed a virtue.",
|
||||
"I transformed a shy friend into a social butterfly, and now I wonder what I've unleashed.",
|
||||
"The quest for sleep, sanity, and a peaceful hobbit life is something I'm still on.",
|
||||
"My bed has a magical ability to remind me of forgotten tasks at night.",
|
||||
"When the Wi-Fi goes down, I discover family members are actually nice people.",
|
||||
"A cookie in each hand, I believe, is the true definition of a balanced diet.",
|
||||
"Why use Google when I'm convinced someone believes they have all the answers?",
|
||||
"Exploring the great indoors, I've found, is because outside is overrated.",
|
||||
"I'm fluent in three languages: English, sarcasm, and late-night coding.",
|
||||
"Why wait for a time machine when I have memories and music?",
|
||||
"The best way to avoid chores, I've found, is to pretend not to know they exist.",
|
||||
"If I can't find the sunshine, I've learned to be the person who switches on the light.",
|
||||
"Realizing too late that adulthood is a trap is a lesson I've learned the hard way.",
|
||||
"The art of being wise, I've discovered, is knowing what to overlook, like unread emails.",
|
||||
"Why call it fast food if I have to wait in line for it?",
|
||||
"I've discovered that my spirit animal is a sloth on weekdays.",
|
||||
"Making a 'quick' phone call, I've realized, is never quick.",
|
||||
"The mystery of how one sock always disappears in the laundry remains unsolved, I've noticed.",
|
||||
"Finding out that my emergency contact is a pizza delivery service was a surprise.",
|
||||
"The realization that autocorrect has become my worst enema... I mean, enemy, is frustrating.",
|
||||
"Wondering why my coffee tastes like I should have made it myself is a daily ritual.",
|
||||
"The silent joy of canceling plans is an underrated pleasure I cherish.",
|
||||
"Discovering that my daily workout is trying to dodge spoilers online is a modern struggle.",
|
||||
"Realizing that my phone's battery life lasts longer than my motivation is a harsh reality.",
|
||||
"The paradox of being a perfectionist procrastinator is a real struggle I face.",
|
||||
"Finding comfort in the fact that chocolate understands me is a sweet relief.",
|
||||
"Realizing I'm not late, I'm just on a creative time schedule is how I cope.",
|
||||
"Discovering that my hobby is collecting dust on my gym membership was an eye-opener.",
|
||||
"The irony of being a tech expert but unable to fix my sleep schedule is not lost on me.",
|
||||
"Understanding that my to-do list is more of a suggestion than a requirement helps me sleep at night.",
|
||||
"The thrill of solving a bug in code is the coder's version of a plot twist, I've found.",
|
||||
"Realizing that my idea of a balanced diet is having a piece of tech in both hands is a modern dilemma.",
|
||||
];
|
||||
@@ -35,6 +35,7 @@ import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
|
||||
import { fromEngineActions } from "~/pages/PageEnigma/Queue/fromEngineActions";
|
||||
import { AssetType, MediaItem } from "~/pages/PageEnigma/models";
|
||||
import { loadingBarData, loadingBarIsShowing } from "~/store/loadingBar";
|
||||
import { editorState, EditorStates } from "~/pages/PageEnigma/store/engine";
|
||||
|
||||
// Main editor class that will call everything else all you need to call is " initialize() ".
|
||||
class Editor {
|
||||
@@ -253,7 +254,9 @@ class Editor {
|
||||
// Find the container element
|
||||
const container = document.getElementById("video-scene-container");
|
||||
|
||||
if (container == null) { return; }
|
||||
if (container == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Use the container's dimensions
|
||||
const width = container.offsetWidth;
|
||||
@@ -268,7 +271,6 @@ class Editor {
|
||||
this.camera.layers.enable(0);
|
||||
this.camera.layers.enable(1); // This camera does not see this layer
|
||||
|
||||
|
||||
this.timeline.camera = this.camera;
|
||||
|
||||
this.render_camera = new THREE.PerspectiveCamera(
|
||||
@@ -278,7 +280,6 @@ class Editor {
|
||||
200,
|
||||
);
|
||||
|
||||
|
||||
this.render_camera.layers.disable(1); // This camera does not see this layer );
|
||||
|
||||
// Base WebGL render and clock for delta time.
|
||||
@@ -328,10 +329,11 @@ class Editor {
|
||||
// MIDDLE: THREE.MOUSE.ROTATE,
|
||||
// RIGHT: THREE.MOUSE.PAN,
|
||||
//}; // Blender Style
|
||||
this.orbitControls.mouseButtons = {
|
||||
LEFT: THREE.MOUSE.ROTATE,
|
||||
MIDDLE: THREE.MOUSE.DOLLY,
|
||||
RIGHT: THREE.MOUSE.PAN }; // Standard
|
||||
this.orbitControls.mouseButtons = {
|
||||
LEFT: THREE.MOUSE.ROTATE,
|
||||
MIDDLE: THREE.MOUSE.DOLLY,
|
||||
RIGHT: THREE.MOUSE.PAN,
|
||||
}; // Standard
|
||||
|
||||
this.control = new TransformControls(this.camera, this.renderer.domElement);
|
||||
this.control.space = "local"; // Local transformation mode
|
||||
@@ -438,7 +440,7 @@ class Editor {
|
||||
console.log(result);
|
||||
}
|
||||
|
||||
public async testTestTimelineEvents() { }
|
||||
public async testTestTimelineEvents() {}
|
||||
|
||||
public async loadScene(scene_media_token: string) {
|
||||
this.dispatchAppUiState({
|
||||
@@ -589,6 +591,7 @@ class Editor {
|
||||
type: APPUI_ACTION_TYPES.SHOW_CONTROLPANELS_SCENEOBJECT,
|
||||
});
|
||||
this.updateSelectedUI();
|
||||
editorState.value = EditorStates.CAMERA_VIEW;
|
||||
if (this.activeScene.hot_items) {
|
||||
this.activeScene.hot_items.forEach((element) => {
|
||||
element.visible = false;
|
||||
@@ -616,6 +619,7 @@ class Editor {
|
||||
this.dispatchAppUiState({
|
||||
type: APPUI_ACTION_TYPES.HIDE_CONTROLPANELS_SCENEOBJECT,
|
||||
});
|
||||
editorState.value = EditorStates.EDIT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -812,15 +816,15 @@ class Editor {
|
||||
if (this.selected === undefined) {
|
||||
return 0;
|
||||
}
|
||||
let posCombo =
|
||||
const posCombo =
|
||||
this.selected.position.x +
|
||||
this.selected.position.y +
|
||||
this.selected.position.z;
|
||||
let rotCombo =
|
||||
const rotCombo =
|
||||
this.selected.rotation.x +
|
||||
this.selected.rotation.y +
|
||||
this.selected.rotation.z;
|
||||
let sclCombo =
|
||||
const sclCombo =
|
||||
this.selected.scale.x + this.selected.scale.y + this.selected.scale.z;
|
||||
return posCombo + rotCombo + sclCombo;
|
||||
}
|
||||
@@ -952,10 +956,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`,
|
||||
@@ -1094,15 +1098,18 @@ class Editor {
|
||||
const stylePreview: HTMLVideoElement | null = document.getElementById(
|
||||
"styled-preview",
|
||||
) as HTMLVideoElement;
|
||||
this.rawRenderer.setSize(stylePreview.width, stylePreview.height);
|
||||
this.render_camera.aspect = stylePreview.width / stylePreview.height;
|
||||
if (stylePreview != null) {
|
||||
this.rawRenderer.setSize(stylePreview.width, stylePreview.height);
|
||||
this.render_camera.aspect = stylePreview.width / stylePreview.height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switchPreview() {
|
||||
if (this.switchPreviewToggle == false) {
|
||||
async switchPreview() {
|
||||
if (!this.switchPreviewToggle) {
|
||||
this.switchPreviewToggle = true;
|
||||
this.generateFrame();
|
||||
editorState.value = EditorStates.PREVIEW;
|
||||
await this.generateFrame();
|
||||
if (this.cameraViewControls) {
|
||||
this.cameraViewControls.enabled = false;
|
||||
}
|
||||
@@ -1110,18 +1117,21 @@ class Editor {
|
||||
}
|
||||
|
||||
switchEdit() {
|
||||
if (this.switchPreviewToggle == true) {
|
||||
if (this.switchPreviewToggle) {
|
||||
this.switchPreviewToggle = false;
|
||||
this.canvasRenderCamReference = document.getElementById("camera-view");
|
||||
this.rawRenderer = new THREE.WebGLRenderer({
|
||||
antialias: false,
|
||||
canvas: this.canvasRenderCamReference,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
if (this.camera_person_mode == true) {
|
||||
this.switchCameraView();
|
||||
}
|
||||
this.activeScene.renderMode(false);
|
||||
editorState.value = EditorStates.EDIT;
|
||||
setTimeout(() => {
|
||||
this.canvasRenderCamReference = document.getElementById("camera-view");
|
||||
this.rawRenderer = new THREE.WebGLRenderer({
|
||||
antialias: false,
|
||||
canvas: this.canvasRenderCamReference,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
if (this.camera_person_mode) {
|
||||
this.switchCameraView();
|
||||
}
|
||||
this.activeScene.renderMode(false);
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1147,14 +1157,15 @@ class Editor {
|
||||
this.activeScene.renderMode(false);
|
||||
this.onWindowResize();
|
||||
|
||||
this.canvasRenderCamReference = document.getElementById("raw-preview");
|
||||
this.canvasRenderCamReference = document.getElementById("video-scene");
|
||||
this.rawRenderer = new THREE.WebGLRenderer({
|
||||
antialias: false,
|
||||
canvas: this.canvasRenderCamReference,
|
||||
preserveDrawingBuffer: true,
|
||||
});
|
||||
if (this.camera_person_mode == false) {
|
||||
if (!this.camera_person_mode) {
|
||||
this.switchCameraView();
|
||||
editorState.value = EditorStates.PREVIEW;
|
||||
}
|
||||
this.activeScene.renderMode(true);
|
||||
|
||||
@@ -1166,27 +1177,30 @@ class Editor {
|
||||
const blob = new Blob([output.buffer], { type: "video/mp4" });
|
||||
this.generating_preview = false;
|
||||
|
||||
const url = await this.api_manager.uploadMediaFrameGeneration(
|
||||
blob,
|
||||
"render.mp4",
|
||||
this.art_style,
|
||||
this.positive_prompt,
|
||||
this.negative_prompt,
|
||||
);
|
||||
console.log(url);
|
||||
try {
|
||||
const url = await this.api_manager.uploadMediaFrameGeneration(
|
||||
blob,
|
||||
"render.mp4",
|
||||
this.art_style,
|
||||
this.positive_prompt,
|
||||
this.negative_prompt,
|
||||
);
|
||||
console.log(url);
|
||||
|
||||
const stylePreview: HTMLVideoElement | null = document.getElementById(
|
||||
"styled-preview",
|
||||
) as HTMLVideoElement;
|
||||
if (stylePreview) {
|
||||
stylePreview.src = url;
|
||||
} else {
|
||||
console.log("No style preview window.");
|
||||
const stylePreview: HTMLVideoElement | null = document.getElementById(
|
||||
"video-scene",
|
||||
) as HTMLVideoElement;
|
||||
if (stylePreview) {
|
||||
stylePreview.src = url;
|
||||
} else {
|
||||
console.log("No style preview window.");
|
||||
}
|
||||
|
||||
return Promise.resolve(url);
|
||||
} catch (err: any) {
|
||||
console.log(err.message);
|
||||
return Promise.resolve("");
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
resolve(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1316,7 +1330,7 @@ class Editor {
|
||||
if (!container) return;
|
||||
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
for (let entry of entries) {
|
||||
for (const entry of entries) {
|
||||
const { width, height } = entry.contentRect;
|
||||
this.camera.aspect = width / height;
|
||||
this.camera.updateProjectionMatrix();
|
||||
@@ -1340,7 +1354,7 @@ class Editor {
|
||||
}
|
||||
|
||||
if (event.button !== 0) {
|
||||
let camera_pos = new THREE.Vector3(
|
||||
const camera_pos = new THREE.Vector3(
|
||||
parseFloat(this.camera.position.x.toFixed(2)),
|
||||
parseFloat(this.camera.position.y.toFixed(2)),
|
||||
parseFloat(this.camera.position.z.toFixed(2)),
|
||||
@@ -1350,13 +1364,17 @@ class Editor {
|
||||
}
|
||||
|
||||
onkeydown(event: any) {
|
||||
if (event.key === 'f' && this.selected && this.orbitControls) {
|
||||
if (event.key === "f" && this.selected && this.orbitControls) {
|
||||
this.orbitControls.target.copy(this.selected.position);
|
||||
this.orbitControls.maxDistance = 4;
|
||||
this.orbitControls.update();
|
||||
this.orbitControls.maxDistance = 999;
|
||||
} else if (event.key === ' ') {
|
||||
if(this.rendering == false && this.switchPreviewToggle == false && this.selectedCanvas){
|
||||
} else if (event.key === " ") {
|
||||
if (
|
||||
this.rendering == false &&
|
||||
this.switchPreviewToggle == false &&
|
||||
this.selectedCanvas
|
||||
) {
|
||||
this.startPlayback();
|
||||
}
|
||||
}
|
||||
@@ -1375,7 +1393,7 @@ class Editor {
|
||||
|
||||
// When the mouse clicks the screen.
|
||||
onMouseClick() {
|
||||
let camera_pos = new THREE.Vector3(
|
||||
const camera_pos = new THREE.Vector3(
|
||||
parseFloat(this.camera.position.x.toFixed(2)),
|
||||
parseFloat(this.camera.position.y.toFixed(2)),
|
||||
parseFloat(this.camera.position.z.toFixed(2)),
|
||||
|
||||
@@ -16,6 +16,7 @@ import { fromEngineActions } from "../Queue/fromEngineActions";
|
||||
import { ClipGroup, ClipType } from "~/pages/PageEnigma/models/track";
|
||||
import { MediaItem } from "~/pages/PageEnigma/models";
|
||||
import Editor from "~/pages/PageEnigma/js/editor";
|
||||
import { editorState, EditorStates } from "~/pages/PageEnigma/store/engine";
|
||||
import EmotionEngine from "./emotion_engine";
|
||||
|
||||
// Every object uuid / entity has a track.
|
||||
@@ -70,7 +71,7 @@ export class TimeLine {
|
||||
emotion_engine: EmotionEngine,
|
||||
scene: Scene,
|
||||
camera: THREE.Camera,
|
||||
mouse: THREE.Vector2 | undefined
|
||||
mouse: THREE.Vector2 | undefined,
|
||||
) {
|
||||
this.editorEngine = editor;
|
||||
this.timeline_items = [];
|
||||
@@ -156,6 +157,18 @@ export class TimeLine {
|
||||
case toEngineActions.ADD_SHAPE:
|
||||
await this.addShape(data);
|
||||
break;
|
||||
case toEngineActions.ENTER_PREVIEW_STATE:
|
||||
await this.editorEngine.switchPreview();
|
||||
break;
|
||||
case toEngineActions.ENTER_EDIT_STATE:
|
||||
this.editorEngine.switchEdit();
|
||||
break;
|
||||
case toEngineActions.TOGGLE_CAMERA_STATE:
|
||||
this.editorEngine.switchCameraView();
|
||||
break;
|
||||
case toEngineActions.GENERATE_VIDEO:
|
||||
this.editorEngine.generateVideo();
|
||||
break;
|
||||
default:
|
||||
console.log("Action Not Wired", action);
|
||||
}
|
||||
@@ -164,8 +177,8 @@ export class TimeLine {
|
||||
public async addCharacter(data: { data: MediaItem }) {
|
||||
const media_id = data.data.media_id;
|
||||
const name = data.data.name;
|
||||
let pos = this.getPos();
|
||||
let new_data = { ...data.data };
|
||||
const pos = this.getPos();
|
||||
const new_data = { ...data.data };
|
||||
|
||||
const obj = await this.scene.load_glb(media_id);
|
||||
obj.userData["name"] = name;
|
||||
@@ -217,10 +230,13 @@ export class TimeLine {
|
||||
}
|
||||
|
||||
public getPos() {
|
||||
let raycaster = new THREE.Raycaster();
|
||||
const raycaster = new THREE.Raycaster();
|
||||
if (this.mouse && this.camera) {
|
||||
raycaster.setFromCamera(this.mouse, this.camera);
|
||||
const intersects = raycaster.intersectObjects(this.scene.scene.children, false);
|
||||
const intersects = raycaster.intersectObjects(
|
||||
this.scene.scene.children,
|
||||
false,
|
||||
);
|
||||
console.log(intersects);
|
||||
if (intersects.length > 0) {
|
||||
return intersects[0].point;
|
||||
@@ -230,7 +246,7 @@ export class TimeLine {
|
||||
}
|
||||
|
||||
public async addObject(data: { data: MediaItem }) {
|
||||
let pos = this.getPos();
|
||||
const pos = this.getPos();
|
||||
const media_id = data.data.media_id;
|
||||
const name = data.data.name;
|
||||
const obj = await this.scene.load_glb(media_id);
|
||||
@@ -240,7 +256,7 @@ export class TimeLine {
|
||||
}
|
||||
|
||||
public async addShape({ data }: { data: MediaItem }) {
|
||||
let pos = this.getPos();
|
||||
const pos = this.getPos();
|
||||
this.editorEngine.create_parim(data.media_id, pos);
|
||||
}
|
||||
|
||||
@@ -297,8 +313,10 @@ export class TimeLine {
|
||||
}
|
||||
|
||||
public deleteObject(object_uuid: string) {
|
||||
let object = this.scene.get_object_by_uuid(object_uuid);
|
||||
if (object?.name === "::CAM::") { return; }
|
||||
const object = this.scene.get_object_by_uuid(object_uuid);
|
||||
if (object?.name === "::CAM::") {
|
||||
return;
|
||||
}
|
||||
this.timeline_items = this.timeline_items.filter(
|
||||
(element) => element.object_uuid !== object_uuid,
|
||||
);
|
||||
@@ -471,15 +489,15 @@ export class TimeLine {
|
||||
public async updatePlayableClip(
|
||||
clip_uuid: string,
|
||||
updates: AnyJson,
|
||||
): Promise<void> { }
|
||||
): Promise<void> {}
|
||||
|
||||
public async deletePlayableClip(clip_uuid: string): Promise<void> { }
|
||||
public async deletePlayableClip(clip_uuid: string): Promise<void> {}
|
||||
|
||||
public async scrub(data: any): Promise<void> {
|
||||
if (this.is_playing) {
|
||||
return;
|
||||
}
|
||||
const value = Math.floor(data["data"]["currentTime"])
|
||||
const value = Math.floor(data["data"]["currentTime"]);
|
||||
this.setScrubberPosition(value);
|
||||
this.update();
|
||||
}
|
||||
@@ -492,7 +510,7 @@ export class TimeLine {
|
||||
});
|
||||
}
|
||||
|
||||
public async scrubberDidStop(offset_frame: number) { }
|
||||
public async scrubberDidStop(offset_frame: number) {}
|
||||
// public streaming events into the timeline from
|
||||
public async setScrubberPosition(offset: number) {
|
||||
this.scrubber_frame_position = offset; // in ms
|
||||
@@ -623,7 +641,7 @@ export class TimeLine {
|
||||
await this.animation_engine.clips[
|
||||
object.uuid + element.media_id
|
||||
].play(object);
|
||||
let fps = 60;
|
||||
const fps = 60;
|
||||
this.animation_engine.clips[object.uuid + element.media_id].step(
|
||||
this.scrubber_frame_position / fps, // Double FPS for best result.
|
||||
);
|
||||
@@ -659,4 +677,4 @@ export class TimeLine {
|
||||
currentTime: this.current_time,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
|
||||
export enum EditorStates {
|
||||
EDIT,
|
||||
CAMERA_VIEW,
|
||||
PREVIEW,
|
||||
}
|
||||
|
||||
export const editorState = signal<EditorStates>(EditorStates.EDIT);
|
||||
@@ -4,81 +4,81 @@ export const styleList = [
|
||||
{
|
||||
label: "Anime",
|
||||
type: ArtStyle.Anime2_5D,
|
||||
image: "/resources/avatars/0.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Anime Flat",
|
||||
type: ArtStyle.Anime2DFlat,
|
||||
image: "/resources/avatars/1.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Cartoon 3D",
|
||||
type: ArtStyle.Cartoon3D,
|
||||
image: "/resources/avatars/2.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Comic Book",
|
||||
type: ArtStyle.ComicBook,
|
||||
image: "/resources/avatars/3.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Ghibli",
|
||||
type: ArtStyle.AnimeGhibli,
|
||||
image: "/resources/avatars/4.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Ink Punk",
|
||||
type: ArtStyle.InkPunk,
|
||||
image: "/resources/avatars/5.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Ink Splash",
|
||||
type: ArtStyle.InkSplash,
|
||||
image: "/resources/avatars/6.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Ink Black and White",
|
||||
type: ArtStyle.InkBWStyle,
|
||||
image: "/resources/avatars/7.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Jo Jo Style",
|
||||
type: ArtStyle.JojoStyle,
|
||||
image: "/resources/avatars/8.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Paper Origami",
|
||||
type: ArtStyle.PaperOrigami,
|
||||
image: "/resources/avatars/9.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Pixel",
|
||||
type: ArtStyle.PixelArt,
|
||||
image: "/resources/avatars/10.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Pop Art",
|
||||
type: ArtStyle.PopArt,
|
||||
image: "/resources/avatars/11.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Realistic 1",
|
||||
type: ArtStyle.Realistic1,
|
||||
image: "/resources/avatars/12.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Realistic 2",
|
||||
type: ArtStyle.Realistic2,
|
||||
image: "/resources/avatars/13.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Anime Retro Neon",
|
||||
type: ArtStyle.AnimeRetroNeon,
|
||||
image: "/resources/avatars/14.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
{
|
||||
label: "Anime Standard",
|
||||
type: ArtStyle.AnimeStandard,
|
||||
image: "/resources/avatars/15.webp",
|
||||
image: "/resources/placeholders/style-placeholder.png",
|
||||
},
|
||||
];
|
||||
@@ -4,14 +4,14 @@
|
||||
|
||||
@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;
|
||||
@apply text-nowrap rounded-lg border-2 border-assets-background bg-assets-background px-2.5 py-1.5 text-sm font-medium text-white/80 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;
|
||||
@apply border-2 border-brand-primary text-white hover:border-brand-primary hover:bg-assets-background;
|
||||
}
|
||||
|
||||
.filter-tab.disabled {
|
||||
@apply hover:bg-assets-background disabled:text-white/50;
|
||||
.filter-tab:disabled {
|
||||
@apply border-transparent bg-assets-background/50 hover:border-transparent disabled:text-white/30;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Reference in new issue
Block a user