diff --git a/.gitignore b/.gitignore index aa5a7831fa..5f99d7ed8b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules - +/~/ /.cache /public/build /build diff --git a/app/components/Button.tsx b/app/components/Button.tsx index ce52235d12..ebf50212fb 100644 --- a/app/components/Button.tsx +++ b/app/components/Button.tsx @@ -3,20 +3,29 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; interface ButtonProps extends React.ButtonHTMLAttributes { icon?: IconDefinition; + variant?: "primary"|"secondary"; } export const Button = ({ icon, children, className : propsClassName, + variant = 'primary', ...rest }:ButtonProps)=>{ - const className = ` - bg-brand-primary hover:bg-brand-primary-400 - text-white text-sm font-semibold - rounded-md px-3.5 py-2.5 shadow-sm - focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-primary-600 - ` + (propsClassName ? ` ${propsClassName}` : null); + function getVariantClassNames(variant: string){ + switch(variant){ + case "secondary":{ + return " bg-brand-secondary hover:bg-brand-secondary-900 text-white focus-visible:outline-brand-secondary"; + } + case "primary": + default:{ + return " bg-brand-primary hover:bg-brand-primary-400 text-white focus-visible:outline-brand-primary-600"; + } + } + }; + const className = "text-sm font-semibold rounded-md px-3.5 py-2.5 shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2" + getVariantClassNames(variant) + + (propsClassName ? ` ${propsClassName}` : ""); return( + + ); + } \ No newline at end of file diff --git a/app/contexts/TopBarInner.tsx b/app/contexts/TopBarInner.tsx new file mode 100644 index 0000000000..39f08848ca --- /dev/null +++ b/app/contexts/TopBarInner.tsx @@ -0,0 +1,6 @@ +import { createContext } from "react"; + +export const TopBarInnerContext = createContext<{ + TopBarInner: JSX.Element | null; + setTopBarInner: (el: JSX.Element | null)=>void; +} | null>(null); \ No newline at end of file diff --git a/app/contexts/TopbarMidSection.tsx b/app/contexts/TopbarMidSection.tsx deleted file mode 100644 index 092e27c488..0000000000 --- a/app/contexts/TopbarMidSection.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { ReactNode, createContext } from "react"; - -export const TopBarMidSection = createContext(null); \ No newline at end of file diff --git a/app/pages/PageCube/PageCube.tsx b/app/pages/PageCube/PageCube.tsx index 4105732d29..0ff62d3fb6 100644 --- a/app/pages/PageCube/PageCube.tsx +++ b/app/pages/PageCube/PageCube.tsx @@ -1,13 +1,28 @@ import { - useRef, - useEffect, - useState, useCallback, + useContext, + useEffect, + useLayoutEffect, + useRef, + useState, } from 'react'; +import { + faChevronLeft +} from "@fortawesome/free-solid-svg-icons"; + +import { Button } from '~/components/Button'; +import { ButtonLink } from '~/components/ButtonLink'; +import { TopBarInnerContext } from "~/contexts/TopBarInner"; + +import { SidePanel } from '~/templates/SidePanel'; + import { SceneWithCube } from './SceneWithCube'; export const PageCube = () => { + + const { setTopBarInner } = useContext(TopBarInnerContext) || {}; + const mountRef = useRef(null); const sceneRef = useRef(null); const [{ @@ -27,7 +42,10 @@ export const PageCube = () => { const listeningXYRotation = useCallback(( {x,y}:{x:number;y:number} )=>{ - setRotations({x:x, y:y}) + setRotations({ + x:Math.round(x*100)/100, + y:Math.round(y*100)/100, + }) },[]); useEffect(() => { //componentDidMount @@ -45,13 +63,35 @@ export const PageCube = () => { }; }, []); + useLayoutEffect(()=>{ + if(setTopBarInner){ + const BackButton = ( + + Back to Dashboard + + ); + setTopBarInner(BackButton); + } + },[setTopBarInner]); + return ( - <> -
- -

{rotationX ? rotationX : ""}

-

{rotationY ? rotationY : ""}

- +
+
+
+
+
+ +

Ration X: {rotationX ? rotationX : ""}

+

Ration Y: {rotationY ? rotationY : ""}

+
+ +

Side Panel Text

+
+
); }; \ No newline at end of file diff --git a/app/pages/PageEnigma/PageEnigma.tsx b/app/pages/PageEnigma/PageEnigma.tsx index 2645398601..e939461d13 100644 --- a/app/pages/PageEnigma/PageEnigma.tsx +++ b/app/pages/PageEnigma/PageEnigma.tsx @@ -1,16 +1,27 @@ -import { - useRef, - useEffect, - // useState, +import { useCallback, + useContext, + useEffect, + useLayoutEffect, + useRef, + // useState, } from 'react'; +import { + faChevronLeft, + faWandSparkles, +} from "@fortawesome/free-solid-svg-icons"; + import { Button } from '~/components/Button'; +import { ButtonLink } from '~/components/ButtonLink'; +import { TopBarInnerContext } from "~/contexts/TopBarInner"; +import { SidePanel } from '~/templates/SidePanel'; import Editor from './js/editor'; export const PageEnigma = () => { + const { setTopBarInner } = useContext(TopBarInnerContext) || {}; const canvasRef = useRef(null); const editorRef = useRef(null); @@ -37,6 +48,29 @@ export const PageEnigma = () => { } }, []); + useLayoutEffect(()=>{ + if(setTopBarInner){ + const TopBarInnerButtons = ( +
+ + Back to Dashboard + + + +
+ ); + setTopBarInner(TopBarInnerButtons); + } + },[setTopBarInner]); + const handleButtonSave = ()=>{ editorRef.current?.save(); } @@ -53,14 +87,16 @@ export const PageEnigma = () => { return(
-
+ + +
+

Timeline Panel

-
Timeline Panel
); } \ No newline at end of file diff --git a/app/pages/PageEnigma/js/api_manager.js b/app/pages/PageEnigma/js/api_manager.js index b0f5ee2ee6..372c46ec95 100644 --- a/app/pages/PageEnigma/js/api_manager.js +++ b/app/pages/PageEnigma/js/api_manager.js @@ -1,3 +1,5 @@ +import { v4 as uuidv4 } from 'uuid'; + class MediaUploadManager { constructor(sessionToken) { this.baseUrl = "https://api.fakeyou.com"; @@ -7,15 +9,19 @@ class MediaUploadManager { async uploadMedia(blob, fileName) { const url = `${this.baseUrl}/v1/media_uploads/upload`; + let uuid = uuidv4(); + const formData = new FormData(); + formData.append('uuid_idempotency_token', uuid); formData.append('file', blob, fileName); + formData.append('source', 'file'); + formData.append('type', 'video'); + formData.append('source', 'file'); const response = await fetch(url, { method: 'POST', - mode: 'cors', + credentials: "include", headers: { - 'Authorization': `Bearer ${this.sessionToken}`, // Assuming the session token is a Bearer token - 'Access-Control-Allow-Origin': 'http://localhost:3000', - 'Access-Control-Allow-Credentials': true + "accept": "application/json", }, body: formData, }); diff --git a/app/pages/PageEnigma/js/audio_manager.js b/app/pages/PageEnigma/js/audio_manager.js index 2f80c03da7..c516504437 100644 --- a/app/pages/PageEnigma/js/audio_manager.js +++ b/app/pages/PageEnigma/js/audio_manager.js @@ -38,4 +38,6 @@ class AudioManager { console.warn(`AudioManager: AudioClip with id "${id}" not found.`); } } -} \ No newline at end of file +} + +export default AudioManager; diff --git a/app/pages/PageEnigma/js/editor.js b/app/pages/PageEnigma/js/editor.js index 23c6e03093..bbedec0ade 100644 --- a/app/pages/PageEnigma/js/editor.js +++ b/app/pages/PageEnigma/js/editor.js @@ -6,6 +6,7 @@ import Stats from 'three/addons/libs/stats.module.js'; import Scene from './scene.js'; import SaveManager from './serialization.js'; import MediaUploadManager from './api_manager.js'; +import AudioManager from './audio_manager.js'; import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'; import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'; @@ -63,12 +64,10 @@ class Editor { // API. this.api_manager = new MediaUploadManager(); - - this.locked = false; - // Debug & Movement. this.stats = null; this.orbit = null; + this.locked = false; // Recording params. this.capturer = null; @@ -81,9 +80,13 @@ class Editor { this.playback = false; this.playback_location = 0; this.max_length = 10; + this.timeline = null; // Save & Load. this.save_manager = new SaveManager(this.version); + + // Audio Engine. + this.audio_manager = new AudioManager(); } // Initializes the main scene and ThreeJS essentials. @@ -302,10 +305,15 @@ class Editor { this.control.detach(this.selected); this.activeScene.scene.remove(this.control); this.activeScene.scene.remove(this.activeScene.gridHelper); - this.save_manager.save(this.activeScene.scene); + this.save_manager.save(this.activeScene.scene, this._save_to_cloud.bind(this), this.audio_manager, this.timeline); this.activeScene._createGrid(); } + _save_to_cloud(blob) { + console.log("Posting to cloud!"); + this.api_manager.uploadMedia(blob, "test.bin"); + } + load() { let uploadedFile = document.getElementById("load-upload").files[0]; this.save_manager.load(uploadedFile, this.load_callback.bind(this)); @@ -365,6 +373,8 @@ class Editor { const blob = new Blob([output.buffer], { type: 'video/mp4' }); const url = URL.createObjectURL(blob); + await this.api_manager.uploadMedia(blob, "output.mp4"); + // Create a link to download the file const downloadLink = document.createElement('a'); downloadLink.href = url; diff --git a/app/pages/PageEnigma/js/scene.js b/app/pages/PageEnigma/js/scene.js index 78d6fd111e..dd7c4be2dd 100644 --- a/app/pages/PageEnigma/js/scene.js +++ b/app/pages/PageEnigma/js/scene.js @@ -86,7 +86,6 @@ class Scene { } play_anim_demo(character_name) { - console.log(this.characters[character_name].anims[0]._clip); this.characters[character_name].animate(this.characters[character_name].anims[0]._clip); this.characters[character_name].sync_lips("/resources/sound/2pac.wav"); this.activeCharacter = character_name; diff --git a/app/pages/PageEnigma/js/serialization.js b/app/pages/PageEnigma/js/serialization.js index 5fc32ee4d7..b555b3b395 100644 --- a/app/pages/PageEnigma/js/serialization.js +++ b/app/pages/PageEnigma/js/serialization.js @@ -8,16 +8,18 @@ class SaveManager { this.scene = null; } - save(scene, scene_data) { + save(scene, scene_callback, audio_manager, timeline) { let gltfExporter = new GLTFExporter(); gltfExporter.parse( scene, - function (gltfJson) { - const jsonString = JSON.stringify(gltfJson); + function (gltf) { console.log(scene); - let blob = new Blob([jsonString], {type: 'application/octet-stream'}) + let save_json = {"glb": gltf, "audio": audio_manager.clips, "timeline": timeline} + let blob = new Blob([JSON.stringify(save_json)], {type: 'application/json'}) let blobUrl = URL.createObjectURL(blob); + scene_callback(blob); + let a = document.createElement('a'); const now = new Date(); @@ -28,7 +30,7 @@ class SaveManager { const minutes = String(now.getMinutes()).padStart(2, '0'); const seconds = String(now.getSeconds()).padStart(2, '0'); const randomNumber = Math.floor(Math.random() * 1000); - let save_name = `save_${randomNumber}_${year}${month}${day}_${hours}${minutes}${seconds}.glb`; + let save_name = `save_${randomNumber}_${year}${month}${day}_${hours}${minutes}${seconds}.sav`; a.href = blobUrl; a.download = save_name; @@ -51,18 +53,13 @@ class SaveManager { reader.onload = function(e) { const contents = e.target.result; - const loader = new THREE.GLTFLoader(); + const loader = new GLTFLoader(); loader.parse(contents, '', function(gltf) { - scene.add(gltf.scene); + console.log(gltf); + load_callback(gltf.scene); }); }; - reader.readAsArrayBuffer(file); - - //const loader = new GLTFLoader(); - //loader.load(uploadedFile, function ( gltf ) { - // console.log(gltf); - // load_callback(gltf.scene); - //}); + reader.readAsArrayBuffer(uploadedFile); } download(data, filename, type) { diff --git a/app/root.tsx b/app/root.tsx index 3feb117533..04637c4c01 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -1,3 +1,4 @@ +import { useState } from 'react'; import { LinksFunction } from "@remix-run/deno"; import { Links, @@ -10,8 +11,9 @@ import { import tailwindCss from "./styles/tailwind.css?url"; import normalizeCss from "./styles/normalize.css?url"; -import { TopBar } from "./template/TopBar"; -import { TopBarMidSection } from "./contexts/TopbarMidSection"; +import { TopBar } from "./templates/TopBar"; +import { TopBarInnerContext } from '~/contexts/TopBarInner'; + export const links : LinksFunction = () => [{ rel: "stylesheet", @@ -22,6 +24,8 @@ export const links : LinksFunction = () => [{ }]; export default function App() { + const [topBarInnerComponent, setTopBarInnerComponent] = useState(null); + return ( @@ -31,8 +35,15 @@ export default function App() { - - + + +
+ + + diff --git a/app/routes/ColourGuide.tsx b/app/routes/ColourGuide.tsx index e69de29bb2..6d71323ce7 100644 --- a/app/routes/ColourGuide.tsx +++ b/app/routes/ColourGuide.tsx @@ -0,0 +1,10 @@ + + +export default function ColourGuide () { + return( + <> +

Primary / Sunglo

+

Secondary / Gunpowder

+ + ); +}; \ No newline at end of file diff --git a/app/routes/ComponentLibrary.tsx b/app/routes/ComponentLibrary.tsx index e69de29bb2..41bbfb7f8d 100644 --- a/app/routes/ComponentLibrary.tsx +++ b/app/routes/ComponentLibrary.tsx @@ -0,0 +1,11 @@ +import { Button } from "~/components/Button"; +import { ButtonLink } from "~/components/ButtonLink"; + +export default function ComponentLibrary () { + return( + <> + + Button with Link + + ); +}; \ No newline at end of file diff --git a/app/routes/Cube.tsx b/app/routes/Cube.tsx index 4fa19f67b5..909c2f0521 100644 --- a/app/routes/Cube.tsx +++ b/app/routes/Cube.tsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { PageCube } from "../pages/PageCube"; import { Button } from '~/components/Button'; export default function Cube(){ + const pausedRiddle = true; const [riddle, setRiddle] = useState(""); const [submitted, setSubmitted] = useState(false); @@ -17,7 +18,7 @@ export default function Cube(){ alert("What is the price for your blind eye?") } - if(submitted){ + if(submitted || pausedRiddle){ return }else{ return( diff --git a/app/routes/_index.tsx b/app/routes/_index.tsx index 7c9037bbfd..4676568346 100644 --- a/app/routes/_index.tsx +++ b/app/routes/_index.tsx @@ -1,4 +1,10 @@ +import { + useContext, + useLayoutEffect, +} from "react"; import type { MetaFunction } from "@remix-run/deno"; +import { TopBarInnerContext } from "~/contexts/TopBarInner"; + import sonic from "./_assets/sonic-the-hedgehog-classic-sonic.gif"; export const meta: MetaFunction = () => { @@ -8,8 +14,13 @@ export const meta: MetaFunction = () => { ]; }; - export default function Index() { + const { setTopBarInner } = useContext(TopBarInnerContext) || {}; + useLayoutEffect(()=>{ + if(setTopBarInner){ + setTopBarInner(null); + } + },[setTopBarInner]); return (
{ + const [open, setOpen] = useState(false) + const handleOpen = ()=>{ + setOpen(true); + } + + return (<> + + + +
+ +
+
+
+ + +
+
+
+ + Library + +
+ +
+
+
+
+ {props.children} +
+
+
+
+
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/app/templates/SidePanel/index.tsx b/app/templates/SidePanel/index.tsx new file mode 100644 index 0000000000..a48bbce375 --- /dev/null +++ b/app/templates/SidePanel/index.tsx @@ -0,0 +1 @@ +export { SidePanel } from './SidePanel'; \ No newline at end of file diff --git a/app/template/TopBar/TopBar.tsx b/app/templates/TopBar/TopBar.tsx similarity index 62% rename from app/template/TopBar/TopBar.tsx rename to app/templates/TopBar/TopBar.tsx index b22f60e2e3..09fe014343 100644 --- a/app/template/TopBar/TopBar.tsx +++ b/app/templates/TopBar/TopBar.tsx @@ -1,18 +1,32 @@ +import { useContext } from 'react'; + import { Button } from '~/components/Button' import { faRightToBracket } from '@fortawesome/free-solid-svg-icons' -export const TopBar = () => { +import { TopBarInnerContext } from '~/contexts/TopBarInner'; +export const TopBar = () => { return ( -
+
+ ) } + +const TopBarInner = ()=>{ + const { TopBarInner } = useContext(TopBarInnerContext) || {} + return(TopBarInner); +} \ No newline at end of file diff --git a/app/template/TopBar/index.tsx b/app/templates/TopBar/index.tsx similarity index 100% rename from app/template/TopBar/index.tsx rename to app/templates/TopBar/index.tsx diff --git a/package-lock.json b/package-lock.json index d499fecfa4..b70a8a2463 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "three": "^0.162.0", + "uuid": "^9.0.1", "webm-writer": "^1.0.0" }, "devDependencies": { @@ -12475,6 +12476,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/uvu": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz", diff --git a/package.json b/package.json index e558ee3989..c78df3a41b 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "three": "^0.162.0", + "uuid": "^9.0.1", "webm-writer": "^1.0.0" }, "devDependencies": { diff --git a/tailwind.config.ts b/tailwind.config.ts index 5e1c1fb5e5..4b8ad34260 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -73,6 +73,7 @@ export default { 'ui-background': '#1a1a27', 'ui-panel': '#242433', + 'ui-panel-border': '#5e6097', gray: colors.gray, white: colors.white