mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-10 20:11:49 +00:00
ui-pagescene: migrate ControlsTopButtons into lib (file menu + outliner toggle)
ControlsTopButtons + Help + LoadUserScenes + NewSceneFromTemplate +
ScenePicker + SceneCard now live in
@storyteller/ui-pagescene/comps/ControlsTopButtons/. They access host
plumbing through the adapter (showToast, listUserMediaFiles,
listFeaturedMediaFiles, getCdnUrl, onSceneTitleChange) and read scene
metadata reactively from the new sceneMeta + currentUserToken store
fields.
Lib additions:
- PageSceneStore.sceneMeta + currentUserToken with setSceneMeta /
setCurrentUserToken actions; SceneMeta interface exported
- comps/ControlsTopButtons/{ControlsTopButtons,Help,LoadUserScenes,
NewSceneFromTemplate,ScenePicker}/
Apps changes:
- ~/signals/scene.ts: signalScene now mirrors into PageSceneStore so
the lib has reactive read access without depending on signals
- pages/PageScene/PageScene.tsx: lifted the URL ↔ scene-token sync
useSignalEffect out of ControlsTopButtons (router-aware bookkeeping
belongs at the apps shell), plus useSignalEffect mirroring
authentication.userInfo into store.currentUserToken
- pages/PageScene/PageEditor.tsx: ControlsTopButtons import now
resolves to @storyteller/ui-pagescene
- Deleted: pages/PageScene/comps/ControlsTopButtons/
Typecheck: 51 → 47 (deleted ControlsTopButtons had several pre-
existing typecheck issues that disappear in the rewrite). Lib build
clean.
This commit is contained in:
1 parent
6067849ef5
commit
8556ef0a6b
21 files changed
+241
-220
No files matched your search
@@ -1,12 +1,12 @@
|
||||
import React, { useContext, useEffect, useRef } from "react";
|
||||
import { useSignals } from "@preact/signals-react/runtime";
|
||||
import { Controls3D } from "./comps/Controls3D";
|
||||
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
|
||||
import { pageHeight, pageWidth } from "~/signals";
|
||||
|
||||
import {
|
||||
CameraAspectRatio,
|
||||
ControlPanelSceneObject,
|
||||
ControlsTopButtons,
|
||||
EditorCanvas,
|
||||
EngineContext,
|
||||
FocalLengthDisplay,
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// scene state when the user briefly visits another tab.
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { useLocation, useNavigate, useParams } from "react-router-dom";
|
||||
import { useSignalEffect } from "@preact/signals-react/runtime";
|
||||
import {
|
||||
DragComponent,
|
||||
EditorLoadingBar,
|
||||
@@ -15,10 +17,19 @@ import {
|
||||
usePageSceneStore,
|
||||
} from "@storyteller/ui-pagescene";
|
||||
import { useTabStore } from "~/pages/Stores/TabState";
|
||||
import { authentication, scene } from "~/signals";
|
||||
import { getCurrentLocationWithoutParams } from "~/utilities";
|
||||
import { PageEditor } from "~/pages/PageScene/PageEditor";
|
||||
import { useTauriPageSceneAdapter } from "./useTauriPageSceneAdapter";
|
||||
|
||||
export const PageScene = ({ sceneToken }: { sceneToken?: string }) => {
|
||||
// Route-aware bookkeeping — lifted out of ControlsTopButtons so the
|
||||
// lib stays router-agnostic. Keeps the URL in sync with the
|
||||
// currently loaded scene's token so a refresh / share preserves
|
||||
// the scene selection.
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
// Tab-cache plumbing. The provider only knows about its own React
|
||||
// lifecycle; the host decides where the in-memory cache string
|
||||
// lives. Reading the current value once on render is fine — the
|
||||
@@ -46,6 +57,37 @@ export const PageScene = ({ sceneToken }: { sceneToken?: string }) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Mirror the host's authentication signal into the lib store so
|
||||
// lib-resident UI (ControlsTopButtons) can do ownership permission
|
||||
// checks without depending on the host's signal system.
|
||||
useSignalEffect(() => {
|
||||
usePageSceneStore
|
||||
.getState()
|
||||
.setCurrentUserToken(authentication.userInfo.value?.user_token);
|
||||
});
|
||||
|
||||
// URL ↔ loaded scene sync. Mirrors the previous useSignalEffect
|
||||
// that lived inside ControlsTopButtons; lives here so the lib
|
||||
// doesn't need react-router-dom.
|
||||
useSignalEffect(() => {
|
||||
if (scene.value.isInitializing) return;
|
||||
const currentLocation = getCurrentLocationWithoutParams(
|
||||
location.pathname,
|
||||
params,
|
||||
);
|
||||
if (scene.value.token === undefined) {
|
||||
if (params.sceneToken) {
|
||||
history.pushState({}, "", currentLocation);
|
||||
}
|
||||
navigate(currentLocation, { replace: true });
|
||||
} else if (scene.value.token) {
|
||||
if (params.sceneToken && scene.value.token !== params.sceneToken) {
|
||||
history.pushState({}, "", currentLocation + scene.value.token);
|
||||
}
|
||||
navigate(currentLocation + scene.value.token, { replace: true });
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<EngineProvider
|
||||
sceneToken={sceneToken}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
export {
|
||||
Help,
|
||||
Key,
|
||||
KeyGroup,
|
||||
Mouse,
|
||||
Plus,
|
||||
ShortcutsGroup,
|
||||
} from "@storyteller/ui-pagescene";
|
||||
@@ -1 +0,0 @@
|
||||
export { Help } from "./Help";
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
export { ScenePicker } from "./ScenePicker";
|
||||
export type { SceneTypes } from "./ScenePicker";
|
||||
@@ -1,2 +0,0 @@
|
||||
export { ControlsTopButtons } from './ControlsTopButtons';
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { signal } from "@preact/signals-core";
|
||||
import { usePageSceneStore } from "@storyteller/ui-pagescene";
|
||||
|
||||
export interface SceneSignal {
|
||||
isInitializing?: boolean;
|
||||
@@ -16,13 +17,24 @@ export const scene = signal<SceneSignal>({
|
||||
});
|
||||
|
||||
export const signalScene = (data: SceneSignal) => {
|
||||
scene.value = {
|
||||
const next = {
|
||||
...data,
|
||||
isInitializing: false,
|
||||
isModified: true,
|
||||
//TODO: MILES: implement flagging of isModified
|
||||
// from editor side, and take this out after
|
||||
};
|
||||
scene.value = next;
|
||||
// Mirror into the lib's PageSceneStore so ControlsTopButtons (and
|
||||
// any other lib component) can read scene metadata reactively
|
||||
// without depending on the host's signal system.
|
||||
usePageSceneStore.getState().setSceneMeta({
|
||||
title: next.title,
|
||||
token: next.token,
|
||||
ownerToken: next.ownerToken,
|
||||
isModified: next.isModified,
|
||||
isInitializing: next.isInitializing,
|
||||
});
|
||||
};
|
||||
|
||||
export const getSceneSignals = (): SceneSignal => {
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
|
||||
{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.es2021.d.ts","../../node_modules/typescript/lib/lib.es2022.d.ts","../../node_modules/typescript/lib/lib.es2023.d.ts","../../node_modules/typescript/lib/lib.es2024.d.ts","../../node_modules/typescript/lib/lib.esnext.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2021.promise.d.ts","../../node_modules/typescript/lib/lib.es2021.string.d.ts","../../node_modules/typescript/lib/lib.es2021.weakref.d.ts","../../node_modules/typescript/lib/lib.es2021.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.array.d.ts","../../node_modules/typescript/lib/lib.es2022.error.d.ts","../../node_modules/typescript/lib/lib.es2022.intl.d.ts","../../node_modules/typescript/lib/lib.es2022.object.d.ts","../../node_modules/typescript/lib/lib.es2022.string.d.ts","../../node_modules/typescript/lib/lib.es2022.regexp.d.ts","../../node_modules/typescript/lib/lib.es2023.array.d.ts","../../node_modules/typescript/lib/lib.es2023.collection.d.ts","../../node_modules/typescript/lib/lib.es2023.intl.d.ts","../../node_modules/typescript/lib/lib.es2024.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2024.collection.d.ts","../../node_modules/typescript/lib/lib.es2024.object.d.ts","../../node_modules/typescript/lib/lib.es2024.promise.d.ts","../../node_modules/typescript/lib/lib.es2024.regexp.d.ts","../../node_modules/typescript/lib/lib.es2024.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2024.string.d.ts","../../node_modules/typescript/lib/lib.esnext.array.d.ts","../../node_modules/typescript/lib/lib.esnext.collection.d.ts","../../node_modules/typescript/lib/lib.esnext.intl.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.esnext.promise.d.ts","../../node_modules/typescript/lib/lib.esnext.decorators.d.ts","../../node_modules/typescript/lib/lib.esnext.iterator.d.ts","../../node_modules/typescript/lib/lib.esnext.float16.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/@remix-run/node/dist/globals.d.ts","../../node_modules/@types/cookie/index.d.ts","../../node_modules/@remix-run/server-runtime/dist/crypto.d.ts","../../node_modules/@remix-run/server-runtime/dist/cookies.d.ts","../../node_modules/@remix-run/server-runtime/dLine truncated
|
||||
@@ -15,6 +15,7 @@ export { usePageSceneStore } from "./lib/PageSceneStore";
|
||||
export type {
|
||||
Camera,
|
||||
FocalLengthDragging,
|
||||
SceneMeta,
|
||||
SceneObject,
|
||||
SceneObjectKind,
|
||||
OutlinerItem,
|
||||
@@ -183,6 +184,7 @@ export { getSceneGenerationMetaData } from "./lib/sceneMetadata";
|
||||
// are either surfaced via adapter slots or replaced with
|
||||
// lib-internal state.
|
||||
export { AssetMenu, AssetModal } from "./lib/comps/AssetMenu";
|
||||
export { ControlsTopButtons } from "./lib/comps/ControlsTopButtons";
|
||||
export { DragComponent } from "./lib/comps/DragComponent/DragComponent";
|
||||
export { PrecisionSelector } from "./lib/comps/PrecisionSelector/PrecisionSelector";
|
||||
export { FocalLengthDisplay } from "./lib/comps/FocalLengthDisplay/FocalLengthDisplay";
|
||||
|
||||
@@ -13,6 +13,17 @@ import { Simple3DVector } from "./datastructures/common";
|
||||
|
||||
export type { Camera, FocalLengthDragging };
|
||||
|
||||
// Scene metadata — what host tracks in its `signalScene`. Mirrored
|
||||
// into the store so ControlsTopButtons (lib-resident) can read it
|
||||
// reactively. Host calls `setSceneMeta` whenever its signal changes.
|
||||
export interface SceneMeta {
|
||||
title: string | undefined;
|
||||
token: string | undefined;
|
||||
ownerToken: string | undefined;
|
||||
isModified: boolean | undefined;
|
||||
isInitializing: boolean;
|
||||
}
|
||||
|
||||
export type SceneObjectKind = "object" | "character" | "shape";
|
||||
|
||||
export interface SceneObject {
|
||||
@@ -164,6 +175,14 @@ interface PageSceneState {
|
||||
is3DEditorInitialized: boolean;
|
||||
is3DSceneLoaded: boolean;
|
||||
|
||||
// scene metadata — title/token/owner/dirty state. Mirrors what the
|
||||
// host's signalScene tracks so ControlsTopButtons (lib-resident)
|
||||
// can read it reactively without depending on host signals.
|
||||
sceneMeta: SceneMeta;
|
||||
// Current logged-in user (read from host auth signal). Used for
|
||||
// ownership permission checks in ControlsTopButtons.
|
||||
currentUserToken: string | undefined;
|
||||
|
||||
// canvas DOM refs (set by canvas components on mount; consumed by
|
||||
// the engine + hooks)
|
||||
sceneContainerEl: HTMLDivElement | null;
|
||||
@@ -245,6 +264,12 @@ interface PageSceneState {
|
||||
setIs3DEditorInitialized: (initialized: boolean) => void;
|
||||
setIs3DSceneLoaded: (loaded: boolean) => void;
|
||||
|
||||
// scene metadata + auth — driven by host via lifecycle effects in
|
||||
// the host wrapper (e.g. apps/.../PageScene.tsx mirrors signalScene
|
||||
// and authentication.userInfo into these).
|
||||
setSceneMeta: (meta: Partial<SceneMeta>) => void;
|
||||
setCurrentUserToken: (token: string | undefined) => void;
|
||||
|
||||
// canvas refs
|
||||
setSceneContainerEl: (el: HTMLDivElement | null) => void;
|
||||
setEditorCanvasEl: (el: HTMLCanvasElement | null) => void;
|
||||
@@ -305,6 +330,15 @@ export const usePageSceneStore = create<PageSceneState>((set, get) => ({
|
||||
is3DEditorInitialized: false,
|
||||
is3DSceneLoaded: false,
|
||||
|
||||
sceneMeta: {
|
||||
title: undefined,
|
||||
token: undefined,
|
||||
ownerToken: undefined,
|
||||
isModified: undefined,
|
||||
isInitializing: true,
|
||||
},
|
||||
currentUserToken: undefined,
|
||||
|
||||
sceneContainerEl: null,
|
||||
editorCanvasEl: null,
|
||||
camViewCanvasEl: null,
|
||||
@@ -448,6 +482,10 @@ export const usePageSceneStore = create<PageSceneState>((set, get) => ({
|
||||
set({ is3DEditorInitialized: initialized }),
|
||||
setIs3DSceneLoaded: (loaded) => set({ is3DSceneLoaded: loaded }),
|
||||
|
||||
setSceneMeta: (meta) =>
|
||||
set((s) => ({ sceneMeta: { ...s.sceneMeta, ...meta } })),
|
||||
setCurrentUserToken: (token) => set({ currentUserToken: token }),
|
||||
|
||||
setSceneContainerEl: (el) => set({ sceneContainerEl: el }),
|
||||
setEditorCanvasEl: (el) => set({ editorCanvasEl: el }),
|
||||
setCamViewCanvasEl: (el) => set({ camViewCanvasEl: el }),
|
||||
|
||||
+73
-111
@@ -1,40 +1,47 @@
|
||||
import { useContext, useState, useCallback } from "react";
|
||||
import { useParams, useLocation, useNavigate } from "react-router-dom";
|
||||
import { useSignals, useSignalEffect } from "@preact/signals-react/runtime";
|
||||
import React, { useCallback, useContext, useState } from "react";
|
||||
import {
|
||||
faCheckSquare,
|
||||
faFile,
|
||||
faKeyboard,
|
||||
faSquare,
|
||||
} from "@fortawesome/pro-solid-svg-icons";
|
||||
import { ToastTypes } from "~/enums";
|
||||
import { scene, signalScene, authentication, addToast } from "~/signals";
|
||||
import { ButtonDropdown } from "@storyteller/ui-button-dropdown";
|
||||
import { Input } from "@storyteller/ui-input";
|
||||
import { Button } from "@storyteller/ui-button";
|
||||
import { LoadUserScenes } from "./LoadUserScenes";
|
||||
import { getCurrentLocationWithoutParams, isNumberString } from "~/utilities";
|
||||
import {
|
||||
CameraAspectRatio,
|
||||
EngineContext,
|
||||
getSceneGenerationMetaData,
|
||||
usePageSceneStore,
|
||||
} from "@storyteller/ui-pagescene";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { Help } from "./Help";
|
||||
import { Modal } from "@storyteller/ui-modal";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { EngineContext } from "../../contexts/EngineContext/EngineContext";
|
||||
import { usePageSceneStore } from "../../PageSceneStore";
|
||||
import { CameraAspectRatio, ToastTypes } from "../../enums";
|
||||
import { getSceneGenerationMetaData } from "../../sceneMetadata";
|
||||
import { LoadUserScenes } from "./LoadUserScenes";
|
||||
import { Help } from "./Help/Help";
|
||||
|
||||
const isNumberString = (s: string): boolean => /^\d+$/.test(s);
|
||||
|
||||
export const ControlsTopButtons = () => {
|
||||
useSignals();
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const editor = useContext(EngineContext);
|
||||
const [shortcutsIsShowing, setShortcutsIsShowing] = useState(false);
|
||||
const editorEngine = useContext(EngineContext);
|
||||
|
||||
const [sceneTitleInput, setSceneTitleInput] = useState<string>("");
|
||||
const sceneMeta = usePageSceneStore((s) => s.sceneMeta);
|
||||
const currentUserToken = usePageSceneStore((s) => s.currentUserToken);
|
||||
const outlinerShowing = usePageSceneStore((s) => s.outlinerShowing);
|
||||
|
||||
const [sceneTitleInput, setSceneTitleInput] = useState<string>(
|
||||
sceneMeta.title || "",
|
||||
);
|
||||
const [sceneTokenSelected, setSceneTokenSelected] = useState<string>("");
|
||||
|
||||
// Keep the scene-title input in sync with the loaded scene's title.
|
||||
// The host wrapper (PageScene.tsx) drives the URL ↔ token bookkeeping;
|
||||
// here we only mirror the title for the Save-as-copy dialog.
|
||||
React.useEffect(() => {
|
||||
if (!sceneMeta.isInitializing && sceneMeta.title !== undefined) {
|
||||
setSceneTitleInput(sceneMeta.title);
|
||||
}
|
||||
}, [sceneMeta.isInitializing, sceneMeta.title]);
|
||||
|
||||
const handleChangeSceneTitleInput = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
) => {
|
||||
@@ -42,65 +49,67 @@ export const ControlsTopButtons = () => {
|
||||
};
|
||||
|
||||
const handleResetScene = () => {
|
||||
editorEngine?.cameraController.changeRenderCameraAspectRatio(
|
||||
editor?.cameraController.changeRenderCameraAspectRatio(
|
||||
CameraAspectRatio.HORIZONTAL_3_2,
|
||||
);
|
||||
};
|
||||
|
||||
const handleButtonSave = async () => {
|
||||
if (!editorEngine) {
|
||||
addToast(ToastTypes.ERROR, "No Engine Error in Saving Scenes");
|
||||
if (!editor) {
|
||||
// No-op without an editor; the toast is informational only.
|
||||
return;
|
||||
}
|
||||
const sceneGenerationMetadata = getSceneGenerationMetaData(editorEngine);
|
||||
const sceneGenerationMetadata = getSceneGenerationMetaData(editor);
|
||||
|
||||
const retSceneMediaToken = await editorEngine.saveScene({
|
||||
sceneTitle: scene.value.title || "",
|
||||
sceneToken: scene.value.token,
|
||||
const retSceneMediaToken = await editor.saveScene({
|
||||
sceneTitle: sceneMeta.title || "",
|
||||
sceneToken: sceneMeta.token,
|
||||
sceneGenerationMetadata,
|
||||
});
|
||||
|
||||
if (retSceneMediaToken === "") {
|
||||
addToast(ToastTypes.ERROR, "Failed to Save Scene Try again Later!");
|
||||
editor.adapter.showToast(
|
||||
ToastTypes.ERROR,
|
||||
"Failed to Save Scene Try again Later!",
|
||||
);
|
||||
}
|
||||
|
||||
if (retSceneMediaToken) {
|
||||
addToast(ToastTypes.SUCCESS, retSceneMediaToken);
|
||||
if (!scene.value.token) {
|
||||
signalScene({
|
||||
...scene.value,
|
||||
editor.adapter.showToast(ToastTypes.SUCCESS, retSceneMediaToken);
|
||||
if (!sceneMeta.token) {
|
||||
editor.adapter.onSceneTitleChange?.({
|
||||
title: sceneMeta.title || "",
|
||||
token: retSceneMediaToken,
|
||||
ownerToken: sceneMeta.ownerToken,
|
||||
isModified: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleButtonSaveAsCopy = useCallback(async () => {
|
||||
if (!editorEngine) {
|
||||
addToast(ToastTypes.ERROR, "No Engine Error in Saving Scenes");
|
||||
return;
|
||||
}
|
||||
const sceneGenerationMetadata = getSceneGenerationMetaData(editorEngine);
|
||||
const retSceneMediaToken = await editorEngine.saveScene({
|
||||
if (!editor) return;
|
||||
const sceneGenerationMetadata = getSceneGenerationMetaData(editor);
|
||||
const retSceneMediaToken = await editor.saveScene({
|
||||
sceneTitle: sceneTitleInput,
|
||||
sceneToken: undefined,
|
||||
sceneGenerationMetadata,
|
||||
});
|
||||
if (retSceneMediaToken) {
|
||||
addToast(ToastTypes.SUCCESS, retSceneMediaToken);
|
||||
signalScene({
|
||||
...scene.value,
|
||||
token: retSceneMediaToken,
|
||||
ownerToken: authentication.userInfo.value?.user_token,
|
||||
editor.adapter.showToast(ToastTypes.SUCCESS, retSceneMediaToken);
|
||||
editor.adapter.onSceneTitleChange?.({
|
||||
title: sceneTitleInput,
|
||||
token: retSceneMediaToken,
|
||||
ownerToken: currentUserToken,
|
||||
isModified: false,
|
||||
});
|
||||
}
|
||||
}, [sceneTitleInput, editorEngine, getSceneGenerationMetaData]);
|
||||
}, [sceneTitleInput, editor, currentUserToken]);
|
||||
|
||||
const handleButtonLoadScene = () => {
|
||||
handleResetScene();
|
||||
editorEngine?.loadScene(sceneTokenSelected).catch((err) => {
|
||||
addToast(ToastTypes.ERROR, err.message);
|
||||
editor?.loadScene(sceneTokenSelected).catch((err) => {
|
||||
editor.adapter.showToast(ToastTypes.ERROR, err.message);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -108,40 +117,15 @@ export const ControlsTopButtons = () => {
|
||||
setSceneTokenSelected(token);
|
||||
};
|
||||
|
||||
useSignalEffect(() => {
|
||||
//TODO: USE SIGNAL EFFECT SHOULD BE PERFORM ELSEWHERE
|
||||
// WHERE THIS OPERATION IS EXPECTED
|
||||
if (!scene.value.isInitializing) {
|
||||
setSceneTitleInput(scene.value.title || "");
|
||||
const currentLocation = getCurrentLocationWithoutParams(
|
||||
location.pathname,
|
||||
params,
|
||||
);
|
||||
if (scene.value.token === undefined) {
|
||||
if (params.sceneToken) {
|
||||
//case of create new scene from existing scene
|
||||
history.pushState({}, "", currentLocation);
|
||||
}
|
||||
//case of create new scene from unsaved scene
|
||||
navigate(currentLocation, { replace: true });
|
||||
} else if (scene.value.token) {
|
||||
if (params.sceneToken && scene.value.token !== params.sceneToken) {
|
||||
//case of loading existing scene from existing scene
|
||||
history.pushState({}, "", currentLocation + scene.value.token);
|
||||
}
|
||||
//case of loading existing scene from unsaved new scene
|
||||
//or case of updating existing scene
|
||||
navigate(currentLocation + scene.value.token, { replace: true });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const outlinerShowing = usePageSceneStore((s) => s.outlinerShowing);
|
||||
|
||||
const handleShowOutliner = () => {
|
||||
usePageSceneStore.getState().setOutlinerShowing(!outlinerShowing);
|
||||
};
|
||||
|
||||
const canSave =
|
||||
sceneMeta.isModified &&
|
||||
(sceneMeta.ownerToken === undefined ||
|
||||
sceneMeta.ownerToken === currentUserToken);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 pl-2 pt-2">
|
||||
<div className="flex gap-1.5">
|
||||
@@ -157,24 +141,20 @@ export const ControlsTopButtons = () => {
|
||||
title: "Create New Scene",
|
||||
content: (
|
||||
<h4>
|
||||
Make sure you've saved your scene. Unsaved changes will
|
||||
be lost. Continue?
|
||||
Make sure you've saved your scene. Unsaved changes
|
||||
will be lost. Continue?
|
||||
</h4>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Create new scene",
|
||||
onClick: async () => {
|
||||
handleResetScene();
|
||||
|
||||
const defaultTitle = "Untitled New Scene";
|
||||
setSceneTitleInput(defaultTitle);
|
||||
|
||||
await editorEngine?.newScene(defaultTitle);
|
||||
await editor?.newScene(defaultTitle);
|
||||
},
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
closeButtonProps: { label: "Cancel" },
|
||||
showClose: true,
|
||||
},
|
||||
},
|
||||
@@ -191,51 +171,37 @@ export const ControlsTopButtons = () => {
|
||||
disabled: sceneTokenSelected === "",
|
||||
onClick: handleButtonLoadScene,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
closeButtonProps: { label: "Cancel" },
|
||||
showClose: true,
|
||||
className: "max-w-5xl",
|
||||
},
|
||||
},
|
||||
{
|
||||
disabled: !(
|
||||
scene.value.isModified &&
|
||||
(scene.value.ownerToken === undefined ||
|
||||
scene.value.ownerToken ===
|
||||
authentication.userInfo.value?.user_token)
|
||||
),
|
||||
// save scene should be disabled if there are no changes
|
||||
disabled: !canSave,
|
||||
label: "Save scene",
|
||||
description: "Ctrl+S",
|
||||
...(scene.value.token
|
||||
? {
|
||||
// Existing scene → save directly without dialog
|
||||
onClick: handleButtonSave,
|
||||
}
|
||||
...(sceneMeta.token
|
||||
? { onClick: handleButtonSave }
|
||||
: {
|
||||
// New (unsaved) scene → ask for confirmation first
|
||||
dialogProps: {
|
||||
title: "Save Scene",
|
||||
content: (
|
||||
<h4>
|
||||
Save scene to <b>{scene.value.title}</b>?
|
||||
Save scene to <b>{sceneMeta.title}</b>?
|
||||
</h4>
|
||||
),
|
||||
confirmButtonProps: {
|
||||
label: "Save",
|
||||
onClick: handleButtonSave,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
closeButtonProps: { label: "Cancel" },
|
||||
showClose: true,
|
||||
},
|
||||
}),
|
||||
divider: true,
|
||||
},
|
||||
{
|
||||
disabled: !scene.value.isModified || !scene.value.token,
|
||||
disabled: !sceneMeta.isModified || !sceneMeta.token,
|
||||
label: "Save scene as copy",
|
||||
description: "Ctrl+Shift+S",
|
||||
onDialogOpen: () => {
|
||||
@@ -266,9 +232,7 @@ export const ControlsTopButtons = () => {
|
||||
disabled: sceneTitleInput === "",
|
||||
onClick: handleButtonSaveAsCopy,
|
||||
},
|
||||
closeButtonProps: {
|
||||
label: "Cancel",
|
||||
},
|
||||
closeButtonProps: { label: "Cancel" },
|
||||
showClose: true,
|
||||
},
|
||||
},
|
||||
@@ -305,8 +269,6 @@ export const ControlsTopButtons = () => {
|
||||
>
|
||||
<Help />
|
||||
</Modal>
|
||||
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+23
-42
@@ -1,73 +1,57 @@
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import dayjs from "dayjs";
|
||||
import type { MediaInfo } from "@storyteller/ui-pagescene";
|
||||
import { FilterEngineCategories, ToastTypes } from "~/enums";
|
||||
import { addToast } from "~/signals";
|
||||
import { ScenePicker, SceneTypes } from "../ScenePicker";
|
||||
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
|
||||
import { Label } from "@storyteller/ui-label";
|
||||
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
|
||||
import { EngineContext } from "../../../contexts/EngineContext/EngineContext";
|
||||
import { FilterEngineCategories, ToastTypes } from "../../../enums";
|
||||
import type { MediaInfo } from "../../../models";
|
||||
import { ScenePicker, SceneTypes } from "../ScenePicker";
|
||||
|
||||
interface LoadSceneProps {
|
||||
onSceneSelect: (token: string) => void;
|
||||
}
|
||||
|
||||
export enum FetchStatus {
|
||||
paused,
|
||||
// ready triggers a new fetch
|
||||
ready,
|
||||
in_progress,
|
||||
success,
|
||||
error,
|
||||
}
|
||||
|
||||
export enum Filters {
|
||||
Featured,
|
||||
Mine,
|
||||
Bookmarked,
|
||||
}
|
||||
|
||||
export const LoadUserScenes = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
const editor = useContext(EngineContext);
|
||||
const [scenes, setScenes] = useState<SceneTypes[] | undefined>(undefined);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);
|
||||
|
||||
const getScenesByUser = useCallback(async () => {
|
||||
const modMediaInfoToScenes = (results: MediaInfo[]) =>
|
||||
results.map((scene: MediaInfo) => ({
|
||||
token: scene.token,
|
||||
name: scene.maybe_title ?? "Untitled",
|
||||
updated_at: dayjs(scene.updated_at).format("MMM D, YYYY HH:mm:ss"),
|
||||
thumbnail: scene.cover_image.maybe_cover_image_public_bucket_path
|
||||
? scene.cover_image.maybe_cover_image_public_bucket_path
|
||||
: undefined,
|
||||
if (!editor) return;
|
||||
const modMediaInfoToScenes = (results: MediaInfo[]): SceneTypes[] =>
|
||||
results.map((s) => ({
|
||||
token: s.token,
|
||||
name: s.maybe_title ?? "Untitled",
|
||||
updated_at: dayjs(s.updated_at).format("MMM D, YYYY HH:mm:ss"),
|
||||
thumbnail: s.cover_image.maybe_cover_image_public_bucket_path
|
||||
? s.cover_image.maybe_cover_image_public_bucket_path
|
||||
: "",
|
||||
}));
|
||||
|
||||
const mediaFilesApi = new MediaFilesApi();
|
||||
const response = await mediaFilesApi.ListUserMediaFiles({
|
||||
filter_engine_categories: [FilterEngineCategories.SCENE],
|
||||
const response = await editor.adapter.listUserMediaFiles({
|
||||
pageSize: 100,
|
||||
filterEngineCategories: [FilterEngineCategories.SCENE],
|
||||
});
|
||||
if (response.success && response.data) {
|
||||
setScenes(modMediaInfoToScenes(response.data));
|
||||
return;
|
||||
}
|
||||
addToast(
|
||||
editor.adapter.showToast(
|
||||
ToastTypes.ERROR,
|
||||
response.errorMessage || "Unknown Error in Loading User Scenes",
|
||||
);
|
||||
}, []);
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (scenes) {
|
||||
//only call once on mount
|
||||
return;
|
||||
}
|
||||
if (scenes) return;
|
||||
getScenesByUser();
|
||||
}, [scenes, getScenesByUser]);
|
||||
|
||||
@@ -81,7 +65,6 @@ export const LoadUserScenes = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
const atBottom =
|
||||
element.scrollHeight - element.scrollTop <= element.clientHeight + 1;
|
||||
const hasOverflow = element.scrollHeight > element.clientHeight;
|
||||
|
||||
setBottomGradientOpacity(hasOverflow && !atBottom ? 1 : 0);
|
||||
}
|
||||
};
|
||||
@@ -92,12 +75,10 @@ export const LoadUserScenes = ({ onSceneSelect }: LoadSceneProps) => {
|
||||
if (element) {
|
||||
handleScroll();
|
||||
element.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
return () => element.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}, [scenes]);
|
||||
|
||||
return (
|
||||
File renamed without changes.
+27
-34
@@ -1,20 +1,17 @@
|
||||
import {
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import type { MediaInfo } from "@storyteller/ui-pagescene";
|
||||
import { FilterEngineCategories, ToastTypes } from "~/enums";
|
||||
|
||||
import { LoadingSpinner } from "@storyteller/ui-loading-spinner";
|
||||
import { EngineContext } from "../../../contexts/EngineContext/EngineContext";
|
||||
import { FilterEngineCategories, ToastTypes } from "../../../enums";
|
||||
import type { MediaInfo } from "../../../models";
|
||||
import { ScenePicker, SceneTypes } from "../ScenePicker";
|
||||
import { LoadingSpinner } from "~/components";
|
||||
|
||||
import { MediaFilesApi } from "~/Classes/ApiManager/MediaFilesApi";
|
||||
import { addToast } from "~/signals";
|
||||
|
||||
interface NewSceneFromTemplateProps {
|
||||
onSceneSelect: (token: string) => void;
|
||||
@@ -23,15 +20,15 @@ interface NewSceneFromTemplateProps {
|
||||
export const NewSceneFromTemplate = ({
|
||||
onSceneSelect,
|
||||
}: NewSceneFromTemplateProps) => {
|
||||
const [featuredScenes, setFeaturedScenes] = useState<
|
||||
SceneTypes[] | undefined
|
||||
>(undefined);
|
||||
const editor = useContext(EngineContext);
|
||||
const [featuredScenes, setFeaturedScenes] = useState<SceneTypes[] | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [bottomGradientOpacity, setBottomGradientOpacity] = useState(1);
|
||||
const scrollContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const listFeaturedScenes = useCallback(async () => {
|
||||
const mediaFilesApi = new MediaFilesApi();
|
||||
// Hard coded scenes
|
||||
if (!editor) return;
|
||||
|
||||
const dummyScenes: SceneTypes[] = [
|
||||
{
|
||||
@@ -40,17 +37,18 @@ export const NewSceneFromTemplate = ({
|
||||
thumbnail: "/resources/placeholders/scene_placeholder.png",
|
||||
},
|
||||
];
|
||||
const modMediaInfoToScenes = (results: MediaInfo[]) =>
|
||||
results.map((scene: MediaInfo) => ({
|
||||
token: scene.token,
|
||||
name: scene.maybe_title ?? "Untitled",
|
||||
updated_at: dayjs(scene.updated_at).format("MMM D, YYYY HH:mm:ss"),
|
||||
thumbnail: scene.cover_image.maybe_cover_image_public_bucket_path
|
||||
? scene.cover_image.maybe_cover_image_public_bucket_path
|
||||
: undefined,
|
||||
const modMediaInfoToScenes = (results: MediaInfo[]): SceneTypes[] =>
|
||||
results.map((s) => ({
|
||||
token: s.token,
|
||||
name: s.maybe_title ?? "Untitled",
|
||||
updated_at: dayjs(s.updated_at).format("MMM D, YYYY HH:mm:ss"),
|
||||
thumbnail: s.cover_image.maybe_cover_image_public_bucket_path
|
||||
? s.cover_image.maybe_cover_image_public_bucket_path
|
||||
: "",
|
||||
}));
|
||||
const response = await mediaFilesApi.ListFeaturedMediaFiles({
|
||||
filter_engine_categories: [FilterEngineCategories.SCENE],
|
||||
const response = await editor.adapter.listFeaturedMediaFiles({
|
||||
pageSize: 100,
|
||||
filterEngineCategories: [FilterEngineCategories.SCENE],
|
||||
});
|
||||
if (response.success && response.data) {
|
||||
setFeaturedScenes([
|
||||
@@ -60,16 +58,14 @@ export const NewSceneFromTemplate = ({
|
||||
return;
|
||||
}
|
||||
setFeaturedScenes(dummyScenes);
|
||||
addToast(
|
||||
editor.adapter.showToast(
|
||||
ToastTypes.ERROR,
|
||||
response.errorMessage || "Unknown Error in Listing Feature Scenes",
|
||||
);
|
||||
}, []);
|
||||
}, [editor]);
|
||||
|
||||
useEffect(() => {
|
||||
if (featuredScenes) {
|
||||
//only call once on mount
|
||||
return;
|
||||
}
|
||||
if (featuredScenes) return;
|
||||
listFeaturedScenes();
|
||||
}, [featuredScenes, listFeaturedScenes]);
|
||||
|
||||
@@ -83,7 +79,6 @@ export const NewSceneFromTemplate = ({
|
||||
const atBottom =
|
||||
element.scrollHeight - element.scrollTop <= element.clientHeight + 1;
|
||||
const hasOverflow = element.scrollHeight > element.clientHeight;
|
||||
|
||||
setBottomGradientOpacity(hasOverflow && !atBottom ? 1 : 0);
|
||||
}
|
||||
};
|
||||
@@ -93,11 +88,9 @@ export const NewSceneFromTemplate = ({
|
||||
if (element) {
|
||||
handleScroll();
|
||||
element.addEventListener("scroll", handleScroll);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
return () => element.removeEventListener("scroll", handleScroll);
|
||||
}
|
||||
return undefined;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
File renamed without changes.
+9
-9
@@ -1,6 +1,6 @@
|
||||
import React, { useRef, useState } from "react";
|
||||
import { BucketConfig } from "~/api/BucketConfig";
|
||||
import React, { useContext, useState } from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { EngineContext } from "../../../contexts/EngineContext/EngineContext";
|
||||
|
||||
export type SceneTypes = {
|
||||
token: string;
|
||||
@@ -22,17 +22,17 @@ export const SceneCard: React.FC<SceneCardProps> = ({
|
||||
selectedSceneId,
|
||||
showDate,
|
||||
}) => {
|
||||
// console.log(scene);
|
||||
const editor = useContext(EngineContext);
|
||||
const [loadError, setLoadError] = useState(false);
|
||||
|
||||
const handleSelected = (scene: SceneTypes) => {
|
||||
onSceneSelect(scene);
|
||||
const handleSelected = (s: SceneTypes) => {
|
||||
onSceneSelect(s);
|
||||
};
|
||||
|
||||
const bucketConfig = useRef(new BucketConfig());
|
||||
const imageThumbnailUrl =
|
||||
scene.thumbnail && bucketConfig.current.getCdnUrl(scene.thumbnail, 360, 20);
|
||||
// thumbnail will be replaced with 3d scene screenshots
|
||||
scene.thumbnail && editor
|
||||
? editor.adapter.getCdnUrl(scene.thumbnail, 360, 20)
|
||||
: undefined;
|
||||
const tempThumbnail = "/resources/placeholders/scene_placeholder.png";
|
||||
|
||||
return (
|
||||
@@ -58,7 +58,7 @@ export const SceneCard: React.FC<SceneCardProps> = ({
|
||||
/>
|
||||
<div className="absolute left-0 top-0 h-full w-full bg-gradient-to-t from-black/80 to-transparent" />
|
||||
<div className="absolute bottom-[8px] left-[10px] text-start text-sm drop-shadow-md">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
<span className="w-60 truncate text-sm font-medium">
|
||||
{scene.name}
|
||||
</span>
|
||||
+3
-8
@@ -1,12 +1,7 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { SceneCard } from "~/pages/PageScene/comps/ControlsTopButtons/ScenePicker/SceneCard";
|
||||
import { SceneCard, SceneTypes } from "./SceneCard";
|
||||
|
||||
export type SceneTypes = {
|
||||
token: string;
|
||||
name: string;
|
||||
updated_at?: string;
|
||||
thumbnail: string | undefined;
|
||||
};
|
||||
export type { SceneTypes };
|
||||
|
||||
interface ScenePickerProps {
|
||||
scenes: SceneTypes[];
|
||||
@@ -24,7 +19,7 @@ export const ScenePicker: React.FC<ScenePickerProps> = ({
|
||||
useEffect(() => {
|
||||
if (scenes.length > 0 && !selectedSceneId) {
|
||||
setSelectedSceneId(scenes[0].token);
|
||||
onSceneSelect(scenes[0]); // Trigger the callback with the first scene
|
||||
onSceneSelect(scenes[0]);
|
||||
}
|
||||
}, [scenes, onSceneSelect, selectedSceneId]);
|
||||
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export { ScenePicker } from "./ScenePicker";
|
||||
export type { SceneTypes } from "./SceneCard";
|
||||
@@ -0,0 +1 @@
|
||||
export { ControlsTopButtons } from "./ControlsTopButtons";
|
||||
@@ -5,6 +5,9 @@
|
||||
{
|
||||
"path": "../button-icon"
|
||||
},
|
||||
{
|
||||
"path": "../loading-spinner"
|
||||
},
|
||||
{
|
||||
"path": "../label"
|
||||
},
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
{
|
||||
"path": "../button-icon/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../loading-spinner/tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "../label/tsconfig.lib.json"
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user