Merge branch 'test' into feature/style-tab

This commit is contained in:
Michael Chung committed 2024-04-24 00:13:04 -04:00
commit 82e18fca7d
57 files changed
+1725 -744

No files matched your search

+2 -60
View File
@@ -28,66 +28,8 @@ Fast Prototyping with Better Netlify Pipeline:
- [Netlify Functions Overview](https://docs.netlify.com/functions/overview)
- [Tailwind Docs](https://tailwindcss.com/docs)
## Netlify Setup
1. Install the [Netlify CLI](https://docs.netlify.com/cli/get-started/):
```sh
npm i -g netlify-cli
Setup
```
If you have previously installed the Netlify CLI, you should update it to the latest version:
```sh
npm i -g netlify-cli@latest
```
2. Sign up and log in to Netlify:
```sh
netlify login
```
3. Create a new site:
```sh
netlify init
```
## Development
Ensure all packages are installed by running:
```sh
npm install
```
Run
```sh
netlify dev
```
Open up [http://localhost:8888](http://localhost:8888), and you're ready to go!
### Serve your site locally
To serve your site locally in a production-like environment, run
```sh
netlify serve
```
Your site will be available at [http://localhost:8888](http://localhost:8888). Note that it will not auto-reload when you make changes.
## Deployment
There are two ways to deploy your app to Netlify, you can either link your app to your git repo and have it auto deploy changes to Netlify, or you can deploy your app manually. If you've followed the setup instructions already, all you need to do is run this:
```sh
# preview deployment
netlify deploy --build
# production deployment
netlify deploy --build --prod
npm run dev
```
+4 -1
View File
@@ -11,4 +11,7 @@ export const inferTts = `${apiHost}/tts/inference`;
export const listTts = `${apiHost}/tts/list`;
// User Media
export const listMediaByUser = (username:string) => `${apiHost}/v1/media_files/list/user/${username}`;
export const listMediaByUser = (username:string) => `${apiHost}/v1/media_files/list/user/${username}`;
// Any Media
export const getMediaFileByToken = (fileToken:string) => `${apiHost}/v1/media_files/file/${fileToken}`;
+56
View File
@@ -0,0 +1,56 @@
class BucketConfig {
isLocalDev: boolean;
constructor() {
this.isLocalDev =
document.location.host.includes("localhost") ||
document.location.host.includes("jungle.horse") ||
document.location.host.startsWith("dev.");
}
// TODO: Prevent callers with undefined/null paths
getGcsUrl(bucketRelativePath: string | undefined | null): string {
let bucket = this.getBucket();
let path = bucketRelativePath;
if (path !== undefined && path !== null && !path.startsWith("/")) {
path = "/" + path;
}
return `https://storage.googleapis.com/${bucket}${path}`;
}
private getBucket(): string {
return this.isLocalDev ? "dev-vocodes-public" : "vocodes-public";
}
getCdnUrl(
bucketRelativePath: string,
width?: number,
quality?: number
): string {
// const basePath = this.isLocalDev
// ? "https://dev-cdn.fakeyou.com"
// : "https://cdn.fakeyou.com";
const basePath = "https://cdn.fakeyou.com";
let path = bucketRelativePath?.startsWith("/")
? bucketRelativePath
: "/" + bucketRelativePath;
let resizeParams = "";
if (width || quality) {
resizeParams = "cdn-cgi/image/";
if (width) {
resizeParams += `width=${width},`;
}
if (quality) {
resizeParams += `quality=${quality},`;
}
resizeParams = resizeParams.slice(0, -1);
}
if (resizeParams) {
return `${basePath}/${resizeParams}${path}`;
} else {
return `${basePath}${path}`;
}
}
}
export { BucketConfig };
+34
View File
@@ -0,0 +1,34 @@
// interface ApiDetails { hostname: string, useSsl: boolean, }
const domainWithoutPort = document.location.host.split(":")[0];
const hostConfig = (host: string, useSsl = true) => ({
formatUrl: (endpoint = "") => `${ useSsl ? "https" : "http" }://${ host + endpoint }`,
host,
useSsl,
});
export default function GetApiHost() {
switch (domainWithoutPort) {
case "fakeyou.com":
case "staging.fakeyou.com":
return hostConfig("api.fakeyou.com");
case "storyteller.ai":
case "staging.storyteller.ai":
//return hostConfig("api.storyteller.ai");
return hostConfig("api.fakeyou.com");
case "storyteller.stream": // Storyteller.stream is deprecated and will be decommissioned in the future.
return hostConfig("api.storyteller.stream");
case "devproxy.fakeyou.com":
return hostConfig("api.fakeyou.com");
case "devproxy.storyteller.ai":
//return hostConfig("api.storyteller.ai");
return hostConfig("api.fakeyou.com");
case "dev.fakeyou.com":
return hostConfig("api.dev.fakeyou.com:12345",false); // false disables SSL
default:
return document.location.host.includes("localhost") ?
hostConfig("localhost:12345",document.location.protocol === "https:") :
hostConfig("api.fakeyou.com"); // default
}
};
+59
View File
@@ -0,0 +1,59 @@
import GetApiHost from "./GetApiHost";
// const { formatUrl } = GetApiHost();
type UrlRoutingFunction<UrlRouteArgs> = (urlRouteArgs: UrlRouteArgs) => string;
interface RequestHeaders {
[name: string]: string;
}
interface RouteSetup<UrlRouteArgs> {
method: string;
multipart?: boolean;
routingFunction: UrlRoutingFunction<UrlRouteArgs>;
}
const METHOD_OMITS_BODY: { [key: string]: boolean } = {
DELETE: false,
GET: true,
OPTIONS: true,
PATCH: false,
POST: false,
PUT: false,
};
const MakeRequest = <UrlRouteArgs, Request, Response, UrlParams>(routeSetup: RouteSetup<UrlRouteArgs>) : (urlRouteArgs: UrlRouteArgs, request: Request, queries?: UrlParams) => Promise<Response> => {
return async function(urlRouteArgs: UrlRouteArgs, request: Request, queries?: any) : Promise<Response> {
const newQueries = queries ? Object.keys(queries).map((key, i) => {
return `${ i ? "&" : "" }${ key }=${ Array.isArray(queries[key]) ? queries[key].join(`&${ key }=`) : queries[key] }`
}).join("") : null;
const endpoint = `${ routeSetup.routingFunction(urlRouteArgs) }${ newQueries ? "?" + newQueries : "" }`;
const method = routeSetup.method;
const methodOmitsBody = METHOD_OMITS_BODY[method] || false;
// return fetch(formatUrl(endpoint), {
return fetch(`https://api.fakeyou.com${ endpoint }`, {
method,
headers: {
"Accept": "application/json",
...methodOmitsBody? {} : { "Content-Type": "application/json" }
},
credentials: 'include',
...methodOmitsBody ? {} : { body: JSON.stringify(request) },
})
.then(res => res.json())
.then(res => {
if (!res.success && res && (res.voice_token || res.dataset_token)) {
return res;
}
else if (res && "success" in res) {
return res;
} else Promise.reject();
})
.catch(e => ({ success : false }));
}
};
export default MakeRequest;
+26
View File
@@ -0,0 +1,26 @@
import MakeRequest from "../MakeRequest";
import { MediaFile } from "./GetMedia";
import { Pagination } from "../_common/SharedFetchTypes";
export interface GetMediaRequest {}
export interface GetMediaListResponse {
pagination: Pagination;
success: boolean;
results: MediaFile[];
}
export interface GetMediaParams {
page_index: number;
}
export const GetMediaByUser = MakeRequest<
string,
GetMediaRequest,
GetMediaListResponse,
GetMediaParams
>({
method: "GET",
routingFunction: (userToken: string) =>
`/v1/media_files/list/user/${userToken}`,
});
+8 -7
View File
@@ -16,12 +16,8 @@ export const Button = ({
disabled,
...rest
}: ButtonPropsI) => {
const variant = disabled ? "disabled" : propsVariant;
function getVariantClassNames(variant: string) {
switch (variant) {
case "disabled": {
return "bg-brand-secondary-500 text-brand-secondary-300";
}
case "secondary": {
return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary";
}
@@ -30,20 +26,25 @@ export const Button = ({
}
case "primary":
default: {
return " bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
return "bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600";
}
}
}
const disabledClass = twMerge(
disabled ? "opacity-40 pointer-events-none" : "",
);
const className = twMerge(
"text-sm font-medium rounded-lg px-3 py-2 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 transition-all duration-150 flex gap-2 items-center justify-center",
getVariantClassNames(variant),
getVariantClassNames(propsVariant),
propsClassName,
disabledClass,
);
return (
<button className={className} disabled={disabled} {...rest}>
{icon && <FontAwesomeIcon icon={icon} size="sm"/>}
{icon && <FontAwesomeIcon icon={icon} size="sm" />}
{children}
</button>
);
+85 -23
View File
@@ -1,24 +1,35 @@
import { useRef } from "react";
import { twMerge } from "tailwind-merge";
import { disableHotkeyInput,enableHotkeyInput,DomLevels } from "~/pages/PageEnigma/store";
import {
disableHotkeyInput,
enableHotkeyInput,
DomLevels,
} from "~/pages/PageEnigma/store";
interface InputVectorProps {
x: number;
y: number;
z: number;
onChange: (v: { x: number; y: number; z: number }) => void;
increment?: number;
}
export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
export const InputVector = ({
x,
y,
z,
onChange,
increment = 0.1,
}: InputVectorProps) => {
const xRef = useRef<HTMLInputElement>(null);
const yRef = useRef<HTMLInputElement>(null);
const zRef = useRef<HTMLInputElement>(null);
const inputCommonClasses =
"relative w-14 h-7 rounded-r-lg bg-brand-secondary p-3 text-sm text-white transition-all duration-150 ease-in-out outline-none -outline-offset-2";
"relative h-6 rounded-r-lg bg-brand-secondary p-2 text-sm text-white transition-all duration-100 ease-in-out outline-none -outline-offset-2 text-end w-full hover:cursor-e-resize hover:bg-brand-secondary-900";
const wrapperCommonClasses =
"flex items-center before:inline-block before:w-10 before:h-10 before:bg-brand-primary before:text-white before:rounded-l-lg before:h-7 before:w-5 before:text-center before:justify-center before:items-center before:font-semibold before:flex before:align-middle before:leading-8 text-xs";
"relative flex items-center before:inline-block before:h-6 before:bg-brand-primary before:text-white before:rounded-l-lg before:w-1.5 before:text-center before:justify-center before:items-center before:font-semibold before:flex before:align-middle before:leading-8 text-xs";
function handleOnChange() {
const newVector = {
@@ -28,14 +39,67 @@ export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
};
onChange(newVector);
}
const blurAllInputs = () => {
xRef.current?.blur();
yRef.current?.blur();
zRef.current?.blur();
};
// For dragging the input value to increment/decrement
const handleMouseDown = (
e: React.MouseEvent,
ref: React.RefObject<HTMLInputElement>,
) => {
e.preventDefault();
blurAllInputs();
let isDragging = false;
let previousMousePosition = e.clientX;
const mouseMoveHandler = (e: MouseEvent) => {
isDragging = true;
const currentMousePosition = e.clientX;
const direction = currentMousePosition > previousMousePosition ? 1 : -1;
previousMousePosition = currentMousePosition;
const currentValue = Number(ref.current?.value) || 0;
const newValue = currentValue + direction * increment;
if (ref.current) {
ref.current.value = newValue.toFixed(1);
ref.current.blur();
}
onChange({
x: Number(xRef.current?.value) || 0,
y: Number(yRef.current?.value) || 0,
z: Number(zRef.current?.value) || 0,
});
};
const mouseUpHandler = () => {
document.removeEventListener("mousemove", mouseMoveHandler);
document.removeEventListener("mouseup", mouseUpHandler);
if (!isDragging) {
ref.current?.focus();
ref.current?.select();
}
};
document.addEventListener("mousemove", mouseMoveHandler);
document.addEventListener("mouseup", mouseUpHandler);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === "Enter") {
(e.currentTarget as HTMLInputElement).blur(); // Unfocus the input field on pressing Enter
}
};
return (
<div className="flex w-full justify-between gap-2">
<span
className={twMerge(
wrapperCommonClasses,
"before:bg-axis-x before:content-['X']",
)}
>
<div className="flex w-full flex-col justify-between gap-1.5">
<span className={twMerge(wrapperCommonClasses, "before:bg-axis-x")}>
<div className="absolute left-3.5 z-10 font-semibold">X</div>
<input
className={twMerge(inputCommonClasses, "focus:outline-axis-x")}
type="number"
@@ -44,14 +108,12 @@ export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
value={x}
onFocus={() => disableHotkeyInput(DomLevels.INPUT)}
onBlur={() => enableHotkeyInput(DomLevels.INPUT)}
onMouseDown={(e) => handleMouseDown(e, xRef)}
onKeyDown={handleKeyDown}
/>
</span>
<span
className={twMerge(
wrapperCommonClasses,
"before:bg-axis-y before:content-['Y']",
)}
>
<span className={twMerge(wrapperCommonClasses, "before:bg-axis-y")}>
<div className="absolute left-3.5 z-10 font-semibold">Y</div>
<input
className={twMerge(inputCommonClasses, "focus:outline-axis-y")}
type="number"
@@ -60,14 +122,12 @@ export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
value={y}
onFocus={() => disableHotkeyInput(DomLevels.INPUT)}
onBlur={() => enableHotkeyInput(DomLevels.INPUT)}
onMouseDown={(e) => handleMouseDown(e, yRef)}
onKeyDown={handleKeyDown}
/>
</span>
<span
className={twMerge(
wrapperCommonClasses,
"before:bg-axis-z before:content-['Z']",
)}
>
<span className={twMerge(wrapperCommonClasses, "before:bg-axis-z")}>
<div className="absolute left-3.5 z-10 font-semibold">Z</div>
<input
className={twMerge(inputCommonClasses, "focus:outline-axis-z")}
type="number"
@@ -76,6 +136,8 @@ export const InputVector = ({ x, y, z, onChange }: InputVectorProps) => {
value={z}
onFocus={() => disableHotkeyInput(DomLevels.INPUT)}
onBlur={() => enableHotkeyInput(DomLevels.INPUT)}
onMouseDown={(e) => handleMouseDown(e, zRef)}
onKeyDown={handleKeyDown}
/>
</span>
</div>
+18
View File
@@ -0,0 +1,18 @@
import { twMerge } from "tailwind-merge"
export const Pill = ({
className,
children,
}:{
className?: string,
children : React.ReactNode
})=>{
return (
<div className={twMerge(
"rounded-md text-xs font-bold bg-brand-primary py-0.5 px-1.5 w-fit",
className
)}>
{children}
</div>
)
}
+51 -47
View File
@@ -2,17 +2,21 @@ import { useEffect } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { Fragment, ReactNode } from "react";
import { twMerge } from "tailwind-merge";
import { disableHotkeyInput, enableHotkeyInput, DomLevels } from "~/pages/PageEnigma/store";
import {
disableHotkeyInput,
enableHotkeyInput,
DomLevels,
} from "~/pages/PageEnigma/store";
const DialogBackdrop = ()=>{
useEffect(()=>{
const DialogBackdrop = () => {
useEffect(() => {
disableHotkeyInput(DomLevels.DIALOGUE);
return()=>{
return () => {
enableHotkeyInput(DomLevels.DIALOGUE);
}
};
}, []);
return(
return (
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
@@ -20,57 +24,57 @@ const DialogBackdrop = ()=>{
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
leaveTo="opacity-0">
<div className="fixed inset-0 bg-black/60" />
</Transition.Child>
);
};
export const TransitionDialogue = ({
isOpen, title, onClose, className, children,
}:{
isOpen,
title,
onClose,
className,
children,
}: {
isOpen: boolean;
title?: ReactNode;
onClose:()=>void;
onClose: () => void;
className?: string;
children: ReactNode;
})=>{
return(
}) => {
return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog
as="div"
className="relative z-50 "
onClose={onClose}
>
<DialogBackdrop />
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className={twMerge("w-full max-w-lg transform rounded-xl border border-ui-panel-border bg-ui-panel p-5 text-left align-middle shadow-xl transition-all", className)}>
{title && (
<Dialog.Title
as="h4"
className="mb-4 text-xl font-bold text-white"
>
{title}
</Dialog.Title>
)}
{children}
</Dialog.Panel>
</Transition.Child>
</div>
<Dialog as="div" className="relative z-50 " onClose={onClose}>
<DialogBackdrop />
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95">
<Dialog.Panel
className={twMerge(
"w-full max-w-lg transform rounded-xl border border-ui-panel-border bg-ui-panel p-5 text-left align-middle shadow-xl transition-all",
className,
)}>
{title && (
<Dialog.Title
as="h4"
className="mb-4 text-xl font-bold text-white">
{title}
</Dialog.Title>
)}
{children}
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
</div>
</Dialog>
</Transition>
);
};
};
+2
View File
@@ -12,6 +12,7 @@ import { ListDropdown } from "./ListDropdown"
import { ListSearchDropdown } from "./ListSearchDropdown"
import { LoadingBar } from "./LoadingBar";
import { LoadingDots, LoadingDotsBricks, LoadingDotsTyping } from "./LoadingDots";
import { Pill } from "./Pill";
export {
Button,
@@ -30,4 +31,5 @@ export {
LoadingDots,
LoadingDotsBricks,
LoadingDotsTyping,
Pill
};
+18 -18
View File
@@ -8,11 +8,12 @@ interface ButtonDialoguePropsI {
buttonProps?: UnionedButtonProps;
confirmButtonProps?: UnionedButtonProps;
closeButtonProps?: UnionedButtonProps;
dialogProps?:{
dialogProps?: {
className?: string;
};
title?: string;
children: React.ReactNode;
showClose?: boolean;
}
export const ButtonDialogue = ({
@@ -22,6 +23,7 @@ export const ButtonDialogue = ({
closeButtonProps: unionedCloseButtonProps,
title,
children,
showClose = true,
}: ButtonDialoguePropsI) => {
const [isOpen, setIsOpen] = useState(false);
@@ -43,19 +45,20 @@ export const ButtonDialogue = ({
title={title}
isOpen={isOpen}
onClose={closeModal}
className={dialogProps.className}
>
<div className="mt-2">{children}</div>
className={dialogProps.className}>
<div className="mt-2 h-full">{children}</div>
<div className="mt-6 flex justify-end gap-2">
<Button
type="button"
onClick={closeModal}
{...closeButtonProps}
variant="secondary"
>
{closeButtonLabel}
</Button>
{showClose && (
<Button
type="button"
onClick={closeModal}
{...closeButtonProps}
variant="secondary">
{closeButtonLabel}
</Button>
)}
{confirmButtonProps && (
<Button
type="button"
@@ -65,15 +68,12 @@ export const ButtonDialogue = ({
confirmButtonProps.onClick(e);
}
closeModal();
}}
>
{confirmButtonProps.label
? confirmButtonProps.label
: "Confirm"}
}}>
{confirmButtonProps.label ? confirmButtonProps.label : "Confirm"}
</Button>
)}
</div>
</TransitionDialogue>
</TransitionDialogue>
</>
);
};
@@ -0,0 +1,128 @@
import { useState } from "react";
import {
H1, H4, H6, P,
Button,
ButtonLink,
ListDropdown,
ListSearchDropdown,
// LoadingBar,
LoadingDotsTyping,
LoadingDotsBricks,
} from "~/components";
export function SectionComponents () {
const [valueListDropdown, setValueListDropdown] = useState<string|undefined>(undefined);
const [valueListSearchDropdown, setValueListSearchDropdown] = useState<string|undefined>(undefined);
const testdata = [
{ name: 'Wade Cooper' },
{ name: 'Arlene Mccoy' },
{ name: 'Devon Webb' },
{ name: 'Tom Cook' },
{ name: 'Tanya Fox' },
{ name: 'Hellen Schmidt' },
]
// Progress Bar Usage States, Variables and Functions
// const [progress, setProgress] = useState(0);
// const loopRef = useRef<NodeJS.Timeout | null>(null);
// const pushProgressBar: ()=>void = useCallback(()=>{
// loopRef.current = setInterval(function timer(){
// setProgress((curr)=>{
// if (curr < 100){
// return curr+10;
// }else{
// if(loopRef.current)
// clearInterval(loopRef.current);
// return curr;
// }
// })}
// , 3000);
// }, []);
// useEffect(()=>{
// pushProgressBar();
// return ()=>{
// if(loopRef.current)
// clearInterval(loopRef.current)
// }
// },[progress]);
return(
<div className="flex flex-col gap-2 mb-4">
<H1>Components</H1>
<H4>Buttons</H4>
<div className="flex gap-2">
<Button>Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary">
Prelim Tertiary
</Button>
<Button disabled>Disabled</Button>
</div>
<div className="flex flex-col gap-2 mb-4">
<H4>ButtonLink</H4>
<ButtonLink to="/">Back to /</ButtonLink>
</div>
<div className="flex flex-col gap-2 mb-4 max-w-2xl">
<H4>List Dropdowns</H4>
<ListDropdown
list={testdata}
onSelect={(val)=>setValueListDropdown(val)}
/>
<P>Selected Item: <b>{valueListDropdown}</b></P>
<ListSearchDropdown
list={testdata}
onSelect={(val)=>setValueListSearchDropdown(val)}
/>
<P>Selected Item: <b>{valueListSearchDropdown}</b></P>
</div>
<div className="flex flex-col gap-2 mb-4">
<H4>Loading Dots</H4>
<div className="flex gap-2">
<div className="w-60 h-40 rounded-lg overflow-hidden">
<LoadingDotsTyping />
</div>
<div className="w-60 h-40 rounded-lg overflow-hidden">
<LoadingDotsBricks />
</div>
</div>
</div>
<div className="flex flex-col gap-2 mb-4 max-w-2xl">
<H4>Loading Bar</H4>
<H6>Loading Bar is currently Broken and not reusable</H6>
{/* <div className="flex gap-2 items-center">
<LoadingBar
progress={progress}
isShowing={progress !== 100}
wrapperClassName="rounded-lg"
message="this takes progress from parent"
/>
<Button
className="h-fit"
onClick={()=>setProgress(0)}
>
Reset
</Button>
</div>
<LoadingBar
label="labeling the bar"
message="displaying a message"
/>
<LoadingBar
label="useFakeTimer = 30000 (30secs)"
useFakeTimer={30000}
/>
<LoadingBar
label="useFakeTimer = 3000 (3secs)"
useFakeTimer={3000}
/>*/}
</div>
</div>
);
};
@@ -0,0 +1,21 @@
import {
H1,H2,H3,H4,H5,H6,P,Label,
} from "~/components";
export const SectionTypography = () => {
return(
<div className="flex flex-col gap-2 mb-4">
<H1>Typography</H1>
<H1>H1. This is an H1 Heading</H1>
<H2>H2. This is an H2 Heading</H2>
<H3>H3. This is an H3 Heading</H3>
<H4>H4. This is an H4 Heading</H4>
<Label>Label. Is essentially same as H4</Label>
<P>P. Is an normal Paragraph</P>
<H5>H5. This is an H5 Heading</H5>
<H6>H6. This is an H6 Heading</H6>
</div>
);
};
+19
View File
@@ -0,0 +1,19 @@
import { SectionTypography } from "./SectionTypography";
import { SectionComponents } from "./SectionComponents";
export const PageComponentLibrary = () => {
return (
<div
className="fixed w-full overflow-scroll"
style={{ height: "calc(100% - 72px)" }}
>
<div
className='bg-ui-panel w-10/12 max-w-7xl mx-auto my-6 rounded-lg p-6'
>
<SectionTypography />
<hr className="mt-2 mb-4" />
<SectionComponents />
</div>
</div>
);
}
-72
View File
@@ -1,72 +0,0 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { faChevronLeft } from "@fortawesome/pro-solid-svg-icons";
import { Button } from "~/components/Button";
import { ButtonLink } from "~/components/ButtonLink";
import { TopBarHelmet } from "~/modules/TopBarHelmet";
import { SidePanel } from "~/modules/SidePanel";
import { SceneWithCube } from "./SceneWithCube";
export const PageCube = () => {
const mountRef = useRef<HTMLDivElement | null>(null);
const sceneRef = useRef<SceneWithCube | null>(null);
const [{ x: rotationX, y: rotationY }, setRotations] = useState<{
x: number | undefined;
y: number | undefined;
}>({ x: undefined, y: undefined });
const toggleStopped = () => {
if (sceneRef.current !== null) {
sceneRef.current.stopped = !sceneRef.current.stopped;
}
};
const listeningXYRotation = useCallback(
({ x, y }: { x: number; y: number }) => {
setRotations({
x: Math.round(x * 100) / 100,
y: Math.round(y * 100) / 100,
});
},
[],
);
useEffect(() => {
//componentDidMount
sceneRef.current = new SceneWithCube(listeningXYRotation);
mountRef.current?.appendChild(sceneRef.current.renderer.domElement);
sceneRef.current.renderer.setAnimationLoop(() => {
sceneRef.current?.update();
});
// Clean up
return () => {
if (sceneRef.current)
mountRef.current?.removeChild(sceneRef.current.renderer.domElement);
};
}, []);
return (
<div className="grid grid-cols-12 gap-2 ">
<TopBarHelmet>
<ButtonLink to={"/"} variant="secondary" icon={faChevronLeft}>
Back to Dashboard
</ButtonLink>
</TopBarHelmet>
<div className="col-span-6 ">
<div className="MountingPoint" ref={mountRef} />
</div>
<div className="col-span-6 ">
<Button onClick={toggleStopped}> STOP </Button>
<p className="text-white">Ration X: {rotationX ? rotationX : ""}</p>
<p className="text-white">Ration Y: {rotationY ? rotationY : ""}</p>
</div>
<SidePanel>
<p className="text-white">Side Panel Context</p>
</SidePanel>
</div>
);
};
-45
View File
@@ -1,45 +0,0 @@
import * as THREE from 'three';
export class SceneWithCube {
scene: THREE.Scene;
camera: THREE.PerspectiveCamera;
renderer: THREE.WebGLRenderer;
cube: THREE.Mesh;
stopped: boolean;
callback: ({x,y}:{x:number;y:number})=>void;
update(){
if(!this.stopped){
this.cube.rotation.x += 0.01;
this.cube.rotation.y += 0.01;
if(this.callback && typeof this.callback === "function")
this.callback({
x: this.cube.rotation.x,
y: this.cube.rotation.y
})
}
this.renderer.render(this.scene, this.camera);
}
constructor(callback: ({x,y}:{x:number;y:number})=>void) {
const width = 1000;
const height = 750;
this.callback = callback;
this.stopped = false;
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(75, width/height, 0.1, 1000);
this.renderer = new THREE.WebGLRenderer();
this.renderer.setSize(width, height);
const geometry = new THREE.BoxGeometry();
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
this.cube = new THREE.Mesh(geometry, material);
this.scene.add(this.cube);
this.camera.position.z = 5;
}
}
-2
View File
@@ -1,2 +0,0 @@
export { PageCube } from "./PageCube";
+1 -1
View File
@@ -20,4 +20,4 @@ export const PageEnigma = () => {
/>
</TrackProvider>
);
};
};
@@ -7,8 +7,6 @@ export enum toEngineActions {
ADD_OBJECT = "add_object",
ADD_SHAPE = "add_shape",
// data= QueueKeyframe
DELETE_CLIP = "delete_clip",
// data=QueueClip
@@ -1,7 +1,8 @@
import { useContext, useEffect } from "react";
import { useContext, useEffect, useState } from "react";
import { Transition } from "@headlessui/react";
import {
faArrowRightArrowLeft,
faChevronDown,
faChevronUp,
faCube,
faTrash,
} from "@fortawesome/pro-solid-svg-icons";
@@ -19,9 +20,30 @@ import Queue from "~/pages/PageEnigma/Queue/Queue";
import { toTimelineActions } from "../../Queue/toTimelineActions";
import { QueueKeyframe } from "~/pages/PageEnigma/models";
// TODO this will be useful later to fix the bug on leading zeros
const formatNumber = (input: string): number => {
// Convert the input string to a number to remove leading zeros
const num = parseFloat(input);
// Convert the number back to a string with at least two decimal places
const str = num.toFixed(2);
return parseFloat(str);
}
// console.log(formatNumber("000123.4567")); // Outputs: "123.46"
// console.log(formatNumber("000123.4")); // Outputs: "123.40"
// console.log(formatNumber("000123")); // Outputs: "123.00"
// console.log(formatNumber("0000.00")); // Outputs: "0.00"
// console.log(formatNumber("0000.000001")); // Outputs: "0.00"
export const ControlPanelSceneObject = () => {
const editorEngine = useContext(EngineContext);
const [appUiState, dispatchAppUiState] = useContext(AppUiContext);
const [isCollapsed, setIsCollapsed] = useState(false);
const toggleCollapse = () => {
setIsCollapsed(!isCollapsed);
};
const position =
appUiState.controlPanel.currentSceneObject?.objectVectors?.position;
@@ -31,6 +53,7 @@ export const ControlPanelSceneObject = () => {
appUiState.controlPanel.currentSceneObject?.objectVectors?.scale;
const currentSceneObject = appUiState.controlPanel.currentSceneObject;
useEffect(() => {
// TODO this causes a subtle bug because it renders way too many times.
if (!appUiState.controlPanel.currentSceneObject) {
@@ -169,63 +192,73 @@ export const ControlPanelSceneObject = () => {
show={appUiState?.controlPanel.isShowing}
className={[
"absolute bottom-0 right-0",
"m-4 flex h-fit w-fit flex-col gap-2",
"m-4 flex h-fit w-56 flex-col gap-2",
"rounded-lg",
"border border-ui-panel-border",
"bg-ui-panel p-4 text-white",
"bg-ui-panel p-3.5 text-white",
].join(" ")}
enter="transition-opacity duration-100"
enter="transition-opacity duration-150"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity duration-100"
leave="transition-opacity duration-150"
leaveFrom="opacity-100"
leaveTo="opacity-0">
<div className="flex justify-between">
<div className="mb-1 flex justify-between">
<div className="flex items-center gap-2">
<FontAwesomeIcon icon={faCube} />
<p className="font-semibold">
<p className="max-w-36 truncate font-semibold">
{appUiState.controlPanel.currentSceneObject.object_name}
</p>
</div>
<div className="flex items-center gap-2 text-xs font-medium opacity-60">
<FontAwesomeIcon icon={faArrowRightArrowLeft} />
<p>Swap Object</p>
<FontAwesomeIcon
icon={isCollapsed ? faChevronUp : faChevronDown}
onClick={toggleCollapse}
className="cursor-pointer opacity-75 transition-opacity duration-100 ease-in-out hover:opacity-50"
/>
</div>
<Transition
show={!isCollapsed}
enter="transition-all duration-200 ease-in-out"
enterFrom="opacity-0 max-h-0"
enterTo="opacity-100 max-h-96"
leave="transition-all duration-200 ease-in-out"
leaveFrom="opacity-100 max-h-96"
leaveTo="opacity-0 max-h-0"
className={"flex flex-col gap-2 overflow-y-auto"}>
<div className="flex flex-col gap-1">
<H5>Location</H5>
<InputVector
x={position?.x || 0}
y={position?.y || 0}
z={position?.z || 0}
onChange={handlePositionChange}
/>
</div>
</div>
<span className="h-1" />
<div className="flex flex-col gap-1">
<H5>Position</H5>
<InputVector
x={position?.x || 0}
y={position?.y || 0}
z={position?.z || 0}
onChange={handlePositionChange}
/>
</div>
<div className="flex flex-col gap-1">
<H5>Rotation</H5>
<InputVector
x={rotation?.x || 0}
y={rotation?.y || 0}
z={rotation?.z || 0}
onChange={handleRotationChange}
increment={1}
/>
</div>
<div className="flex flex-col gap-1">
<H5>Rotation</H5>
<InputVector
x={rotation?.x || 0}
y={rotation?.y || 0}
z={rotation?.z || 0}
onChange={handleRotationChange}
/>
</div>
<div className="flex flex-col gap-1">
<H5>Scale</H5>
<InputVector
x={scale?.x || 0}
y={scale?.y || 0}
z={scale?.z || 0}
onChange={handleScaleChange}
/>
</div>
</Transition>
<div className="flex flex-col gap-1">
<H5>Scale</H5>
<InputVector
x={scale?.x || 0}
y={scale?.y || 0}
z={scale?.z || 0}
onChange={handleScaleChange}
/>
</div>
<span className="h-2" />
<div className="flex gap-2">
<div className="mt-2 flex gap-1.5">
<Button
variant="secondary"
className="grow"
@@ -6,6 +6,7 @@ import { ToasterContext } from "~/contexts/ToasterContext";
// import { APPUI_ACTION_TYPES } from "app/reducers";
import { TestFeaturesButtons } from "./TestFeaturesButtons";
import { Help } from "./Help";
export const ControlsTopButtons = () => {
const editorEngine = useContext(EngineContext);
@@ -49,8 +50,7 @@ export const ControlsTopButtons = () => {
label: "Load",
disabled: sceneToken === "",
onClick: handleButtonLoadScene,
}}
>
}}>
<Input
label="Please provide the Token of the scene you wished to load:"
onChange={(e) => {
@@ -69,8 +69,7 @@ export const ControlsTopButtons = () => {
label: "Save",
disabled: sceneName === "",
onClick: handleButtonSave,
}}
>
}}>
<Input
label="Please Enter a name for your scene"
onChange={(e) => {
@@ -89,8 +88,7 @@ export const ControlsTopButtons = () => {
disabled: mediaToken === "",
onClick: handleMediaToken,
}}
title="Add Scene Object via Media Token"
>
title="Add Scene Object via Media Token">
<Input
label="Please Enter a Media Token"
onChange={(e) => {
@@ -104,15 +102,14 @@ export const ControlsTopButtons = () => {
variant: "secondary",
label: "Help",
}}
title="Help"
>
<p>Do you need help?</p>
<p>Ask Michael about this project</p>
<p>Ask Miles about ThreeJS</p>
<p>Ask Wil about React</p>
dialogProps={{
className: "max-w-6xl w-auto",
}}
title="Help">
<Help />
</ButtonDialogue>
</div>
<TestFeaturesButtons debug={false}/>
<TestFeaturesButtons debug={false} />
</div>
);
};
@@ -0,0 +1,144 @@
import { Label } from "~/components";
const ShortcutsGroup = (props: {
label: string;
children?: React.ReactNode;
}) => (
<div className="flex flex-col gap-1">
<Label className="text-md font-semibold" {...props}>
{props.label}
</Label>
<div className="relative flex flex-col gap-2.5">
{props.children}
<div className="absolute top-0 h-full w-0.5 bg-white/15" />
</div>
</div>
);
const Shortcut = (props: { label: string; children: React.ReactNode }) => (
<div className="flex items-center gap-2 ps-4">
<span className="text-md w-[280px] font-medium opacity-80">
{props.label}
</span>
{props.children}
</div>
);
const Key = (props: { button: string }) => (
<div className="flex h-7 w-auto min-w-7 items-center justify-center rounded-md border-b-2 border-[#9E9EA6] bg-white px-2 pt-1 text-center text-sm font-bold text-ui-background">
{props.button}
</div>
);
const KeyGroup = (props: { children: React.ReactNode }) => (
<div className="flex gap-1">{props.children}</div>
);
const Mouse = (props: {
button: "left" | "middle" | "right";
label: string;
}) => (
<div className="flex items-center gap-2.5">
<img
alt={`${props.button} mouse`}
src={`/resources/icons/mouse_${props.button === "left" ? "lmb" : props.button === "middle" ? "mmb" : "rmb"}.png`}
className="h-7 w-auto object-contain"
/>
<span className="text-sm font-normal opacity-60">{props.label}</span>
</div>
);
const Plus = () => <div className="text-xl font-medium">+</div>;
export const Help = () => {
return (
<div className="mt-8 grid select-none grid-cols-2 gap-12 ps-4">
<div className="flex flex-col gap-8">
<ShortcutsGroup label="Navigation">
<Shortcut label="Pan View">
<Mouse button="right" label="(Drag)" />
</Shortcut>
<Shortcut label="Rotate View">
<Mouse button="middle" label="(Drag)" />
</Shortcut>
<Shortcut label="Zoom">
<Mouse button="middle" label="(Scroll)" />
</Shortcut>
{/* <Shortcut label="Free Fly">
<KeyGroup>
<Key button="W" />
<Key button="A" />
<Key button="S" />
<Key button="D" />
<Key button="Q" />
<Key button="E" />
</KeyGroup>
</Shortcut> */}
</ShortcutsGroup>
<ShortcutsGroup label="Interaction">
<Shortcut label="Select Object">
<Mouse button="left" label="(Click)" />
</Shortcut>
{/* <Shortcut label="Clear Selection">
<Key button="Esc" />
</Shortcut> */}
{/* <Shortcut label="Focus Selection">
<Key button="F" />
</Shortcut> */}
<Shortcut label="Delete Selection">
<Key button="Del" />
</Shortcut>
</ShortcutsGroup>
<ShortcutsGroup label="Side Panel">
<Shortcut label="Add Character">
<Mouse button="left" label="(Drag onto scene)" />
</Shortcut>
<Shortcut label="Add Animation">
<Mouse button="left" label="(Drag onto timeline)" />
</Shortcut>
<Shortcut label="Add Object">
<Mouse button="left" label="(Drag onto scene)" />
</Shortcut>
</ShortcutsGroup>
</div>
<div className="flex flex-col gap-8">
<ShortcutsGroup label="Timeline">
<Shortcut label="Select Clip">
<Mouse button="left" label="(Click)" />
</Shortcut>
<Shortcut label="Move Clip">
<Mouse button="left" label="(Drag)" />
</Shortcut>
<Shortcut label="Delete Selection">
<Key button="Del" />
</Shortcut>
<Shortcut label="Scroll">
<Mouse button="middle" label="(Scroll)" />
</Shortcut>
<Shortcut label="Side Scroll">
<Key button="Shift" />
<Plus />
<Mouse button="middle" label="(Scroll)" />
</Shortcut>
{/* <Shortcut label="Zoom">
<Key button="Ctrl/Cmd" />
<Plus />
<Mouse button="middle" label="(Scroll)" />
</Shortcut> */}
<Shortcut label="Add Keyframe to Selected Object">
<Key button="K" />
</Shortcut>
<Shortcut label="Select Keyframe">
<Mouse button="left" label="(Click)" />
</Shortcut>
<Shortcut label="Delete Selected Keyframe">
<Key button="Del" />
</Shortcut>
</ShortcutsGroup>
</div>
</div>
);
};
@@ -0,0 +1 @@
export { Help } from "./Help";
@@ -1,53 +1,40 @@
import { useCallback, useContext, useEffect, useState } from "react";
import { twMerge } from "tailwind-merge";
import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
import { faCirclePlus, faArrowsRotate } from "@fortawesome/pro-solid-svg-icons";
import { AppUiContext } from "~/contexts/AppUiContext";
import { AuthenticationContext } from "~/contexts/Authentication";
import { APPUI_ACTION_TYPES } from "~/reducers";
import { audioFilter, audioItems } from "~/pages/PageEnigma/store";
import {
AssetFilterOption,
MediaItem,
AssetType,
} from "~/pages/PageEnigma/models";
import { Button, ButtonIcon } from "~/components";
import { ItemElements } from "~/pages/PageEnigma/comps/SidePanelTabs/itemTabs/ItemElements";
import { MediaItem, AssetType } from "~/pages/PageEnigma/models";
import { audioItemsFromServer } from "~/pages/PageEnigma/store/mediaFromServer";
import { ListAudioByUser } from "./listAudioByUser";
import { ListAudioByUser } from "./utilities";
import { inferenceJobs } from "~/pages/PageEnigma/store/inferenceJobs";
import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager";
import { TabTitle } from "~/pages/PageEnigma/comps/SidePanelTabs/comps/TabTitle";
import { PageLibrary } from "./pageLibrary";
import { PageTTS } from "./pageTTS";
import { AudioTabPages } from "./types";
export const AudioTabComponent = () => {
useSignals();
const [state, setState] = useState({
firstLoad: false,
fetchingUserAudio: false,
page: AudioTabPages.LIBRARY,
});
const { authState } = useContext(AuthenticationContext);
const [, dispatchAppUiState] = useContext(AppUiContext);
const handleOpenTtsDialogue = () => {
dispatchAppUiState({
type: APPUI_ACTION_TYPES.OPEN_DIALOGUE_TTS,
});
};
const allAudioItems = [...audioItems.value, ...audioItemsFromServer.value];
const handleListAudioByUser = useCallback((username: string) => {
setState((curr) => ({ ...curr, fetchingUserAudio: true }));
ListAudioByUser(username).then((res: any[]) => {
setState((curr) => ({ ...curr, fetchingUserAudio: false }));
audioItemsFromServer.value = res.map((item) => {
const morphedItem: MediaItem = {
const handleListAudioByUser = useCallback((username:string, sessionToken:string)=>{
setState((curr)=>({...curr, fetchingUserAudio:true}));
ListAudioByUser(username, sessionToken).then((res:any[])=>{
setState((curr)=>({...curr, fetchingUserAudio:false}));
audioItemsFromServer.value = res.map(item=>{
const morphedItem:MediaItem = {
version: 1,
type: AssetType.AUDIO,
media_id: item.token,
object_uuid: item.token,
name: item.maybe_title || item.origin.maybe_model.title,
description: item.maybe_text_transcript,
publicBucketPath: item.public_bucket_path,
length: 25,
thumbnail: "/resources/placeholders/audio_placeholder.png",
isMine: true,
@@ -58,14 +45,11 @@ export const AudioTabComponent = () => {
});
}, []);
useEffect(() => {
if (authState.userInfo) {
if (
state.firstLoad === false &&
audioItemsFromServer.value.length === 0
) {
handleListAudioByUser(authState.userInfo.username);
setState((curr) => ({ ...curr, firstLoad: true }));
useEffect(()=>{
if (authState.userInfo && authState.sessionToken){
if(state.firstLoad === false && audioItemsFromServer.value.length === 0){
handleListAudioByUser(authState.userInfo.username, authState.sessionToken);
setState((curr)=>({...curr, firstLoad:true}));
}
}
}, [authState, state, handleListAudioByUser]);
@@ -89,71 +73,30 @@ export const AudioTabComponent = () => {
return foundItemOfJob !== undefined;
}
});
if (found === undefined) {
handleListAudioByUser(authState.userInfo.username);
if(found === undefined && authState.sessionToken){
handleListAudioByUser(authState.userInfo.username, authState.sessionToken);
}
}
});
return (
<>
<div className="w-full overflow-x-auto p-4 pb-0">
<TabTitle title="Audio" />
<div className="mb-4 flex justify-start gap-2">
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.ALL ? "active" : "",
)}
onClick={() => (audioFilter.value = AssetFilterOption.ALL)}>
All
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.MINE ? "active" : "",
)}
onClick={() => (audioFilter.value = AssetFilterOption.MINE)}
disabled={!allAudioItems.some((item) => item.isMine)}>
My Audios
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.BOOKMARKED
? "active"
: "",
)}
onClick={() => (audioFilter.value = AssetFilterOption.BOOKMARKED)}
disabled={!allAudioItems.some((item) => item.isBookmarked)}>
Bookmarked
</button>
<ButtonIcon
className={twMerge(
"absolute right-0 mr-4",
// state.fetchingUserAudio ? "animate-spin" : "",
)}
icon={faArrowsRotate}
onClick={() => {
if (authState.userInfo?.username) {
handleListAudioByUser(authState.userInfo?.username);
}
}}
/>
</div>
</div>
<div className="w-full px-4">
<Button
icon={faCirclePlus}
variant="action"
className="w-full py-3 text-sm font-medium"
onClick={handleOpenTtsDialogue}>
Generate Audio
</Button>
</div>
<div className="h-full w-full overflow-y-auto px-4 pt-4">
<ItemElements items={allAudioItems} assetFilter={audioFilter.value} />
</div>
</>
);
const changePage = (newPage:AudioTabPages)=>{
setState((curr)=>({
...curr,
page: newPage
}))
}
if(state.page === AudioTabPages.LIBRARY){
return <PageLibrary changePage={changePage}/>
}else if(state.page === AudioTabPages.TTS && authState.sessionToken){
return(
<PageTTS
changePage={changePage}
sessionToken={authState.sessionToken}
/>
);
}else{
return(
<p>Unknown Error</p>
)
}
};
@@ -0,0 +1,55 @@
import { useCallback, useEffect, useState, useRef } from "react";
import WaveSurfer from "wavesurfer.js";
import { faCirclePlay, faCirclePause } from "@fortawesome/pro-solid-svg-icons";
import { ButtonIcon } from "~/components";
export const WaveformPlayer = ({ audio }: { audio: string }) => {
const waveSurferRef = useRef<WaveSurfer | undefined>(undefined);
const [isPlaying, toggleIsPlaying] = useState(false);
const containerRef = useCallback((node: HTMLDivElement) => {
if (node) {
const waveSurfer = WaveSurfer.create({
container: node,
barWidth: 2,
height: 24,
cursorWidth: 0,
waveColor: "#D7C8C8",
progressColor: "#FB8381",
});
waveSurfer.load(audio);
waveSurfer.on("ready", () => {
waveSurferRef.current = waveSurfer;
});
waveSurfer.on("play", () => {
toggleIsPlaying(true);
});
waveSurfer.on("pause", () => {
toggleIsPlaying(false);
});
waveSurfer.on("finish", () => {
waveSurfer.seekTo(0);
});
}
}, []);
useEffect(() => {
return () => {
console.log("OK?");
waveSurferRef.current?.destroy();
};
}, []);
return (
<div className="flex items-center gap-2 py-1">
<ButtonIcon
icon={isPlaying ? faCirclePause : faCirclePlay}
className="w-auto bg-transparent p-0 text-2xl hover:bg-transparent hover:opacity-75"
onClick={() => {
waveSurferRef.current?.playPause();
}}
/>
<div ref={containerRef} className="w-full" />
</div>
);
};
@@ -0,0 +1,101 @@
import { useCallback, useContext, useEffect } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { WaveformPlayer } from "./WaveformPlayer";
import { MediaItem } from "~/pages/PageEnigma/models";
import { TrackContext } from "~/pages/PageEnigma/contexts/TrackContext/TrackContext";
import {
canDrop,
currPosition,
dragItem,
initPosition,
} from "~/pages/PageEnigma/store";
import { H5, H6, Pill } from "~/components";
function getGcsUrl(bucketRelativePath: string | undefined | null): string {
let bucket = "vocodes-public";
let path = bucketRelativePath;
if (path !== undefined && path !== null && !path.startsWith("/")) {
path = "/" + path;
}
return `https://storage.googleapis.com/${bucket}${path}`;
}
interface Props {
item: MediaItem;
}
export const AudioItemElement = ({ item }: Props) => {
useSignals();
const { startDrag, endDrag } = useContext(TrackContext);
const { initX, initY } = initPosition.value;
useEffect(() => {
const onPointerUp = () => {
if (dragItem.value) {
endDrag();
}
};
const onMouseMove = (event: MouseEvent) => {
if (dragItem.value) {
event.stopPropagation();
event.preventDefault();
const deltaX = event.pageX - initX;
const deltaY = event.pageY - initY;
currPosition.value = { currX: initX + deltaX, currY: initY + deltaY };
}
};
window.addEventListener("pointerup", onPointerUp);
window.addEventListener("pointermove", onMouseMove);
return () => {
window.removeEventListener("pointerup", onPointerUp);
window.removeEventListener("pointermove", onMouseMove);
};
}, [startDrag, endDrag, initX, initY]);
const onPointerDown = useCallback(
(event: React.PointerEvent<HTMLDivElement>) => {
if (event.button === 0) {
startDrag(item);
currPosition.value = {
currX: event.pageX,
currY: event.pageY,
};
initPosition.value = {
initX: event.pageX,
initY: event.pageY,
};
canDrop.value = false;
}
},
[item, startDrag],
);
return (
<div
className="relative w-full cursor-pointer rounded-lg transition-all duration-200"
onPointerDown={onPointerDown}>
<div className="flex w-full flex-col gap-0.5 rounded-lg bg-assets-background p-2.5">
<div className="flex justify-between">
<Pill>Audio</Pill>
</div>
{item.publicBucketPath && (
<WaveformPlayer audio={getGcsUrl(item.publicBucketPath)} />
)}
<H5 className="text-overflow-ellipsis">{item.name}</H5>
{item.description && (
<H6 className="text-overflow-ellipsis text-xs text-white/90">
{item.description}
</H6>
)}
</div>
</div>
);
};
@@ -0,0 +1,27 @@
import { AudioItemElement } from "./audioItemElement";
import { AssetFilterOption, MediaItem } from "~/pages/PageEnigma/models";
interface Props {
items: MediaItem[];
assetFilter: AssetFilterOption;
}
export const AudioItemElements = ({ items, assetFilter }: Props) => {
const displayItems = items.filter((item) => {
if (assetFilter === AssetFilterOption.ALL) {
return true;
}
if (assetFilter === AssetFilterOption.MINE) {
return item.isMine;
}
return item.isBookmarked;
});
return (
<div className={`grid grid-cols-1 gap-2.5`}>
{displayItems.map((item) => (
<AudioItemElement key={item.media_id} item={item} />
))}
</div>
);
};
@@ -1,55 +0,0 @@
import { listMediaByUser } from "~/api";
import {
MediaFileType,
MediaFileClass,
MediaFileSubtype,
UserDetailsLight,
WeightCategory,
WeightType
} from "./types";
export interface MediaFile {
token: string;
media_type: MediaFileType;
media_class: MediaFileClass | null;
maybe_media_subtype: MediaFileSubtype | null;
public_bucket_path: string;
maybe_engine_extension: string | null;
maybe_batch_token: string;
maybe_title: string | null;
maybe_original_filename: string | null;
maybe_creator_user: UserDetailsLight | null;
maybe_prompt_token: string | null;
creator_set_visibility: string;
created_at: Date;
updated_at: Date;
maybe_model_weight_info: {
title: string;
weight_token: string;
weight_category: WeightCategory;
weight_type: WeightType;
maybe_weight_creator: UserDetailsLight;
maybe_cover_image_public_bucket_path: string;
};
}
export const ListAudioByUser = async(username:string) => {
return await fetch(listMediaByUser(username),{
method: 'GET',
headers: {
"Accept": "application/json",
},
// credentials: 'include'
})
.then(res => res.json())
.then(res => {
if(res.success && res.results){
return res.results.filter((item:MediaFile)=>item['media_type']==='audio');
}else{
Promise.reject();
}
})
.catch(e => ({ success : false }));
}
@@ -0,0 +1,75 @@
import { faCirclePlus } from "@fortawesome/pro-solid-svg-icons";
import { twMerge } from "tailwind-merge";
import { useSignals } from "@preact/signals-react/runtime";
import { audioFilter, audioItems } from "~/pages/PageEnigma/store";
import { AssetFilterOption } from "~/pages/PageEnigma/models";
import { audioItemsFromServer } from "~/pages/PageEnigma/store/mediaFromServer";
import { Button } from "~/components";
import { AudioItemElements } from "./audioItemElements";
import { AudioTabPages } from "./types";
export const PageLibrary = ({
changePage,
}: {
changePage: (newPage: AudioTabPages) => void;
}) => {
useSignals();
const allAudioItems = [...audioItems.value, ...audioItemsFromServer.value];
return (
<>
<div className="w-full overflow-x-auto">
<div className="mb-4 mt-4 flex items-center justify-start gap-2 px-4">
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.ALL ? "active" : "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.ALL)}>
All
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.MINE ? "active" : "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.MINE)}
disabled={!allAudioItems.some((item) => item.isMine)}>
My Audios
</button>
<button
className={twMerge(
"filter-tab",
audioFilter.value === AssetFilterOption.BOOKMARKED
? "active"
: "",
"disabled",
)}
onClick={() => (audioFilter.value = AssetFilterOption.BOOKMARKED)}
disabled={!allAudioItems.some((item) => item.isBookmarked)}>
Bookmarked
</button>
</div>
</div>
<div className="w-full px-4">
<Button
icon={faCirclePlus}
variant="action"
className="w-full py-3 text-sm font-medium"
onClick={() => changePage(AudioTabPages.TTS)}>
Generate Audio
</Button>
</div>
<div className="mt-4 h-full w-full overflow-y-auto px-4">
<AudioItemElements
items={allAudioItems}
assetFilter={audioFilter.value}
/>
</div>
</>
);
};
@@ -0,0 +1,253 @@
import { useCallback, useEffect, useState } from "react";
import { useSignalEffect } from "@preact/signals-react/runtime";
import { v4 as uuidv4 } from "uuid";
import {
faChevronLeft,
faBrainCircuit,
faVolumeHigh,
} from "@fortawesome/pro-solid-svg-icons";
import {
Button,
ButtonIcon,
H2,
Label,
LoadingDotsTyping,
ListSearchDropdown,
Textarea,
} from "~/components";
import {
TtsModelListItem,
GenerateTtsAudioResponse,
} from "~/pages/PageEnigma/models/tts";
import { addInferenceJob, inferenceJobs } from "../../../store/inferenceJobs";
import { JobState } from "~/hooks/useInferenceJobManager/useInferenceJobManager";
import {
ListTtsModels,
GenerateTtsAudio,
GetMediaFileByToken,
} from "./utilities";
import { AudioTabPages } from "./types";
import { AudioItemElement } from "./audioItemElement";
import { MediaItem, AssetType } from "~/pages/PageEnigma/models";
type TtsState = {
voice: TtsModelListItem | undefined;
text: string;
hasEnqueued: boolean;
inferenceToken?: string;
inferenceJobType?: string;
hasTtsResult: boolean;
};
const initialState: TtsState = {
voice: undefined,
text: "",
hasEnqueued: false,
inferenceJobType: undefined,
inferenceToken: undefined,
hasTtsResult: false,
};
export const PageTTS = ({
changePage,
sessionToken,
}: {
changePage: (newPage: AudioTabPages) => void;
sessionToken: string;
}) => {
const [ttsState, setTtsState] = useState<TtsState>(initialState);
const [ttsResultFile, setTtsResultFile] = useState<MediaItem | undefined>();
useSignalEffect(() => {
console.log(inferenceJobs.value);
if (ttsState.hasEnqueued && ttsState.inferenceToken) {
const found = inferenceJobs.value.find(
(job) => job.job_id === ttsState.inferenceToken,
);
console.log(`finding: ${ttsState.inferenceToken}`);
console.log(found);
if (found?.job_status === JobState.COMPLETE_SUCCESS) {
setTtsState((curr) => ({
...curr,
hasTtsResult: true,
}));
GetMediaFileByToken(found.result.entity_token, sessionToken).then(
(res) => {
console.log(res);
const morphedItem: MediaItem = {
version: 1,
type: AssetType.AUDIO,
media_id: res.media_file.token,
object_uuid: res.media_file.token,
name: ttsState.voice?.title || "",
description: ttsState.text,
publicBucketPath: res.media_file.public_bucket_path,
length: 25,
thumbnail: "/resources/placeholders/audio_placeholder.png",
isMine: true,
// isBookmarked?: boolean;
};
setTtsResultFile(morphedItem);
},
);
}
}
});
const [ttsModels, setTtsModels] = useState<Array<TtsModelListItem>>([]);
const listModels = useCallback(async () => {
const ttsModelsLoaded = ttsModels.length > 0;
if (ttsModelsLoaded) {
return; // Already queried.
}
const models = await ListTtsModels(sessionToken);
if (models) {
setTtsModels(models);
}
}, []);
useEffect(() => {
listModels();
}, [listModels]);
useEffect(() => {
if (
ttsState.hasEnqueued &&
ttsState.inferenceToken &&
ttsState.inferenceJobType
) {
console.log(`tts has Enqueued`);
addInferenceJob({
version: 1,
job_id: ttsState.inferenceToken,
job_type: ttsState.inferenceJobType,
job_status: JobState.PENDING,
});
}
}, [ttsState]);
const requestTts = useCallback(
(sessionToken: string) => {
const modelToken = ttsState.voice
? ttsState.voice.model_token
: undefined;
if (modelToken) {
setTtsState((curr) => ({
...curr,
hasEnqueued: true,
inferenceToken: undefined,
inferenceJobType: undefined,
hasAudio: false,
result: undefined,
}));
const request = {
uuid_idempotency_token: uuidv4(),
tts_model_token: modelToken,
inference_text: ttsState.text,
};
GenerateTtsAudio(request, sessionToken).then(
(res: GenerateTtsAudioResponse) => {
if (res && res.inference_job_token) {
setTtsState((curr) => ({
...curr,
inferenceToken: res.inference_job_token,
inferenceJobType: res.inference_job_token_type,
}));
}
},
);
} else {
console.log("no voice model selected");
}
},
[ttsState],
);
const handleTextInput = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setTtsState((curr) => ({
...curr,
text: e.target.value,
}));
};
const handleOnSelect = (val: string) => {
console.log(val);
const voiceModel = ttsModels.find((item) => {
if (item.title === val) return item;
});
setTtsState((curr) => ({
...curr,
voice: voiceModel,
}));
};
return (
<div className="flex flex-col p-4">
<div className="mb-5 flex items-center gap-3">
<ButtonIcon
className="w-auto p-0 text-xl opacity-60 hover:opacity-40"
icon={faChevronLeft}
onClick={() => changePage(AudioTabPages.LIBRARY)}
/>
<H2 className="font-semibold">Generate TTS</H2>
</div>
<Label className="mb-1">Select a Voice</Label>
{ttsModels.length > 0 && (
<ListSearchDropdown
list={ttsModels}
listDisplayKey="title"
onSelect={handleOnSelect}
/>
)}
<div className="mt-4 flex w-full justify-between">
<Label>What would you like to say?</Label>
<div className="flex items-center gap-2"></div>
</div>
<Textarea
placeholder="Enter what you want the voice to say here."
value={ttsState.text}
onChange={handleTextInput}
rows={8}
/>
<div className="mt-4 flex gap-2">
<div className="h-auto w-full">
{!ttsState.hasTtsResult && !ttsState.hasEnqueued && (
<Button
className="h-11 w-full text-sm"
variant={ttsState.hasTtsResult ? "secondary" : "primary"}
disabled={ttsState.text === ""}
icon={faVolumeHigh}
onClick={() => requestTts(sessionToken)}>
Generate
</Button>
)}
{!ttsState.hasTtsResult && ttsState.hasEnqueued && (
<LoadingDotsTyping className="rounded-lg bg-brand-secondary-500" />
)}
{ttsResultFile && <AudioItemElement item={ttsResultFile} />}
</div>
</div>
<div className="mt-4 flex justify-between gap-2">
{ttsState.hasEnqueued && (
<Button
className="h-11 w-full text-sm"
type="button"
disabled={!ttsState.hasTtsResult || ttsState.text === ""}
onClick={() => requestTts(sessionToken)}
icon={faVolumeHigh}>
Generate Another
</Button>
)}
</div>
</div>
);
};
@@ -122,3 +122,39 @@ export enum WeightType {
VALL_E = "vall_e",
NONE = "none",
}
export enum AudioTabPages {
LIBRARY = 'library',
TTS = 'tts',
}
export interface MediaFile {
token: string;
media_type: MediaFileType;
media_class: MediaFileClass | null;
maybe_media_subtype: MediaFileSubtype | null;
public_bucket_path: string;
maybe_engine_extension: string | null;
maybe_batch_token: string;
maybe_title: string | null;
maybe_original_filename: string | null;
maybe_creator_user: UserDetailsLight | null;
maybe_prompt_token: string | null;
creator_set_visibility: string;
created_at: Date;
updated_at: Date;
maybe_model_weight_info: {
title: string;
weight_token: string;
weight_category: WeightCategory;
weight_type: WeightType;
maybe_weight_creator: UserDetailsLight;
maybe_cover_image_public_bucket_path: string;
};
}
export interface GetMediaFileResponse {
success: boolean;
media_file?: MediaFile;
}
@@ -0,0 +1,138 @@
import { listTts, inferTts, listMediaByUser, getMediaFileByToken } from '~/api';
import { MediaFile, GetMediaFileResponse } from './types';
import {
TtsModelListItem,
TtsModelListResponsePayload,
GenerateTtsAudioErrorType,
StatusLike,
GenerateTtsAudioRequest,
GenerateTtsAudioResponse,
} from '~/pages/PageEnigma/models/tts';
export const ListAudioByUser = async(username:string, sessionToken: string) => {
return await fetch(listMediaByUser(username),{
method: 'GET',
headers: {
"Accept": "application/json",
'session': sessionToken,
},
// credentials: 'include'
})
.then(res => res.json())
.then(res => {
if(res.success && res.results){
return res.results.filter((item:MediaFile)=>item['media_type']==='audio');
}else{
Promise.reject();
}
})
.catch(e => ({ success : false }));
}
export async function ListTtsModels(sessionToken:string) : Promise<Array<TtsModelListItem>| undefined> {
return await fetch(listTts, {
method: 'GET',
headers: {
'Accept': 'application/json',
'session': sessionToken,
},
// credentials: 'include',
})
.then(res => res.json())
.then(res => {
const response : TtsModelListResponsePayload = res;
if (!response.success) {
return;
}
return response?.models;
})
.catch(e => {
return undefined;
});
}
export function maybeMapError(statuslike: StatusLike) : GenerateTtsAudioErrorType | undefined {
switch (statuslike.status) {
case 400:
return GenerateTtsAudioErrorType.BadRequest;
case 404:
return GenerateTtsAudioErrorType.NotFound;
case 429:
return GenerateTtsAudioErrorType.TooManyRequests;
case 500:
return GenerateTtsAudioErrorType.ServerError;
}
}
// export function GenerateTtsAudioIsError(response: GenerateTtsAudioResponse): response is GenerateTtsAudioError {
// return !('inference_job_token' in response);
// }
export async function GenerateTtsAudio(request: GenerateTtsAudioRequest, sessionToken:string) : Promise<GenerateTtsAudioResponse>
{
return await fetch(inferTts, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'session': sessionToken,
},
// credentials: 'include',
body: JSON.stringify(request),
})
.then(res => res.json())
.then(res => {
if (!('inference_job_token' in res)) {
return { error: GenerateTtsAudioErrorType.UnknownError };
}
const ret:GenerateTtsAudioResponse = {
success: true,
inference_job_token: res.inference_job_token,
inference_job_token_type: res.inference_job_token_type,
}
return ret;
})
.catch(e => {
let maybeError = maybeMapError(e);
if (maybeError !== undefined) {
return { error: maybeError };
}
return { error: GenerateTtsAudioErrorType.UnknownError };
});
};
export async function GetMediaFileByToken (fileToken: string, sessionToken: string) : Promise<GetMediaResponse>
{
return await fetch(getMediaFileByToken(fileToken), {
method: "GET",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'session': sessionToken,
},
})
.then(res => res.json())
.then(res => {
const response: GetMediaFileResponse = res;
if (response && response.success && response.media_file) {
// NB: Timestamps aren't converted to Date objects on their own!
response.media_file.created_at = new Date(
response.media_file.created_at
);
response.media_file.updated_at = new Date(
response.media_file.updated_at
);
return response;
} else {
return { success: false };
}
})
.catch(e => {
return { success: false };
});
;
}
@@ -1,4 +1,4 @@
import { AssetFilterOption, AssetType } from "~/pages/PageEnigma/models";
import { useContext, useState, useEffect } from "react";
import { useSignals } from "@preact/signals-react/runtime";
import {
characterFilter,
@@ -12,17 +12,71 @@ 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";
import { BucketConfig } from "~/api/BucketConfig";
import { AuthenticationContext } from "~/contexts/Authentication";
interface Props {
type: AssetType;
}
export enum FetchStatus {
paused,
ready,
in_progress,
success,
error
}
export const ObjectsTab = ({ type }: Props) => {
useSignals();
const [objects,objectsSet] = useState<{ value: MediaItem[] }>({
value:[{
media_id: "",
name: "",
type: "",
version: 1,
}]
});
const { authState } = useContext(AuthenticationContext);
const [status,statusSet] = useState(FetchStatus.ready);
useEffect(() => {
if (status === FetchStatus.ready && type !== AssetType.CHARACTER) {
statusSet(FetchStatus.in_progress);
GetMediaByUser(authState.userInfo.username,{},{
filter_media_type: "glb"
})
.then((res: GetMediaListResponse) => {
if (res.success && res.results) {
statusSet(FetchStatus.success);
objectsSet({
value: res.results.map((item,i) => {
let bucketConfig = new BucketConfig();
let itemThumb = bucketConfig.getCdnUrl(item.cover_image.maybe_cover_image_public_bucket_path,600,100);
return {
media_id: item.token,
name: item.maybe_title,
type: item.media_type,
version: 1,
...item.cover_image.maybe_cover_image_public_bucket_path ? {
thumbnail: itemThumb
} : {}
}
})
.filter((item,i) => (item.thumbnail))
});
}
});
}
},[status,type]);
const assetFilter =
type === AssetType.CHARACTER ? characterFilter : objectFilter;
const items = type === AssetType.CHARACTER ? characterItems : objectItems;
const items = type === AssetType.CHARACTER ? characterItems : objects;
return (
<>
@@ -74,4 +128,4 @@ export const ObjectsTab = ({ type }: Props) => {
</div>
</>
);
};
};
@@ -17,7 +17,6 @@ import {
audioGroup,
objectGroup,
deleteCharacter,
addShape,
} from "~/pages/PageEnigma/store";
import useUpdateKeyframe from "~/pages/PageEnigma/contexts/TrackContext/utils/useUpdateKeyframe";
import { deleteObject } from "~/pages/PageEnigma/store/objectGroup/deleteObject";
@@ -49,10 +48,10 @@ export const TrackProvider = ({ children }: Props) => {
if (dragItem.value.type === AssetType.OBJECT) {
addObject(dragItem.value);
}
if (dragItem.value.type === AssetType.SHAPE) {
addShape(dragItem.value);
}
}
// if (dragItem.value.type === AssetType.SHAPE) {
// console.log("Dragged In Shape Type")
// }
if (canDrop.value && dragItem.value) {
if (dragItem.value.type === AssetType.ANIMATION) {
@@ -133,10 +133,10 @@ export class TransformClip {
if (this.keyframes.length > 0) {
let first_pos = this.keyframes[0].position;
let first_rot = this.keyframes[0].rotation;
let first_quat = new THREE.Quaternion().setFromEuler(new THREE.Euler(THREE.MathUtils.degToRad(first_rot.x), THREE.MathUtils.degToRad(first_rot.y), THREE.MathUtils.degToRad(first_rot.z)));
let first_quat = new THREE.Euler(THREE.MathUtils.degToRad(first_rot.x), THREE.MathUtils.degToRad(first_rot.y), THREE.MathUtils.degToRad(first_rot.z));
let first_scl = this.keyframes[0].scale;
object.position.copy(first_pos);
object.rotation.set(first_quat.x, first_quat.y, first_quat.z);
object.rotation.copy(first_quat);
object.scale.copy(first_scl);
this.step_frame = 0;
}
+57 -20
View File
@@ -111,6 +111,8 @@ class Editor {
last_scrub: number;
record_stream: any | undefined;
recorder: MediaRecorder | undefined;
selectedCanvas: boolean;
// Default params.
constructor() {
@@ -182,6 +184,7 @@ class Editor {
this.last_scrub = 0;
this.frames = 0;
this.last_selected_sum = 0;
this.selectedCanvas = false;
// Audio Engine Test.
this.render_width = 1280;
@@ -251,27 +254,34 @@ 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;
const height = container.offsetHeight;
// Sets up camera and base position.
this.camera = new THREE.PerspectiveCamera(70, width / height, 0.15, 30);
this.camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 200);
this.camera.position.z = 3;
this.camera.position.y = 3;
this.camera.position.x = -3;
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(
70,
width / height,
0.15,
30,
0.01,
200,
);
this.render_camera.layers.disable(1); // This camera does not see this layer );
// Base WebGL render and clock for delta time.
this.renderer = new THREE.WebGLRenderer({
antialias: true,
@@ -315,24 +325,27 @@ class Editor {
this.renderer.domElement,
);
this.orbitControls.mouseButtons = {
MIDDLE: THREE.MOUSE.ROTATE,
RIGHT: THREE.MOUSE.PAN,
}; // Blender Style
// this.orbitControls.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN }; // Standard
//this.orbitControls.mouseButtons = {
// 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.control = new TransformControls(this.camera, this.renderer.domElement);
this.control.space = "local"; // Local transformation mode
// .space = 'world'; // Global mode
this.control.setScaleSnap(0.05);
this.control.setTranslationSnap(0.05);
this.control.setRotationSnap(0.05);
this.control.setScaleSnap(0.01);
this.control.setTranslationSnap(0.01);
this.control.setRotationSnap(0.01);
console.log("Control Sensitivity:", this.control.sensitivity);
// OnClick and MouseMove events.
window.addEventListener("mousemove", this.onMouseMove.bind(this), false);
window.addEventListener("click", this.onMouseClick.bind(this), false);
window.addEventListener("keydown", this.onkeydown.bind(this), false);
// Base control and debug stuff remove debug in prod.
if (this.control == undefined) {
return;
@@ -349,6 +362,10 @@ class Editor {
});
this.control.setSize(0.5); // Good default value for visuals.
this.raycaster = new THREE.Raycaster();
// Configure raycaster to check both layers
this.raycaster.layers.set(0); // Enable default layer
this.raycaster.layers.enable(1); // Also check objects on the custom layer
this.mouse = new THREE.Vector2();
this.activeScene.scene.add(this.control);
// Resets canvas size.
@@ -396,9 +413,11 @@ class Editor {
this.orbitControls.enabled = true;
this.cameraViewControls.enabled = false;
}
this.selectedCanvas = true;
} else {
this.orbitControls.enabled = false;
this.cameraViewControls.enabled = false;
this.selectedCanvas = false;
}
this.cameraViewControls?.reset();
}
@@ -421,7 +440,7 @@ class Editor {
console.log(result);
}
public async testTestTimelineEvents() {}
public async testTestTimelineEvents() { }
public async loadScene(scene_media_token: string) {
this.dispatchAppUiState({
@@ -573,6 +592,11 @@ class Editor {
});
this.updateSelectedUI();
editorState.value = EditorStates.CAMERA_VIEW;
if (this.activeScene.hot_items) {
this.activeScene.hot_items.forEach((element) => {
element.visible = false;
});
}
} else {
this.camera.position.copy(this.last_cam_pos);
this.camera.rotation.copy(this.last_cam_rot);
@@ -664,9 +688,9 @@ class Editor {
this.saoPass = new SAOPass(this.activeScene.scene, this.camera);
this.saoPass.params.saoBias = 3.1;
this.saoPass.params.saoBias = 4.1;
this.saoPass.params.saoIntensity = 1.0;
this.saoPass.params.saoScale = 6.0;
this.saoPass.params.saoScale = 32.0;
this.saoPass.params.saoKernelRadius = 5.0;
this.saoPass.params.saoMinResolution = 0.0;
@@ -932,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`,
@@ -1339,6 +1363,19 @@ class Editor {
}
}
onkeydown(event: any) {
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){
this.startPlayback();
}
}
}
// Sets new mouse location usually used in raycasts.
onMouseMove(event: any) {
const rect = this.canvReference.getBoundingClientRect();
+5
View File
@@ -75,6 +75,7 @@ class Scene {
obj.receiveShadow = false;
obj.castShadow = false;
obj.userData['media_id'] = "Point::" + keyframe_uuid;
obj.layers.set(1); // Enable default layer
if (this.hot_items != undefined) {
this.hot_items.push(obj);
}
@@ -105,6 +106,7 @@ class Scene {
cam_obj.userData["name"] = "::CAM::";
cam_obj.name = "::CAM::";
cam_obj.position.set(0, 0.6, 1.5);
cam_obj.layers.set(1);
this.scene.add(cam_obj);
});
}
@@ -155,6 +157,8 @@ class Scene {
child.userData["metalness"] = 0.0;
child.userData["shininess"] = 0.5;
child.userData["specular"] = 0.5;
child.layers.enable(0);
child.layers.enable(1);
if (auto_add) { this.scene.add(child); }
resolve(child);
});
@@ -251,6 +255,7 @@ class Scene {
const size = 25;
const divisions = 50;
this.gridHelper = new THREE.GridHelper(size, divisions, new THREE.Color("rgb(199,195,195)"), new THREE.Color("rgb(161,157,157)"));
this.gridHelper.layers.set(1); // Enable default layer
this.scene.add(this.gridHelper);
}
}
+10 -3
View File
@@ -306,7 +306,10 @@ export class TimeLine {
),
);
this.scene.createPoint(data_json["position"], data_json["keyframe_uuid"]);
let point = this.scene.createPoint(data_json["position"], data_json["keyframe_uuid"]);
if(this.editorEngine.camera_person_mode){
point.visible = false;
}
}
public deleteObject(object_uuid: string) {
@@ -422,11 +425,13 @@ export class TimeLine {
const media_id = data["data"]["media_id"];
const offset = data["data"]["offset"];
const length = data["data"]["length"] + offset;
const clip_uuid = data['data']['clip_uuid'];
for (const element of this.timeline_items) {
if (
element.media_id === media_id &&
element.object_uuid === object_uuid
element.object_uuid === object_uuid &&
element.clip_uuid == clip_uuid
) {
element.length = length;
element.offset = offset;
@@ -439,12 +444,14 @@ export class TimeLine {
const object_uuid = data["data"]["object_uuid"];
const media_id = data["data"]["media_id"];
const type = data["type"];
const clip_uuid = data['data']['clip_uuid'];
for (let i = 0; i < this.timeline_items.length; i++) {
const element = this.timeline_items[i];
if (
element.media_id === media_id &&
element.object_uuid === object_uuid
element.object_uuid === object_uuid &&
element.clip_uuid == clip_uuid
) {
this.timeline_items.splice(i, 1);
break;
+2
View File
@@ -4,6 +4,8 @@ export interface MediaItem {
media_id: string;
object_uuid?: string;
name: string;
description?: string;
publicBucketPath?: string;
length?: number;
thumbnail?: string;
isMine?: boolean;
+77
View File
@@ -0,0 +1,77 @@
/*
* Types for Listing TTS Models
*/
export interface TtsModelListResponsePayload {
success: boolean,
models: Array<TtsModelListItem>,
}
export interface UserRatings {
positive_count: number,
negative_count: number,
// Total count does not take into account "neutral" ratings.
total_count: number,
}
export interface TtsModelListItem {
model_token: string,
tts_model_type: string,
creator_user_token: string,
creator_username: string,
creator_display_name: string,
creator_gravatar_hash: string,
ietf_language_tag: string,
ietf_primary_language_subtag: string,
updatable_slug: string,
title: string,
is_front_page_featured: boolean,
is_twitch_featured: boolean,
user_ratings: UserRatings,
category_tokens: string[],
created_at: string,
updated_at: string,
}
/*
* Types for Making a TTS Generation Request
*/
export interface GenerateTtsAudioRequest {
uuid_idempotency_token: string,
tts_model_token: string,
inference_text: string,
// TODO(2022-03): TEMPORARY
is_storyteller_demo?: boolean,
}
export interface GenerateTtsAudioResponse {
success?: boolean,
inference_job_token?: string,
// Which queue to poll
inference_job_token_type?: string,
error?: GenerateTtsAudioErrorType,
}
export enum GenerateTtsAudioErrorType {
BadRequest,
NotFound,
TooManyRequests,
ServerError,
UnknownError,
}
export interface EndpointSuccessResponse {
success: boolean,
inference_job_token: string,
inference_job_token_type?: string,
}
export interface EndpointErrorResponse {
success: boolean,
}
export type EndpointResponse = EndpointSuccessResponse | EndpointErrorResponse;
export interface StatusLike {
status: number,
}
-1
View File
@@ -5,7 +5,6 @@ export * from "./dragAndDrop";
export * from "./media";
export * from "./objectGroup";
export * from "./selectedItem";
export * from "./shape";
export * from "./sidePanel";
export * from "./sizing";
export * from "./timing";
+2
View File
@@ -455,6 +455,7 @@ export const audioItems = signal<MediaItem[]>([
length: 25,
name: "Talk (Demo Sounds)",
thumbnail: "/resources/placeholders/audio_placeholder.png",
publicBucketPath: "/media/j/a/r/r/3/jarr3asge6t0x048wdzcehzjd2nh4ep7/fakeyou_jarr3asge6t0x048wdzcehzjd2nh4ep7.wav",
},
{
version: 1,
@@ -463,6 +464,7 @@ export const audioItems = signal<MediaItem[]>([
length: 25,
name: "NCS Song",
thumbnail: "/resources/placeholders/audio_placeholder.png",
publicBucketPath: "/media/s/j/t/0/6/sjt06a8y2qrdqe574nry02bpd3bt01ma/upload_sjt06a8y2qrdqe574nry02bpd3bt01ma.wav",
},
]);
-12
View File
@@ -1,12 +0,0 @@
import { MediaItem } from "~/pages/PageEnigma/models";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
export function addShape(item: MediaItem) {
Queue.publish({
queueName: QueueNames.TO_ENGINE,
action: toEngineActions.ADD_SHAPE,
data: item,
});
}
+1 -136
View File
@@ -1,136 +1 @@
import { useCallback, useEffect, useRef, useState } from "react";
import {
Button,
ButtonLink,
H1,H2,H3,H4,H5,H6,P,Label,
ListDropdown,
ListSearchDropdown,
LoadingBar,
LoadingDotsTyping,
LoadingDotsBricks,
} from "~/components";
export default function ComponentLibrary () {
const [progress, setProgress] = useState(0);
const loopRef = useRef<NodeJS.Timeout | null>(null);
const testdata = [
{ name: 'Wade Cooper' },
{ name: 'Arlene Mccoy' },
{ name: 'Devon Webb' },
{ name: 'Tom Cook' },
{ name: 'Tanya Fox' },
{ name: 'Hellen Schmidt' },
]
const pushProgressBar: ()=>void = useCallback(()=>{
loopRef.current = setInterval(function timer(){
setProgress((curr)=>{
if (curr < 100){
return curr+10;
}else{
if(loopRef.current)
clearInterval(loopRef.current);
return curr;
}
})}
, 3000);
}, []);
useEffect(()=>{
pushProgressBar();
return ()=>{
if(loopRef.current)
clearInterval(loopRef.current)
}
},[progress]);
return(
<div
className="fixed w-full overflow-scroll"
style={{height: "calc(100% - 72px)"}}
>
<div
className='bg-ui-panel w-10/12 max-w-7xl mx-auto my-6 rounded-lg p-6'
>
<div className="flex flex-col gap-2 mb-4">
<H1>Typography</H1>
<H1>H1. This is an H1 Heading</H1>
<H2>H2. This is an H2 Heading</H2>
<H3>H3. This is an H3 Heading</H3>
<H4>H4. This is an H4 Heading</H4>
<Label>Label. Is essentially same as H4</Label>
<P>P. Is an normal Paragraph</P>
<H5>H5. This is an H5 Heading</H5>
<H6>H6. This is an H6 Heading</H6>
<hr className="mt-2 mb-4"/>
<H1>Components</H1>
<H4>Buttons</H4>
<div className="flex gap-2">
<Button>Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button className="bg-brand-tertiary hover:bg-brand-teriary-400 focus-visible:outline-brand-tertiary">
Prelim Tertiary
</Button>
<Button disabled>Disabled</Button>
</div>
</div>
<div className="flex flex-col gap-2 mb-4">
<H4>ButtonLink</H4>
<ButtonLink to="/">Back to /</ButtonLink>
</div>
<div className="flex flex-col gap-2 mb-4 max-w-2xl">
<H4>List Dropdowns</H4>
<ListDropdown list={testdata}/>
<ListSearchDropdown list={testdata}/>
</div>
<div className="flex flex-col gap-2 mb-4">
<H4>Loading Dots</H4>
<div className="flex gap-2">
<div className="w-60 h-40 rounded-lg overflow-hidden">
<LoadingDotsTyping />
</div>
<div className="w-60 h-40 rounded-lg overflow-hidden">
<LoadingDotsBricks />
</div>
</div>
</div>
<div className="flex flex-col gap-2 mb-4 max-w-2xl">
<H4>Loading Bar</H4>
<div className="flex gap-2 items-center">
<LoadingBar
progress={progress}
isShowing={progress !== 100}
wrapperClassName="rounded-lg"
message="this takes progress from parent"
/>
<Button
className="h-fit"
onClick={()=>setProgress(0)}
>
Reset
</Button>
</div>
<LoadingBar
label="labeling the bar"
message="displaying a message"
/>
<LoadingBar
label="useFakeTimer = 30000 (30secs)"
useFakeTimer={30000}
/>
<LoadingBar
label="useFakeTimer = 3000 (3secs)"
useFakeTimer={3000}
/>
</div>
</div>
</div>
);
};
export { PageComponentLibrary as default } from "~/pages/PageComponentLibrary";
-41
View File
@@ -1,41 +0,0 @@
import { useState } from 'react';
import { PageCube } from "../pages/PageCube";
import { Button } from '~/components';
export default function Cube(){
const pausedRiddle = true;
const [riddle, setRiddle] = useState<string>("");
const [submitted, setSubmitted] = useState<boolean>(false);
const handleInput = (event:React.ChangeEvent<HTMLInputElement>)=>{
setRiddle(event.target.value);
}
const handleSubmit = () => {
const state = riddle.length > 5 && riddle[riddle.length-1] === "!";
if(state)
setSubmitted(state);
else
alert("What is the price for your blind eye?")
}
if(submitted || pausedRiddle){
return <PageCube />
}else{
return(
<div className="w-full flex justify-center content-center">
<form
className="mt-20 flex flex-col content-center"
onSubmit={handleSubmit}
>
<label className='text-white'>
Riddle:
</label>
<input type="text" value={riddle} onChange={handleInput} />
<br/>
<Button type="submit" className='text-white'>Submit</Button>
</form>
</div>
);
}
}
-34
View File
@@ -1,34 +0,0 @@
import type { MetaFunction } from "@remix-run/deno";
import { TopBarHelmet } from "~/modules/TopBarHelmet";
import sonic from "./_assets/sonic-the-hedgehog-classic-sonic.gif";
import { useSignals } from "@preact/signals-react/runtime";
import { useEffect } from "react";
import { pageHeight, pageWidth } from "~/store";
import { sidePanelWidth, timelineHeight } from "~/pages/PageEnigma/store";
export const meta: MetaFunction = () => {
return [
{ title: "New Remix App" },
{ name: "description", content: "Welcome to Remix!" },
];
};
export default function Sonic() {
return (
<div className="size-full">
<TopBarHelmet>{null}</TopBarHelmet>
<img
alt="sonic"
src={sonic}
className="
fixed
left-0 top-0
-z-10 h-screen
w-screen
object-cover
"
/>
</div>
);
}
+12
View File
@@ -18,6 +18,18 @@
border: 3px solid gray;
}
/* Chrome, Safari, Edge, Opera */
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Firefox */
input[type="number"] {
-moz-appearance: textfield;
}
body {
font-family: "Fira Sans", sans-serif;
color: #fff;
+6
View File
@@ -32,6 +32,7 @@
"terser": "^5.30.3",
"three": "^0.162.0",
"uuid": "^9.0.1",
"wavesurfer.js": "^7.7.11",
"webm-writer": "^1.0.0"
},
"devDependencies": {
@@ -13181,6 +13182,11 @@
"fsevents": "~2.3.2"
}
},
"node_modules/wavesurfer.js": {
"version": "7.7.11",
"resolved": "https://registry.npmjs.org/wavesurfer.js/-/wavesurfer.js-7.7.11.tgz",
"integrity": "sha512-27otJlDBcUSbMh/YkxEZN0yXTHINQNYSOl5aJplYlIaHw2u3BrABUzOmUZV3dKIy8udOLJXS/67y5pOIzgAg+w=="
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
+1
View File
@@ -39,6 +39,7 @@
"terser": "^5.30.3",
"three": "^0.162.0",
"uuid": "^9.0.1",
"wavesurfer.js": "^7.7.11",
"webm-writer": "^1.0.0"
},
"devDependencies": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 686 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB