Merge pull request #184 from storytold/feature/style-tab

Move styling to tab from page
This commit is contained in:
ArEnSc authored and GitHub committed 2024-04-24 14:44:13 -04:00
commit 1eff37f0d4
31 files changed
+785 -485

No files matched your search

+7 -5
View File
@@ -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>
+27 -13
View File
@@ -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 -28
View File
@@ -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>
+4 -4
View File
@@ -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>
+2 -14
View File
@@ -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 />;
};
-100
View File
@@ -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>
);
};
+17 -11
View File
@@ -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&apos;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.",
];
+76 -58
View File
@@ -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)),
+33 -15
View File
@@ -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,
});
}
}
}
+9
View File
@@ -0,0 +1,9 @@
import { signal } from "@preact/signals-core";
export enum EditorStates {
EDIT,
CAMERA_VIEW,
PREVIEW,
}
export const editorState = signal<EditorStates>(EditorStates.EDIT);
+16 -16
View File
@@ -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 -4
View File
@@ -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