mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-25 11:19:29 -04:00
Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd9d85058c | ||
|
|
c4c4d77bdb | ||
|
|
8e120e02de | ||
|
|
d8607168fb | ||
|
|
bb611b850d | ||
|
|
3ef364fdbd | ||
|
|
5016bc64bd | ||
|
|
8aa3f2ba56 | ||
|
|
6871f5ea45 | ||
|
|
1b198e2334 | ||
|
|
17a0204131 | ||
|
|
52c361ed8e | ||
|
|
14aa429878 | ||
|
|
f902be970f | ||
|
|
783761736e | ||
|
|
794fcf4efb | ||
|
|
183b480e6d | ||
|
|
ae215a7901 | ||
|
|
2117566fbd | ||
|
|
d492b93842 | ||
|
|
ebcc317f31 | ||
|
|
8a19e914dc | ||
|
|
5df413af49 |
@@ -17,6 +17,7 @@ interface PopoverMenuProps {
|
||||
onAdd?: () => void;
|
||||
triggerIcon?: ReactNode;
|
||||
showAddButton?: boolean;
|
||||
disableAddButton?: boolean;
|
||||
showIconsInList?: boolean;
|
||||
mode?: "default" | "toggle" | "button";
|
||||
triggerLabel?: string;
|
||||
@@ -36,6 +37,7 @@ export const PopoverMenu = ({
|
||||
onAdd,
|
||||
triggerIcon,
|
||||
showAddButton = false,
|
||||
disableAddButton = false,
|
||||
showIconsInList = false,
|
||||
mode = "default",
|
||||
triggerLabel,
|
||||
@@ -84,9 +86,15 @@ export const PopoverMenu = ({
|
||||
{({ close }) => (
|
||||
<>
|
||||
<Popover.Button className={className}>
|
||||
{triggerIcon}
|
||||
{mode === "toggle" && selectedItem ? selectedItem.label : null}
|
||||
{mode === "default" && triggerLabel ? triggerLabel : null}
|
||||
<>
|
||||
{triggerIcon}
|
||||
{mode === "toggle" && selectedItem ? (
|
||||
<span className="truncate">{selectedItem.label}</span>
|
||||
) : null}
|
||||
{mode === "default" && triggerLabel ? (
|
||||
<span className="truncate">{triggerLabel}</span>
|
||||
) : null}
|
||||
</>
|
||||
</Popover.Button>
|
||||
|
||||
<div
|
||||
@@ -164,9 +172,9 @@ export const PopoverMenu = ({
|
||||
variant="secondary"
|
||||
disabled={item.disabled}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex items-center gap-2 truncate">
|
||||
{showIconsInList && item.icon}
|
||||
{item.label}
|
||||
<span className="truncate">{item.label}</span>
|
||||
</div>
|
||||
{mode === "toggle" && (
|
||||
<input
|
||||
@@ -180,8 +188,14 @@ export const PopoverMenu = ({
|
||||
{showAddButton && onAdd && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="mb-0.5 mt-2 border-none bg-[#7B7B84] py-1 hover:bg-[#8c8c96]"
|
||||
className={twMerge(
|
||||
"mb-0.5 mt-2 border-none py-1",
|
||||
disableAddButton
|
||||
? "cursor-not-allowed bg-[#7B7B84]/50 opacity-50"
|
||||
: "bg-[#7B7B84] hover:bg-[#8c8c96]",
|
||||
)}
|
||||
onClick={onAdd}
|
||||
disabled={disableAddButton}
|
||||
>
|
||||
+ Add
|
||||
</Button>
|
||||
|
||||
@@ -27,18 +27,18 @@ export const TopBar = ({ pageName }: Props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<header className="fixed left-0 top-0 z-30 w-full border-b border-ui-panel-border bg-ui-background">
|
||||
<header className="fixed left-0 top-0 z-30 w-full border-b border-white/5 bg-ui-background">
|
||||
<nav
|
||||
className="mx-auto grid h-[64px] w-screen grid-cols-3 items-center justify-between p-3"
|
||||
className="mx-auto grid h-[56px] w-screen grid-cols-3 items-center justify-between px-3"
|
||||
aria-label="Global"
|
||||
>
|
||||
<div className="flex gap-4">
|
||||
<a href="/" className="">
|
||||
<span className="sr-only">Storyteller.ai</span>
|
||||
<span className="sr-only">ArtCraft</span>
|
||||
<img
|
||||
className="h-[39px] w-auto pb-[3px]"
|
||||
src="/resources/images/Storyteller-Logo-1.png"
|
||||
alt="Logo StoryTeller.ai"
|
||||
className="h-[30px] w-auto"
|
||||
src="/resources/images/artcraft-logo-3.png"
|
||||
alt="Logo ArtCraft"
|
||||
/>
|
||||
</a>
|
||||
{!isEditorPath(currentLocation) && (
|
||||
|
||||
@@ -197,8 +197,6 @@ class Editor {
|
||||
processingHasFailed: boolean;
|
||||
stats: Stats;
|
||||
|
||||
cameras: Map<string, THREE.PerspectiveCamera> = new Map();
|
||||
|
||||
constructor() {
|
||||
this.processingHasFailed = false;
|
||||
console.log(
|
||||
@@ -329,34 +327,6 @@ class Editor {
|
||||
if (this.engineFrameBuffers.frameWorkerManager) {
|
||||
this.engineFrameBuffers.frameWorkerManager.type = imageFormat;
|
||||
}
|
||||
|
||||
// Initialize cameras from signals
|
||||
cameras.value.forEach((camData) => {
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
camData.fov,
|
||||
this.getRenderDimensions().aspectRatio,
|
||||
0.01,
|
||||
200,
|
||||
);
|
||||
camera.position.set(
|
||||
camData.position.x,
|
||||
camData.position.y,
|
||||
camData.position.z,
|
||||
);
|
||||
camera.rotation.set(
|
||||
camData.rotation.x,
|
||||
camData.rotation.y,
|
||||
camData.rotation.z,
|
||||
);
|
||||
this.cameras.set(camData.id, camera);
|
||||
});
|
||||
|
||||
// Set initial camera
|
||||
this.camera = this.cameras.get("main") || null;
|
||||
this.render_camera = this.camera;
|
||||
|
||||
// Subscribe to camera changes
|
||||
this.setupCameraSignalHandlers();
|
||||
}
|
||||
|
||||
// Add helper method to convert focal length to FOV
|
||||
@@ -365,99 +335,6 @@ class Editor {
|
||||
return 2 * Math.atan(sensorHeight / (2 * focalLength)) * (180 / Math.PI);
|
||||
}
|
||||
|
||||
setupCameraSignalHandlers() {
|
||||
// Handle camera selection changes
|
||||
selectedCameraId.subscribe((id) => {
|
||||
const newCamera = this.cameras.get(id);
|
||||
if (newCamera) {
|
||||
this.camera = newCamera;
|
||||
this.render_camera = newCamera;
|
||||
|
||||
// Update camera properties in signals
|
||||
const camData = cameras.value.find((c) => c.id === id);
|
||||
if (camData) {
|
||||
// Convert focal length to FOV
|
||||
const fov = this.focalLengthToFov(camData.focalLength);
|
||||
newCamera.fov = fov;
|
||||
newCamera.updateProjectionMatrix();
|
||||
this.renderScene(); // Force a render update
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle camera updates
|
||||
cameras.subscribe((newCameras) => {
|
||||
newCameras.forEach((camData) => {
|
||||
let camera = this.cameras.get(camData.id);
|
||||
|
||||
// Create new camera if it doesn't exist
|
||||
if (!camera) {
|
||||
const fov = this.focalLengthToFov(camData.focalLength);
|
||||
camera = new THREE.PerspectiveCamera(
|
||||
fov,
|
||||
this.getRenderDimensions().aspectRatio,
|
||||
0.01,
|
||||
200,
|
||||
);
|
||||
this.cameras.set(camData.id, camera);
|
||||
}
|
||||
|
||||
// Update camera properties
|
||||
camera.position.set(
|
||||
camData.position.x,
|
||||
camData.position.y,
|
||||
camData.position.z,
|
||||
);
|
||||
camera.rotation.set(
|
||||
camData.rotation.x,
|
||||
camData.rotation.y,
|
||||
camData.rotation.z,
|
||||
);
|
||||
|
||||
// Update FOV based on focal length
|
||||
const fov = this.focalLengthToFov(camData.focalLength);
|
||||
camera.fov = fov;
|
||||
camera.updateProjectionMatrix();
|
||||
|
||||
// Force a render update if this is the active camera
|
||||
if (camData.id === selectedCameraId.value) {
|
||||
this.renderScene();
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up deleted cameras
|
||||
const currentIds = new Set(newCameras.map((c) => c.id));
|
||||
for (const [id] of this.cameras.entries()) {
|
||||
if (!currentIds.has(id) && id !== "main") {
|
||||
this.cameras.delete(id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update camera position in signals when it changes
|
||||
updateCameraPosition() {
|
||||
if (this.camera && selectedCameraId.value) {
|
||||
const pos = this.camera.position;
|
||||
const rot = this.camera.rotation;
|
||||
|
||||
const camData = cameras.value.find(
|
||||
(c) => c.id === selectedCameraId.value,
|
||||
);
|
||||
if (camData) {
|
||||
cameras.value = cameras.value.map((c) =>
|
||||
c.id === selectedCameraId.value
|
||||
? {
|
||||
...c,
|
||||
position: { x: pos.x, y: pos.y, z: pos.z },
|
||||
rotation: { x: rot.x, y: rot.y, z: rot.z },
|
||||
}
|
||||
: c,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getRenderDimensions() {
|
||||
switch (this.render_camera_aspect_ratio) {
|
||||
case CameraAspectRatio.HORIZONTAL_16_9: {
|
||||
@@ -590,24 +467,52 @@ class Editor {
|
||||
const width = this.container.offsetWidth;
|
||||
const height = this.container.offsetHeight;
|
||||
|
||||
// Sets up camera and base position.
|
||||
this.camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 200);
|
||||
this.camera.position.z = 2.5;
|
||||
this.camera.position.y = 2.5;
|
||||
this.camera.position.x = -2.5;
|
||||
this.camera.lookAt(0, 0, 0);
|
||||
// Sets up camera and base position using camera configurations from camera.ts
|
||||
const mainCameraConfig = cameras.value.find((cam) => cam.id === "main");
|
||||
if (mainCameraConfig) {
|
||||
this.camera = new THREE.PerspectiveCamera(
|
||||
this.focalLengthToFov(mainCameraConfig.focalLength),
|
||||
width / height,
|
||||
0.01,
|
||||
200,
|
||||
);
|
||||
this.camera.position.set(
|
||||
mainCameraConfig.position.x,
|
||||
mainCameraConfig.position.y,
|
||||
mainCameraConfig.position.z,
|
||||
);
|
||||
this.camera.lookAt(
|
||||
mainCameraConfig.lookAt.x,
|
||||
mainCameraConfig.lookAt.y,
|
||||
mainCameraConfig.lookAt.z,
|
||||
);
|
||||
}
|
||||
|
||||
this.camera.layers.enable(0);
|
||||
this.camera.layers.enable(1); // This camera does not see this layer
|
||||
this.camera.layers.enable(1);
|
||||
|
||||
this.timeline.camera = this.camera;
|
||||
|
||||
this.render_camera = new THREE.PerspectiveCamera(
|
||||
70,
|
||||
width / height,
|
||||
0.01,
|
||||
200,
|
||||
);
|
||||
const otherCameras = cameras.value.filter((cam) => cam.id !== "main");
|
||||
if (otherCameras.length > 0) {
|
||||
const renderCameraConfig = otherCameras[0];
|
||||
this.render_camera = new THREE.PerspectiveCamera(
|
||||
this.focalLengthToFov(renderCameraConfig.focalLength),
|
||||
width / height,
|
||||
0.01,
|
||||
200,
|
||||
);
|
||||
this.render_camera.position.set(
|
||||
renderCameraConfig.position.x,
|
||||
renderCameraConfig.position.y,
|
||||
renderCameraConfig.position.z,
|
||||
);
|
||||
this.render_camera.lookAt(
|
||||
renderCameraConfig.lookAt.x,
|
||||
renderCameraConfig.lookAt.y,
|
||||
renderCameraConfig.lookAt.z,
|
||||
);
|
||||
}
|
||||
|
||||
this.render_camera.layers.disable(1); // This camera does not see this layer );
|
||||
|
||||
@@ -801,6 +706,18 @@ class Editor {
|
||||
const wasGridVisible = gridVisibility.value;
|
||||
gridVisibility.value = false;
|
||||
|
||||
// Store and hide transform controls
|
||||
const wasControlVisible = this.control?.visible ?? false;
|
||||
if (this.control) {
|
||||
this.control.visible = false;
|
||||
}
|
||||
|
||||
// Store and disable outline pass
|
||||
const wasOutlineEnabled = this.outlinePass?.enabled ?? false;
|
||||
if (this.outlinePass) {
|
||||
this.outlinePass.enabled = false;
|
||||
}
|
||||
|
||||
// High quality dimensions for each aspect ratio
|
||||
let targetWidth: number;
|
||||
let targetHeight: number;
|
||||
@@ -894,6 +811,16 @@ class Editor {
|
||||
// Restore grid visibility
|
||||
gridVisibility.value = wasGridVisible;
|
||||
|
||||
// Restore transform controls visibility
|
||||
if (this.control) {
|
||||
this.control.visible = wasControlVisible;
|
||||
}
|
||||
|
||||
// Restore outline pass
|
||||
if (this.outlinePass) {
|
||||
this.outlinePass.enabled = wasOutlineEnabled;
|
||||
}
|
||||
|
||||
if (shouldDownload) {
|
||||
const link = document.createElement("a");
|
||||
link.download = "scene-snapshot.png";
|
||||
|
||||
@@ -70,7 +70,7 @@ class FreeCam extends EventDispatcher {
|
||||
}
|
||||
|
||||
keydown(event: KeyboardEvent) {
|
||||
if (event.altKey || !this.enabled) {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -235,12 +235,18 @@ class FreeCam extends EventDispatcher {
|
||||
const pos = this.object.position;
|
||||
const rot = this.object.rotation;
|
||||
|
||||
// Calculate lookAt point based on camera's forward direction
|
||||
const lookAtPoint = new Vector3(0, 0, -1);
|
||||
lookAtPoint.applyQuaternion(this.object.quaternion);
|
||||
lookAtPoint.add(pos);
|
||||
|
||||
cameras.value = cameras.value.map((cam) =>
|
||||
cam.id === selectedCameraId.value
|
||||
? {
|
||||
...cam,
|
||||
position: { x: pos.x, y: pos.y, z: pos.z },
|
||||
rotation: { x: rot.x, y: rot.y, z: rot.z },
|
||||
lookAt: { x: lookAtPoint.x, y: lookAtPoint.y, z: lookAtPoint.z },
|
||||
}
|
||||
: cam,
|
||||
);
|
||||
|
||||
@@ -335,8 +335,19 @@ export class MouseControls {
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent browser shortcuts for Alt combinations
|
||||
if (
|
||||
event.altKey &&
|
||||
(event.key === "Alt" || event.key.toLowerCase() === "d")
|
||||
) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
if (event.shiftKey) {
|
||||
this.cameraViewControls.movementSpeed = 4;
|
||||
} else if (event.altKey) {
|
||||
this.cameraViewControls.movementSpeed = 0.1;
|
||||
} else {
|
||||
this.cameraViewControls.movementSpeed = 1.15;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import {
|
||||
} from "../signals";
|
||||
import Editor from "./editor";
|
||||
import { getArtStyle } from "~/enums";
|
||||
import { cameras, selectedCameraId, Camera } from "../signals/camera";
|
||||
|
||||
export type EditorInitializeConfig = {
|
||||
sceneToken: string;
|
||||
};
|
||||
@@ -98,6 +100,16 @@ export class SaveManager {
|
||||
);
|
||||
const timeline_json = await proxyTimeline.saveToJson();
|
||||
|
||||
// Save all cameras data
|
||||
const camerasData = cameras.value.map((cam: Camera) => ({
|
||||
id: cam.id,
|
||||
label: cam.label,
|
||||
focalLength: cam.focalLength,
|
||||
position: cam.position,
|
||||
rotation: cam.rotation,
|
||||
lookAt: cam.lookAt,
|
||||
}));
|
||||
|
||||
const save_data = {
|
||||
version: this.editor.version,
|
||||
scene: scene_json,
|
||||
@@ -108,6 +120,9 @@ export class SaveManager {
|
||||
position: this.editor.camera?.position,
|
||||
rotation: this.editor.camera?.rotation,
|
||||
},
|
||||
// Add cameras array and selected camera
|
||||
cameras: camerasData,
|
||||
selectedCameraId: selectedCameraId.value,
|
||||
};
|
||||
// take json scene and figure out checksum
|
||||
const jsonString = JSON.stringify(save_data);
|
||||
@@ -146,6 +161,16 @@ export class SaveManager {
|
||||
);
|
||||
const timeline_json = await proxyTimeline.saveToJson();
|
||||
|
||||
// Save all cameras data
|
||||
const camerasData = cameras.value.map((cam: Camera) => ({
|
||||
id: cam.id,
|
||||
label: cam.label,
|
||||
focalLength: cam.focalLength,
|
||||
position: cam.position,
|
||||
rotation: cam.rotation,
|
||||
lookAt: cam.lookAt,
|
||||
}));
|
||||
|
||||
const save_data = {
|
||||
version: this.editor.version,
|
||||
scene: scene_json,
|
||||
@@ -156,6 +181,9 @@ export class SaveManager {
|
||||
position: this.editor.camera?.position,
|
||||
rotation: this.editor.camera?.rotation,
|
||||
},
|
||||
// Add cameras array and selected camera
|
||||
cameras: camerasData,
|
||||
selectedCameraId: selectedCameraId.value,
|
||||
};
|
||||
|
||||
// TODO turn scene information into and object ...
|
||||
@@ -218,6 +246,23 @@ export class SaveManager {
|
||||
this.editor.camera.rotation.copy(camera_rotation);
|
||||
}
|
||||
|
||||
// Restore cameras data
|
||||
if (scene_json.cameras) {
|
||||
cameras.value = scene_json.cameras.map((cam: Camera) => ({
|
||||
id: cam.id,
|
||||
label: cam.label,
|
||||
focalLength: cam.focalLength,
|
||||
position: cam.position,
|
||||
rotation: cam.rotation,
|
||||
lookAt: cam.lookAt,
|
||||
}));
|
||||
}
|
||||
|
||||
// Restore selected camera
|
||||
if (scene_json.selectedCameraId) {
|
||||
selectedCameraId.value = scene_json.selectedCameraId;
|
||||
}
|
||||
|
||||
// For Remixing Scenes.
|
||||
// this calls the signal function to propagate the data to the UI
|
||||
restoreSceneGenerationMetadata(scene_json);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { GetCdnOrigin } from "~/api/GetCdnOrigin";
|
||||
import { GetFrontendEnvironment } from "~/Classes/GetFrontendEnvironment";
|
||||
import { gridVisibility } from "../signals/engine";
|
||||
import { InfiniteGridHelper } from "./InfiniteGridHelper";
|
||||
import { cameras, selectedCameraId } from "../signals/camera";
|
||||
|
||||
class Scene {
|
||||
name: string;
|
||||
@@ -86,7 +87,7 @@ class Scene {
|
||||
this._createGrid();
|
||||
this._create_base_lighting();
|
||||
this._create_skybox();
|
||||
this._create_camera_obj();
|
||||
// this._create_camera_obj();
|
||||
|
||||
this.helper = new MMDAnimationHelper({ afterglow: 0.0 });
|
||||
this.scene.userData["helper"] = this.helper;
|
||||
@@ -110,7 +111,7 @@ class Scene {
|
||||
this._createGrid();
|
||||
this._create_base_lighting();
|
||||
this._create_skybox();
|
||||
this._create_camera_obj();
|
||||
// this._create_camera_obj();
|
||||
}
|
||||
|
||||
async instantiate(
|
||||
@@ -370,31 +371,45 @@ class Scene {
|
||||
}
|
||||
|
||||
async _create_camera_obj() {
|
||||
const camera_position = new THREE.Vector3(0, 0.6, 1.5);
|
||||
cameras.value.forEach((cameraConfig) => {
|
||||
const camera_position = new THREE.Vector3(
|
||||
cameraConfig.position.x,
|
||||
cameraConfig.position.y,
|
||||
cameraConfig.position.z,
|
||||
);
|
||||
|
||||
// TODO(bt,2025-02-10): Make the local dev assets more configurable or seedable.
|
||||
const camera_id = GetFrontendEnvironment().getIsLocalDev()
|
||||
? "m_4djqzq89mbn6sdbbqm25ks6drhr55k" // Local development
|
||||
: "m_cxh4asqhapdz10j880755dg4yevshb"; // Production
|
||||
// TODO(bt,2025-02-10): Make the local dev assets more configurable or seedable.
|
||||
const camera_id = GetFrontendEnvironment().getIsLocalDev()
|
||||
? "m_cxh4asqhapdz10j880755dg4yevshb" // Local development
|
||||
: "m_cxh4asqhapdz10j880755dg4yevshb"; // Production
|
||||
|
||||
const camera_obj = await this.loadGlbWithPlaceholder(
|
||||
camera_id,
|
||||
"Camera",
|
||||
false,
|
||||
camera_position,
|
||||
);
|
||||
camera_obj.userData["name"] = this.camera_name;
|
||||
camera_obj.name = this.camera_name;
|
||||
camera_obj.position.set(
|
||||
camera_position.x,
|
||||
camera_position.y,
|
||||
camera_position.z,
|
||||
);
|
||||
camera_obj.layers.set(1);
|
||||
camera_obj.children.forEach((child) => {
|
||||
child.layers.set(1);
|
||||
this.loadGlbWithPlaceholder(
|
||||
camera_id,
|
||||
"Camera",
|
||||
false,
|
||||
camera_position,
|
||||
).then((camera_obj) => {
|
||||
camera_obj.userData["name"] = cameraConfig.label;
|
||||
camera_obj.name = cameraConfig.label;
|
||||
camera_obj.position.set(
|
||||
camera_position.x,
|
||||
camera_position.y,
|
||||
camera_position.z,
|
||||
);
|
||||
camera_obj.layers.set(1);
|
||||
camera_obj.children.forEach((child) => {
|
||||
child.layers.set(1);
|
||||
});
|
||||
this.scene.add(camera_obj);
|
||||
|
||||
if (cameraConfig.id === selectedCameraId.value) {
|
||||
camera_obj.visible = false;
|
||||
camera_obj.layers.disableAll(); // Make the active camera not touchable
|
||||
} else {
|
||||
camera_obj.visible = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
this.scene.add(camera_obj);
|
||||
}
|
||||
|
||||
renderMode(enabled: boolean = true) {
|
||||
|
||||
@@ -25,6 +25,7 @@ import { CameraAspectRatio } from "./enums";
|
||||
import { PromptBox } from "./comps/PromptBox";
|
||||
import { OnboardingHelper } from "./comps/OnboardingHelper";
|
||||
import { focalLengthDragging } from "./signals/camera";
|
||||
import { FocalLengthDisplay } from "./comps/FocalLengthDisplay/FocalLengthDisplay";
|
||||
|
||||
export const PageEditor = () => {
|
||||
useSignals();
|
||||
@@ -51,11 +52,11 @@ export const PageEditor = () => {
|
||||
: pageWidth.value - 84;
|
||||
const height =
|
||||
dndTimelineHeight.value > -1
|
||||
? pageHeight.value - dndTimelineHeight.value - 64
|
||||
: pageHeight.value - timelineHeight.value - 64;
|
||||
? pageHeight.value - dndTimelineHeight.value - 56
|
||||
: pageHeight.value - timelineHeight.value - 56;
|
||||
|
||||
const getScale = () => {
|
||||
const height = pageHeight.value - timelineHeight.value - 64;
|
||||
const height = pageHeight.value - timelineHeight.value - 56;
|
||||
const scaleHeight = height < 610 ? height / 610 : 1;
|
||||
|
||||
if (
|
||||
@@ -103,13 +104,7 @@ export const PageEditor = () => {
|
||||
</SceneContainer>
|
||||
|
||||
{/* Focal Length Display */}
|
||||
{focalLengthDragging.value.isDragging && (
|
||||
<div className="absolute left-1/2 top-16 -translate-x-1/2 transform">
|
||||
<div className="glass rounded-xl px-6 py-3 text-center text-3xl font-bold text-white">
|
||||
{focalLengthDragging.value.focalLength}mm
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<FocalLengthDisplay />
|
||||
|
||||
{/* Top controls */}
|
||||
<div
|
||||
|
||||
@@ -33,6 +33,8 @@ export enum toEngineActions {
|
||||
CHANGE_CAMERA_ASPECT_RATIO = "change_camera_aspect_ratio",
|
||||
// data=CamperaAspectRatio
|
||||
|
||||
CAMERA_CHANGED = "camera_changed",
|
||||
|
||||
UNMUTE = "unmute",
|
||||
// data={version, type, group, object_uuid} if lipsync
|
||||
// data={version, type, group} if global_audio
|
||||
|
||||
+41
-16
@@ -4,16 +4,16 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { PopoverItem } from "~/components/reusable/Popover";
|
||||
import { Button, Input, Label, Tooltip } from "~/components";
|
||||
import { SliderV2 } from "~/components/reusable/SliderV2/SliderV2";
|
||||
import {
|
||||
updateCamera,
|
||||
focalLengthDragging,
|
||||
} from "~/pages/PageEnigma/signals/camera";
|
||||
import { focalLengthDragging } from "~/pages/PageEnigma/signals/camera";
|
||||
import { useState, useEffect } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
interface ExtendedPopoverItem extends PopoverItem {
|
||||
id: string;
|
||||
focalLength: number;
|
||||
position: { x: number; y: number; z: number };
|
||||
rotation: { x: number; y: number; z: number };
|
||||
lookAt: { x: number; y: number; z: number };
|
||||
}
|
||||
|
||||
interface CameraSettingsModalProps {
|
||||
@@ -24,7 +24,7 @@ interface CameraSettingsModalProps {
|
||||
onCameraFocalLengthChange: (id: string, value: number) => void;
|
||||
onAddCamera: () => void;
|
||||
selectedCameraId: string;
|
||||
onSelectCamera: (id: string) => void;
|
||||
handleCameraSelect: (selectedItem: PopoverItem) => void;
|
||||
onDeleteCamera: (id: string) => void;
|
||||
}
|
||||
|
||||
@@ -36,11 +36,20 @@ export const CameraSettingsModal = ({
|
||||
onCameraFocalLengthChange,
|
||||
onAddCamera,
|
||||
selectedCameraId,
|
||||
onSelectCamera,
|
||||
handleCameraSelect,
|
||||
onDeleteCamera,
|
||||
}: CameraSettingsModalProps) => {
|
||||
const selectedCamera = cameras.find((cam) => cam.id === selectedCameraId);
|
||||
const [isDragging, setIsDragging] = useState(false);
|
||||
const [tempCameraName, setTempCameraName] = useState<string>("");
|
||||
const [editingCameraId, setEditingCameraId] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedCamera) {
|
||||
setTempCameraName(selectedCamera.label);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [selectedCamera?.id]);
|
||||
|
||||
useEffect(() => {
|
||||
const handlePointerUp = () => {
|
||||
@@ -67,16 +76,29 @@ export const CameraSettingsModal = ({
|
||||
onCameraFocalLengthChange(id, value);
|
||||
};
|
||||
|
||||
const handleNameBlur = () => {
|
||||
if (selectedCamera && tempCameraName.trim() !== "") {
|
||||
onCameraNameChange(selectedCamera.id, tempCameraName);
|
||||
} else if (selectedCamera) {
|
||||
setTempCameraName(selectedCamera.label);
|
||||
}
|
||||
setEditingCameraId(null);
|
||||
};
|
||||
|
||||
const handleNameFocus = (id: string) => {
|
||||
setEditingCameraId(id);
|
||||
};
|
||||
|
||||
return (
|
||||
<TransitionDialogue
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
className={twMerge(
|
||||
"h-[500px] max-w-3xl transition-opacity duration-200",
|
||||
isDragging ? "opacity-30 hover:opacity-20" : "opacity-100",
|
||||
"h-[500px] max-w-3xl duration-200",
|
||||
isDragging ? "opacity-10" : "opacity-100",
|
||||
)}
|
||||
backdropClassName={twMerge(
|
||||
"transition-opacity duration-200",
|
||||
"duration-200",
|
||||
isDragging ? "opacity-0" : "opacity-100",
|
||||
)}
|
||||
childPadding={false}
|
||||
@@ -99,14 +121,16 @@ export const CameraSettingsModal = ({
|
||||
{cameras.map((camera) => (
|
||||
<button
|
||||
key={camera.id}
|
||||
className={`w-full rounded-lg p-2 text-left transition-colors duration-100 hover:bg-[#63636B]/40 ${
|
||||
className={`h-9 w-full rounded-lg p-2 text-left transition-colors duration-100 hover:bg-[#63636B]/40 ${
|
||||
camera.id === selectedCameraId ? "bg-[#63636B]/40" : ""
|
||||
}`}
|
||||
onClick={() => onSelectCamera(camera.id)}
|
||||
onClick={() => handleCameraSelect(camera)}
|
||||
>
|
||||
<div className="flex items-center gap-2.5 text-sm">
|
||||
{camera.icon}
|
||||
{camera.label}
|
||||
{editingCameraId === camera.id
|
||||
? tempCameraName
|
||||
: camera.label}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
@@ -154,10 +178,11 @@ export const CameraSettingsModal = ({
|
||||
<Input
|
||||
id="camera-name"
|
||||
type="text"
|
||||
value={selectedCamera?.label || ""}
|
||||
onChange={(e) =>
|
||||
selectedCamera &&
|
||||
onCameraNameChange(selectedCamera.id, e.target.value)
|
||||
value={tempCameraName}
|
||||
onChange={(e) => setTempCameraName(e.target.value)}
|
||||
onBlur={handleNameBlur}
|
||||
onFocus={() =>
|
||||
selectedCamera && handleNameFocus(selectedCamera.id)
|
||||
}
|
||||
className="text-sm"
|
||||
/>
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ export const ControlPanelSceneObject = () => {
|
||||
};
|
||||
|
||||
const getScale = () => {
|
||||
const height = pageHeight.value - timelineHeight.value - 64;
|
||||
const height = pageHeight.value - timelineHeight.value - 56;
|
||||
return height < 620 ? height / 620 : 1;
|
||||
};
|
||||
|
||||
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { Transition } from "@headlessui/react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { focalLengthDragging } from "~/pages/PageEnigma/signals/camera";
|
||||
|
||||
export const FocalLengthDisplay = () => {
|
||||
useSignals();
|
||||
|
||||
return (
|
||||
<Transition
|
||||
show={focalLengthDragging.value.isDragging}
|
||||
enter="transition-opacity duration-200"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="transition-opacity duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="absolute left-1/2 top-16 -translate-x-1/2 transform">
|
||||
<div className="glass rounded-xl px-5 py-2.5 text-center text-2xl font-bold text-white">
|
||||
{focalLengthDragging.value.focalLength}mm
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
@@ -1,9 +1,15 @@
|
||||
import { TransitionDialogue } from "~/components/reusable/TransitionDialogue";
|
||||
import { faCheck, faXmark } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { faCheck } from "@fortawesome/pro-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { Button, LoadingSpinner, CloseButton } from "~/components";
|
||||
import { TabSelector } from "~/components/reusable/TabSelector";
|
||||
import { useState, useEffect, Fragment } from "react";
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
Fragment,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { LibraryModalApi } from "./LibraryModalApi";
|
||||
import { FilterMediaClasses } from "../../../../enums";
|
||||
import { BucketConfig } from "../../../../api/BucketConfig";
|
||||
@@ -38,323 +44,351 @@ interface LibraryModalProps {
|
||||
onTabChange: (tabId: string) => void;
|
||||
}
|
||||
|
||||
export const LibraryModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
mode = "view",
|
||||
selectedItemIds = [],
|
||||
onSelectItem,
|
||||
maxSelections = 4,
|
||||
onUseSelected,
|
||||
tabs,
|
||||
activeTab,
|
||||
onTabChange,
|
||||
}: LibraryModalProps) => {
|
||||
const [groupedItems, setGroupedItems] = useState<GroupedItems>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lightboxImage, setLightboxImage] = useState<LibraryItem | null>(null);
|
||||
const [isLightboxVisible, setIsLightboxVisible] = useState(false);
|
||||
const [failedImageUrls] = useState<Set<string>>(new Set());
|
||||
const bucketConfig = new BucketConfig();
|
||||
const api = new LibraryModalApi();
|
||||
|
||||
const formatDate = (date: string) => {
|
||||
const d = new Date(date);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
};
|
||||
|
||||
const groupItemsByDate = (items: LibraryItem[]) => {
|
||||
const grouped = items.reduce((acc: GroupedItems, item) => {
|
||||
const dateKey = formatDate(item.createdAt);
|
||||
if (!acc[dateKey]) {
|
||||
acc[dateKey] = [];
|
||||
}
|
||||
acc[dateKey].push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Sort dates in descending order
|
||||
return Object.fromEntries(
|
||||
Object.entries(grouped).sort(
|
||||
(a, b) =>
|
||||
new Date(b[1][0].createdAt).getTime() -
|
||||
new Date(a[1][0].createdAt).getTime(),
|
||||
),
|
||||
export const LibraryModal = React.memo(
|
||||
({
|
||||
isOpen,
|
||||
onClose,
|
||||
mode = "view",
|
||||
selectedItemIds = [],
|
||||
onSelectItem,
|
||||
maxSelections = 4,
|
||||
onUseSelected,
|
||||
tabs,
|
||||
activeTab,
|
||||
onTabChange,
|
||||
}: LibraryModalProps) => {
|
||||
const [groupedItems, setGroupedItems] = useState<GroupedItems>({});
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [lightboxImage, setLightboxImage] = useState<LibraryItem | null>(
|
||||
null,
|
||||
);
|
||||
};
|
||||
const [isLightboxVisible, setIsLightboxVisible] = useState(false);
|
||||
const [failedImageUrls] = useState<Set<string>>(new Set());
|
||||
const bucketConfig = useMemo(() => new BucketConfig(), []);
|
||||
const api = useMemo(() => new LibraryModalApi(), []);
|
||||
|
||||
const handleImageError = (url: string) => {
|
||||
failedImageUrls.add(url);
|
||||
};
|
||||
const formatDate = useCallback((date: string) => {
|
||||
const d = new Date(date);
|
||||
return d.toLocaleDateString("en-US", {
|
||||
weekday: "short",
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
});
|
||||
}, []);
|
||||
|
||||
const getImageUrl = (path: string | undefined | null) => {
|
||||
if (!path) return null;
|
||||
const url = bucketConfig.getGcsUrl(path);
|
||||
return failedImageUrls.has(url) ? null : url;
|
||||
};
|
||||
const groupItemsByDate = useCallback(
|
||||
(items: LibraryItem[]) => {
|
||||
const grouped = items.reduce((acc: GroupedItems, item) => {
|
||||
const dateKey = formatDate(item.createdAt);
|
||||
if (!acc[dateKey]) {
|
||||
acc[dateKey] = [];
|
||||
}
|
||||
acc[dateKey].push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
useEffect(() => {
|
||||
const loadItems = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await api.listUserMediaFiles({
|
||||
filter_media_classes: [FilterMediaClasses.IMAGE],
|
||||
});
|
||||
if (response.success && response.data) {
|
||||
const newItems = response.data.map((item) => ({
|
||||
id: item.token,
|
||||
label: item.maybe_title || "Untitled",
|
||||
thumbnail: getImageUrl(
|
||||
item.cover_image.maybe_cover_image_public_bucket_path ||
|
||||
item.public_bucket_path,
|
||||
),
|
||||
fullImage: getImageUrl(item.public_bucket_path),
|
||||
createdAt: item.created_at,
|
||||
}));
|
||||
setGroupedItems(groupItemsByDate(newItems));
|
||||
// Sort dates in descending order
|
||||
return Object.fromEntries(
|
||||
Object.entries(grouped).sort(
|
||||
(a, b) =>
|
||||
new Date(b[1][0].createdAt).getTime() -
|
||||
new Date(a[1][0].createdAt).getTime(),
|
||||
),
|
||||
);
|
||||
},
|
||||
[formatDate],
|
||||
);
|
||||
|
||||
const handleImageError = useCallback(
|
||||
(url: string) => {
|
||||
failedImageUrls.add(url);
|
||||
},
|
||||
[failedImageUrls],
|
||||
);
|
||||
|
||||
const getImageUrl = useCallback(
|
||||
(path: string | undefined | null) => {
|
||||
if (!path) return null;
|
||||
const url = bucketConfig.getGcsUrl(path);
|
||||
return failedImageUrls.has(url) ? null : url;
|
||||
},
|
||||
[bucketConfig, failedImageUrls],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const loadItems = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const response = await api.listUserMediaFiles({
|
||||
filter_media_classes: [FilterMediaClasses.IMAGE],
|
||||
});
|
||||
if (response.success && response.data) {
|
||||
const newItems = response.data.map((item) => ({
|
||||
id: item.token,
|
||||
label: item.maybe_title || "Untitled",
|
||||
thumbnail: getImageUrl(
|
||||
item.cover_image.maybe_cover_image_public_bucket_path ||
|
||||
item.public_bucket_path,
|
||||
),
|
||||
fullImage: getImageUrl(item.public_bucket_path),
|
||||
createdAt: item.created_at,
|
||||
}));
|
||||
setGroupedItems(groupItemsByDate(newItems));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch library items:", error);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch library items:", error);
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
if (isOpen) {
|
||||
loadItems();
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab, isOpen]);
|
||||
|
||||
if (isOpen) {
|
||||
loadItems();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeTab, isOpen]);
|
||||
// useEffect(() => {
|
||||
// console.log("LibraryModal render caused by:", {
|
||||
// isOpen,
|
||||
// activeTab,
|
||||
// selectedItemIds,
|
||||
// });
|
||||
// }, [isOpen, activeTab, selectedItemIds]);
|
||||
|
||||
const handleItemClick = (item: LibraryItem) => {
|
||||
if (mode === "select" && onSelectItem) {
|
||||
onSelectItem(item.id);
|
||||
} else {
|
||||
setLightboxImage(item);
|
||||
setIsLightboxVisible(true);
|
||||
}
|
||||
};
|
||||
const handleItemClick = useCallback(
|
||||
(item: LibraryItem) => {
|
||||
if (mode === "select" && onSelectItem) {
|
||||
onSelectItem(item.id);
|
||||
} else {
|
||||
setLightboxImage(item);
|
||||
setIsLightboxVisible(true);
|
||||
}
|
||||
},
|
||||
[mode, onSelectItem],
|
||||
);
|
||||
|
||||
const handleCloseLightbox = () => {
|
||||
setIsLightboxVisible(false);
|
||||
// Wait for animation to complete before removing the image
|
||||
setTimeout(() => setLightboxImage(null), 300);
|
||||
};
|
||||
const handleCloseLightbox = useCallback(() => {
|
||||
setIsLightboxVisible(false);
|
||||
// Wait for animation to complete before removing the image
|
||||
setTimeout(() => setLightboxImage(null), 300);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TransitionDialogue
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
className={twMerge(
|
||||
"h-[620px] max-w-4xl",
|
||||
mode === "view" && "h-[80vh] max-h-[870px] max-w-7xl",
|
||||
)}
|
||||
childPadding={false}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b border-white/10 p-4 py-3">
|
||||
<div className="grid grid-cols-3 items-center">
|
||||
<h2 className="text-xl font-semibold">
|
||||
{mode === "select" ? "Select Images" : "My Gallery"}
|
||||
</h2>
|
||||
<div className="flex items-center justify-center">
|
||||
<TabSelector
|
||||
tabs={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={onTabChange}
|
||||
className="w-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<CloseButton onClick={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
const handleDeselectAll = useCallback(() => {
|
||||
selectedItemIds.forEach((id) => onSelectItem?.(id));
|
||||
}, [selectedItemIds, onSelectItem]);
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-white/60">
|
||||
<LoadingSpinner className="h-12 w-12" />
|
||||
const handleUseSelected = useCallback(() => {
|
||||
const selectedItems = Object.values(groupedItems)
|
||||
.flat()
|
||||
.filter((item) => selectedItemIds.includes(item.id));
|
||||
onUseSelected?.(selectedItems);
|
||||
}, [groupedItems, selectedItemIds, onUseSelected]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<TransitionDialogue
|
||||
isOpen={isOpen}
|
||||
onClose={onClose}
|
||||
className={twMerge(
|
||||
"h-[620px] max-w-4xl",
|
||||
mode === "view" && "h-[80vh] max-h-[870px] max-w-7xl",
|
||||
)}
|
||||
childPadding={false}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="border-b border-white/10 p-4 py-3">
|
||||
<div className="grid grid-cols-3 items-center">
|
||||
<h2 className="text-xl font-semibold">
|
||||
{mode === "select" ? "Select Images" : "My Gallery"}
|
||||
</h2>
|
||||
<div className="flex items-center justify-center">
|
||||
<TabSelector
|
||||
tabs={tabs}
|
||||
activeTab={activeTab}
|
||||
onTabChange={onTabChange}
|
||||
className="w-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<CloseButton onClick={onClose} />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{Object.entries(groupedItems).map(([date, dateItems]) => (
|
||||
<div key={date}>
|
||||
<h3 className="text-md mb-2 font-medium text-white/60">
|
||||
{date}
|
||||
</h3>
|
||||
<div
|
||||
className={twMerge(
|
||||
"grid grid-cols-5 gap-1",
|
||||
mode === "view" && "grid-cols-5",
|
||||
)}
|
||||
>
|
||||
{dateItems.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
className={twMerge(
|
||||
"group relative aspect-square overflow-hidden rounded-md border-[3px] transition-all",
|
||||
mode === "select" &&
|
||||
selectedItemIds.includes(item.id)
|
||||
? "border-brand-primary"
|
||||
: "border-transparent hover:border-white",
|
||||
)}
|
||||
onClick={() => handleItemClick(item)}
|
||||
>
|
||||
<div className="relative h-full w-full">
|
||||
{!item.thumbnail ? (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gray-800">
|
||||
<span className="text-white/60">
|
||||
Image not available
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={item.thumbnail}
|
||||
alt={item.label}
|
||||
className="h-full w-full object-cover"
|
||||
onError={() =>
|
||||
handleImageError(item.thumbnail!)
|
||||
}
|
||||
crossOrigin="anonymous"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex-1 overflow-y-auto p-4">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-white/60">
|
||||
<LoadingSpinner className="h-12 w-12" />
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-6">
|
||||
{Object.entries(groupedItems).map(([date, dateItems]) => (
|
||||
<div key={date}>
|
||||
<h3 className="text-md mb-2 font-medium text-white/60">
|
||||
{date}
|
||||
</h3>
|
||||
<div
|
||||
className={twMerge(
|
||||
"grid grid-cols-5 gap-1",
|
||||
mode === "view" && "grid-cols-5",
|
||||
)}
|
||||
>
|
||||
{dateItems.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
className={twMerge(
|
||||
"group relative aspect-square overflow-hidden rounded-md border-[3px] transition-all",
|
||||
mode === "select" &&
|
||||
selectedItemIds.includes(item.id)
|
||||
? "border-brand-primary"
|
||||
: "border-transparent hover:border-white",
|
||||
)}
|
||||
onClick={() => handleItemClick(item)}
|
||||
>
|
||||
<div className="relative h-full w-full">
|
||||
{!item.thumbnail ? (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gray-800">
|
||||
<span className="text-white/60">
|
||||
Image not available
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={item.thumbnail}
|
||||
alt={item.label}
|
||||
className="h-full w-full object-cover"
|
||||
onError={() =>
|
||||
handleImageError(item.thumbnail!)
|
||||
}
|
||||
crossOrigin="anonymous"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
)}
|
||||
{mode === "select" &&
|
||||
selectedItemIds.includes(item.id) && (
|
||||
<div className="absolute inset-0 bg-white/30" />
|
||||
)}
|
||||
</div>
|
||||
{mode === "select" &&
|
||||
selectedItemIds.includes(item.id) && (
|
||||
<div className="absolute inset-0 bg-white/30" />
|
||||
<div className="absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-brand-primary">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{mode === "select" &&
|
||||
selectedItemIds.includes(item.id) && (
|
||||
<div className="absolute right-2 top-2 flex h-6 w-6 items-center justify-center rounded-full bg-brand-primary">
|
||||
<FontAwesomeIcon
|
||||
icon={faCheck}
|
||||
className="text-sm"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</button>
|
||||
))}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{mode === "select" && (
|
||||
<div className="flex items-center justify-between border-t border-white/10 bg-black/25 p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-sm font-semibold text-white/80">
|
||||
{selectedItemIds.length}/{maxSelections} selected
|
||||
</div>
|
||||
))}
|
||||
{selectedItemIds.length > 0 && (
|
||||
<span className="text-white/10">|</span>
|
||||
)}
|
||||
{selectedItemIds.length > 0 && (
|
||||
<button
|
||||
onClick={handleDeselectAll}
|
||||
className="text-sm text-white/60 hover:text-white"
|
||||
>
|
||||
Deselect All
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleUseSelected}
|
||||
disabled={selectedItemIds.length === 0}
|
||||
>
|
||||
Use selected
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TransitionDialogue>
|
||||
|
||||
{mode === "select" && (
|
||||
<div className="flex items-center justify-between border-t border-white/10 bg-black/25 p-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="text-sm font-semibold text-white/80">
|
||||
{selectedItemIds.length}/{maxSelections} selected
|
||||
</div>
|
||||
{selectedItemIds.length > 0 && (
|
||||
<span className="text-white/10">|</span>
|
||||
)}
|
||||
{selectedItemIds.length > 0 && (
|
||||
<button
|
||||
onClick={() => {
|
||||
selectedItemIds.forEach((id) => onSelectItem?.(id));
|
||||
}}
|
||||
className="text-sm text-white/60 hover:text-white"
|
||||
>
|
||||
Deselect All
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
const selectedItems = Object.values(groupedItems)
|
||||
.flat()
|
||||
.filter((item) => selectedItemIds.includes(item.id));
|
||||
onUseSelected?.(selectedItems);
|
||||
}}
|
||||
disabled={selectedItemIds.length === 0}
|
||||
>
|
||||
Use selected
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</TransitionDialogue>
|
||||
|
||||
{/* Lightbox Modal */}
|
||||
{lightboxImage &&
|
||||
createPortal(
|
||||
<Transition appear show={isLightboxVisible} as={Fragment}>
|
||||
<div className="fixed inset-0 z-[100]">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
className="fixed inset-0 cursor-pointer bg-black/60"
|
||||
onClick={handleCloseLightbox}
|
||||
/>
|
||||
</Transition.Child>
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center p-4"
|
||||
onClick={handleCloseLightbox}
|
||||
>
|
||||
{/* Lightbox Modal */}
|
||||
{lightboxImage &&
|
||||
createPortal(
|
||||
<Transition appear show={isLightboxVisible} as={Fragment}>
|
||||
<div className="fixed inset-0 z-[100]">
|
||||
<Transition.Child
|
||||
as={Fragment}
|
||||
enter="ease-out duration-300"
|
||||
enterFrom="opacity-0 scale-95"
|
||||
enterTo="opacity-100 scale-100"
|
||||
enterFrom="opacity-0"
|
||||
enterTo="opacity-100"
|
||||
leave="ease-in duration-200"
|
||||
leaveFrom="opacity-100 scale-100"
|
||||
leaveTo="opacity-0 scale-95"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div
|
||||
className="relative h-[90vh] w-[80vw] rounded-xl bg-[#2C2C2C]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<CloseButton
|
||||
onClick={handleCloseLightbox}
|
||||
className="absolute right-4 top-4 z-10"
|
||||
/>
|
||||
<div className="grid h-full grid-cols-3 gap-6">
|
||||
<div className="col-span-2 flex h-full items-center justify-center overflow-hidden bg-black/40">
|
||||
{!lightboxImage.fullImage ? (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gray-800">
|
||||
<span className="text-white/60">
|
||||
Image not available
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={lightboxImage.fullImage}
|
||||
alt={lightboxImage.label}
|
||||
className="h-full w-full object-contain"
|
||||
onError={() =>
|
||||
handleImageError(lightboxImage.fullImage!)
|
||||
}
|
||||
crossOrigin="anonymous"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-5">Prompt</div>
|
||||
</div>
|
||||
</div>
|
||||
className="fixed inset-0 cursor-pointer bg-black/60"
|
||||
onClick={handleCloseLightbox}
|
||||
/>
|
||||
</Transition.Child>
|
||||
<div
|
||||
className="fixed inset-0 flex items-center justify-center p-4"
|
||||
onClick={handleCloseLightbox}
|
||||
>
|
||||
<Transition.Child
|
||||
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"
|
||||
>
|
||||
<div
|
||||
className="relative h-[90vh] w-[80vw] rounded-xl bg-[#2C2C2C]"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<CloseButton
|
||||
onClick={handleCloseLightbox}
|
||||
className="absolute right-4 top-4 z-10"
|
||||
/>
|
||||
<div className="grid h-full grid-cols-3 gap-6">
|
||||
<div className="col-span-2 flex h-full items-center justify-center overflow-hidden bg-black/40">
|
||||
{!lightboxImage.fullImage ? (
|
||||
<div className="flex h-full w-full items-center justify-center bg-gray-800">
|
||||
<span className="text-white/60">
|
||||
Image not available
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<img
|
||||
src={lightboxImage.fullImage}
|
||||
alt={lightboxImage.label}
|
||||
className="h-full w-full object-contain"
|
||||
onError={() =>
|
||||
handleImageError(lightboxImage.fullImage!)
|
||||
}
|
||||
crossOrigin="anonymous"
|
||||
referrerPolicy="no-referrer"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-5">Prompt</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition.Child>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>,
|
||||
document.body,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
</Transition>,
|
||||
document.body,
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
LibraryModal.displayName = "LibraryModal";
|
||||
|
||||
@@ -38,7 +38,7 @@ export const OnboardingHelper = () => {
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="glass pointer-events-none absolute left-1/2 top-52 z-10 flex -translate-x-1/2 transform items-center justify-center gap-2 rounded-2xl border-2 border-brand-primary !bg-brand-primary/10 px-5 py-4">
|
||||
<div className="glass pointer-events-none absolute left-1/2 top-48 z-10 flex -translate-x-1/2 transform items-center justify-center gap-2 rounded-2xl border-2 border-brand-primary !bg-brand-primary/10 px-5 py-4">
|
||||
<div className="flex items-center">
|
||||
<p className="pr-3 text-lg font-semibold">Hold</p>
|
||||
<Mouse button="left" />
|
||||
|
||||
@@ -140,7 +140,7 @@ export const Outliner = () => {
|
||||
|
||||
useEffect(() => {
|
||||
const updateEditorHeight = () => {
|
||||
setEditorHeight(pageHeight.value - timelineHeight.value - 64);
|
||||
setEditorHeight(pageHeight.value - timelineHeight.value - 56);
|
||||
};
|
||||
|
||||
updateEditorHeight();
|
||||
|
||||
+8
-1
@@ -24,16 +24,19 @@ import { CameraAspectRatio, EditorStates } from "~/pages/PageEnigma/enums";
|
||||
import { CameraViewCanvas } from "~/pages/PageEnigma/comps/EngineCanvases";
|
||||
|
||||
import { Button, ButtonIcon, Tooltip } from "~/components";
|
||||
import { useEffect } from "react";
|
||||
|
||||
export const PreviewEngineCamera = () => {
|
||||
useSignals();
|
||||
|
||||
const handleButtonCameraView = () => {
|
||||
console.log("Camera view button clicked");
|
||||
Queue.publish({
|
||||
queueName: QueueNames.TO_ENGINE,
|
||||
action: toEngineActions.TOGGLE_CAMERA_STATE,
|
||||
data: null,
|
||||
});
|
||||
console.log("Action published to queue");
|
||||
};
|
||||
|
||||
const getLargeScreenHeightClass = () => {
|
||||
@@ -67,11 +70,15 @@ export const PreviewEngineCamera = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
console.log("Editor state changed:", editorState.value);
|
||||
}, [editorState.value]);
|
||||
|
||||
return (
|
||||
<div
|
||||
id="preview-engine-camera"
|
||||
className={twMerge(
|
||||
"origin-bottom-left shadow-lg", //hidden right now with css
|
||||
"hidden origin-bottom-left shadow-lg", //hidden right now with css
|
||||
editorState.value === EditorStates.PREVIEW
|
||||
? "invisible h-0 w-0"
|
||||
: "visible",
|
||||
|
||||
@@ -27,7 +27,7 @@ export const PreviewFrameImage = () => {
|
||||
pageWidth.value -
|
||||
(sidePanelVisible.value ? sidePanelWidth.value : 0) -
|
||||
84,
|
||||
height: pageHeight.value - timelineHeight.value - 64,
|
||||
height: pageHeight.value - timelineHeight.value - 56,
|
||||
}}
|
||||
>
|
||||
<div className="relative flex h-full w-full flex-col items-center justify-center gap-5">
|
||||
@@ -48,7 +48,7 @@ export const PreviewFrameImage = () => {
|
||||
pageWidth.value -
|
||||
(sidePanelVisible.value ? sidePanelWidth.value : 0) -
|
||||
84,
|
||||
height: pageHeight.value - timelineHeight.value - 64,
|
||||
height: pageHeight.value - timelineHeight.value - 56,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -46,6 +46,7 @@ import {
|
||||
addCamera,
|
||||
deleteCamera,
|
||||
updateCamera,
|
||||
focalLengthDragging,
|
||||
} from "~/pages/PageEnigma/signals/camera";
|
||||
|
||||
interface ReferenceImage {
|
||||
@@ -93,7 +94,6 @@ export const PromptBox = () => {
|
||||
[],
|
||||
);
|
||||
const [activeLibraryTab, setActiveLibraryTab] = useState("my-media");
|
||||
const [isGridVisible, setIsGridVisible] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (textareaRef.current) {
|
||||
@@ -121,36 +121,138 @@ export const PromptBox = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [cameraAspectRatio.value]);
|
||||
|
||||
useEffect(() => {
|
||||
setGridVisibility(isGridVisible);
|
||||
}, [isGridVisible]);
|
||||
|
||||
const handleCameraSelect = (selectedItem: PopoverItem) => {
|
||||
const selectedCamera = cameras.value.find(
|
||||
(cam) => cam.label === selectedItem.label,
|
||||
);
|
||||
if (selectedCamera) {
|
||||
if (selectedCamera && editorEngine) {
|
||||
selectedCameraId.value = selectedCamera.id;
|
||||
// Force update camera properties
|
||||
|
||||
// Show focal length display temporarily
|
||||
// TODO: Rename dragging to visible - BFlat
|
||||
focalLengthDragging.value = {
|
||||
isDragging: true,
|
||||
focalLength: selectedCamera.focalLength,
|
||||
};
|
||||
setTimeout(() => {
|
||||
focalLengthDragging.value = {
|
||||
isDragging: false,
|
||||
focalLength: selectedCamera.focalLength,
|
||||
};
|
||||
}, 1500);
|
||||
|
||||
// Update the main camera to match the selected camera's properties
|
||||
if (editorEngine.camera) {
|
||||
// First update position and lookAt
|
||||
editorEngine.camera.position.set(
|
||||
selectedCamera.position.x,
|
||||
selectedCamera.position.y,
|
||||
selectedCamera.position.z,
|
||||
);
|
||||
editorEngine.camera.lookAt(
|
||||
selectedCamera.lookAt.x,
|
||||
selectedCamera.lookAt.y,
|
||||
selectedCamera.lookAt.z,
|
||||
);
|
||||
|
||||
// Update FOV
|
||||
editorEngine.camera.fov = editorEngine.focalLengthToFov(
|
||||
selectedCamera.focalLength,
|
||||
);
|
||||
editorEngine.camera.updateProjectionMatrix();
|
||||
|
||||
// Reset and update camera controls
|
||||
if (editorEngine.cameraViewControls) {
|
||||
editorEngine.cameraViewControls.reset();
|
||||
editorEngine.cameraViewControls.update(0);
|
||||
}
|
||||
|
||||
// Force a render to update the view
|
||||
editorEngine.renderScene();
|
||||
|
||||
// Queue an update to ensure the engine processes the camera change
|
||||
// Queue.publish({
|
||||
// queueName: QueueNames.TO_ENGINE,
|
||||
// action: toEngineActions.CAMERA_CHANGED,
|
||||
// data: selectedCamera,
|
||||
// });
|
||||
}
|
||||
|
||||
// Force update camera properties in the state
|
||||
updateCamera(selectedCamera.id, {
|
||||
focalLength: selectedCamera.focalLength,
|
||||
position: selectedCamera.position,
|
||||
rotation: selectedCamera.rotation,
|
||||
lookAt: selectedCamera.lookAt,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleAddCamera = () => {
|
||||
// Check if we've reached the maximum number of cameras
|
||||
if (cameras.value.length >= 6) {
|
||||
console.warn("Maximum number of cameras (6) reached");
|
||||
return;
|
||||
}
|
||||
|
||||
const newIndex = cameras.value.length + 1;
|
||||
const newId = `cam${newIndex}`;
|
||||
|
||||
// This is for generating random orbital position for the new camera using spherical coordinates
|
||||
const radius = Math.random() * 5 + 7; // Distance from center: 7 to 12 units
|
||||
const theta = Math.random() * Math.PI * 2; // Azimuthal angle: 0 to 2π
|
||||
const phi = Math.PI / 3 + (Math.random() * Math.PI) / 6; // Polar angle: π/3 to π/2 (60° to 90° from horizontal)
|
||||
|
||||
// Convert spherical coordinates to Cartesian coordinates
|
||||
const randomX = radius * Math.sin(phi) * Math.cos(theta);
|
||||
const randomY = Math.abs(radius * Math.cos(phi)) + 2; // Ensure Y is positive and at least 2 units up
|
||||
const randomZ = radius * Math.sin(phi) * Math.sin(theta);
|
||||
|
||||
addCamera({
|
||||
id: newId,
|
||||
label: `Camera ${newIndex}`,
|
||||
focalLength: 35,
|
||||
fov: 70,
|
||||
position: { x: 0, y: 2.5, z: -5 },
|
||||
focalLength: 24,
|
||||
position: {
|
||||
x: randomX,
|
||||
y: randomY,
|
||||
z: randomZ,
|
||||
},
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
lookAt: { x: 0, y: 0, z: 0 },
|
||||
});
|
||||
|
||||
// Switch to the newly created camera
|
||||
selectedCameraId.value = newId;
|
||||
|
||||
// Update the engine camera to match the new camera's properties
|
||||
if (editorEngine && editorEngine.camera) {
|
||||
editorEngine.camera.position.set(randomX, randomY, randomZ);
|
||||
editorEngine.camera.lookAt(0, 0, 0);
|
||||
editorEngine.camera.fov = editorEngine.focalLengthToFov(24);
|
||||
editorEngine.camera.updateProjectionMatrix();
|
||||
|
||||
// Reset and update camera controls
|
||||
if (editorEngine.cameraViewControls) {
|
||||
editorEngine.cameraViewControls.reset();
|
||||
editorEngine.cameraViewControls.update(0);
|
||||
}
|
||||
|
||||
// Force a render to update the view
|
||||
editorEngine.renderScene();
|
||||
|
||||
// Queue.publish({
|
||||
// queueName: QueueNames.TO_ENGINE,
|
||||
// action: toEngineActions.CAMERA_CHANGED,
|
||||
// data: {
|
||||
// id: newId,
|
||||
// label: `Camera ${newIndex}`,
|
||||
// focalLength: 24,
|
||||
// position: { x: randomX, y: randomY, z: randomZ },
|
||||
// rotation: { x: 0, y: 0, z: 0 },
|
||||
// lookAt: { x: 0, y: 0, z: 0 },
|
||||
// },
|
||||
// });
|
||||
}
|
||||
};
|
||||
|
||||
const handleCameraNameChange = (id: string, newName: string) => {
|
||||
@@ -534,6 +636,9 @@ export const PromptBox = () => {
|
||||
<FontAwesomeIcon icon={faCamera} className="h-4 w-4" />
|
||||
),
|
||||
focalLength: cam.focalLength,
|
||||
position: cam.position,
|
||||
rotation: cam.rotation,
|
||||
lookAt: cam.lookAt,
|
||||
}))}
|
||||
onSelect={handleCameraSelect}
|
||||
onAdd={handleAddCamera}
|
||||
@@ -541,11 +646,13 @@ export const PromptBox = () => {
|
||||
<FontAwesomeIcon icon={faCamera} className="h-4 w-4" />
|
||||
}
|
||||
showAddButton
|
||||
disableAddButton={cameras.value.length >= 6}
|
||||
showIconsInList
|
||||
mode="toggle"
|
||||
panelTitle="Camera"
|
||||
panelActionLabel="Settings"
|
||||
onPanelAction={() => setIsCameraSettingsOpen(true)}
|
||||
buttonClassName="max-w-[130px] h-9"
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
@@ -630,12 +737,15 @@ export const PromptBox = () => {
|
||||
selected: cam.id === selectedCameraId.value,
|
||||
icon: <FontAwesomeIcon icon={faCamera} className="h-4 w-4" />,
|
||||
focalLength: cam.focalLength,
|
||||
position: cam.position,
|
||||
rotation: cam.rotation,
|
||||
lookAt: cam.lookAt,
|
||||
}))}
|
||||
onCameraNameChange={handleCameraNameChange}
|
||||
onCameraFocalLengthChange={handleCameraFocalLengthChange}
|
||||
onAddCamera={handleAddCamera}
|
||||
selectedCameraId={selectedCameraId.value}
|
||||
onSelectCamera={(id: string) => (selectedCameraId.value = id)}
|
||||
handleCameraSelect={handleCameraSelect}
|
||||
onDeleteCamera={deleteCamera}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const SceneContainer = ({ children }: { children: React.ReactNode }) => {
|
||||
|
||||
const containerWidth = pageWidth.value;
|
||||
|
||||
const containerHeight = pageHeight.value - timelineHeight.value - 64;
|
||||
const containerHeight = pageHeight.value - timelineHeight.value - 56;
|
||||
|
||||
const callbackRef = useCallback(
|
||||
(node: HTMLDivElement) => {
|
||||
|
||||
@@ -35,7 +35,7 @@ export const SidePanelMenu = ({
|
||||
sidePanelVisible.value ? "border-transparent" : "border-[#363636]",
|
||||
)}
|
||||
style={{
|
||||
height: pageHeight.value - 64,
|
||||
height: pageHeight.value - 56,
|
||||
}}
|
||||
>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
|
||||
@@ -4,9 +4,9 @@ export interface Camera {
|
||||
id: string;
|
||||
label: string;
|
||||
focalLength: number;
|
||||
fov: number;
|
||||
position: { x: number; y: number; z: number };
|
||||
rotation: { x: number; y: number; z: number };
|
||||
lookAt: { x: number; y: number; z: number };
|
||||
}
|
||||
|
||||
export const cameras = signal<Camera[]>([
|
||||
@@ -14,17 +14,17 @@ export const cameras = signal<Camera[]>([
|
||||
id: "main",
|
||||
label: "Main View",
|
||||
focalLength: 17,
|
||||
fov: 70,
|
||||
position: { x: 2.5, y: 2.5, z: -2.5 },
|
||||
position: { x: -2.5, y: 2.5, z: 2.5 },
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
lookAt: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
{
|
||||
id: "cam2",
|
||||
label: "Camera 2",
|
||||
focalLength: 35,
|
||||
fov: 70,
|
||||
position: { x: -2.5, y: 2.5, z: 2.5 },
|
||||
focalLength: 10,
|
||||
position: { x: 0, y: 0.6, z: 1.5 },
|
||||
rotation: { x: 0, y: 0, z: 0 },
|
||||
lookAt: { x: 0, y: 0, z: 0 },
|
||||
},
|
||||
]);
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ img {
|
||||
}
|
||||
|
||||
.topbar-spacer {
|
||||
height: 64px;
|
||||
height: 56px;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[build]
|
||||
command = "./script/netlify_build.sh"
|
||||
#command = "./script/netlify_build.sh"
|
||||
#publish = "build/client"
|
||||
publish = "apps/editor3d/dist/"
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euxo pipefail
|
||||
#set -euxo pipefail
|
||||
|
||||
# echo "Run build script"
|
||||
# npm install
|
||||
@@ -23,19 +23,74 @@ set -euxo pipefail
|
||||
# # echo "List files after build"
|
||||
# # find dist/
|
||||
|
||||
#printf "==================================="
|
||||
#echo "List Env"
|
||||
#env
|
||||
|
||||
printf "==================================="
|
||||
echo "Run build script (TODO: Make strict)"
|
||||
nx build editor3d
|
||||
|
||||
printf "==================================="
|
||||
echo "Current dir (1)"
|
||||
pwd
|
||||
|
||||
printf "==================================="
|
||||
echo "List directory files (1)"
|
||||
pwd
|
||||
ls -lA .
|
||||
|
||||
printf "==================================="
|
||||
echo "List directory files (2)"
|
||||
#find / | grep build/server
|
||||
find / 2> /dev/null | grep build/server
|
||||
|
||||
|
||||
printf "==================================="
|
||||
echo "LIST DIRECTORY FILES (3)"
|
||||
#find / | grep build/server
|
||||
find / 2> /dev/null | grep EnvironmentVar
|
||||
find / 2> /dev/null | grep PageEnigma
|
||||
|
||||
|
||||
printf "==================================="
|
||||
echo "Find build manifest"
|
||||
find . | grep manifest.json
|
||||
|
||||
printf "==================================="
|
||||
echo "Change to project dir"
|
||||
pushd apps/editor3d/
|
||||
|
||||
printf "==================================="
|
||||
echo "Current dir (2)"
|
||||
pwd
|
||||
|
||||
printf "==================================="
|
||||
echo "List directory files (4)"
|
||||
pwd
|
||||
ls -lA .
|
||||
|
||||
printf "==================================="
|
||||
echo "List build directory files"
|
||||
ls -lA build/
|
||||
|
||||
mkdir dist/
|
||||
|
||||
printf "==================================="
|
||||
echo "Copy files"
|
||||
cp -r build/server/* dist/
|
||||
cp -r build/client/* dist/
|
||||
|
||||
#echo "Copy netlify configs into dist"
|
||||
#cp _headers dist/
|
||||
cp _headers dist/
|
||||
#cp _redirects dist/
|
||||
|
||||
#echo "Copy netlify 404.html page into dist"
|
||||
#cp "404.html" dist/
|
||||
|
||||
|
||||
printf "==================================="
|
||||
echo "List files after build"
|
||||
find dist/
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user