Files
storyteller-bevy/react-frontend/src/app/app.tsx
T
Danny McGee 0a0acacfdb Single-canvas juggling (#15)
This PR adds a `CanvasProviderElement` to `@storyteller/studio-web` to manage
the complexity of maintaining a single concrete `HTMLCanvasElement` for
`@storyteller/studio`, while still being able to treat components like the
`ViewerElement` as self-contained "views" that behave as expected with regards
to CSS styling, DOM placement, etc.

Consuming applications should wrap the new `<sts-canvas-provider />` element
around some common ancestor of all views into the `@storyteller/studio` app,
similar to a React context provider.

The `react-frontend` application has been updated to take advantage of the new
`CanvasProviderElement` by demonstrating a more realistic use case for the
`ViewerElement`. The new demo presents a grid of "thumbnails," one for each
object in our hard-coded "object library." Clicking on one of these thumbnails
will render a `ViewerElement` for that object in a modal dialog.
2023-12-19 16:11:25 -05:00

169 lines
3.1 KiB
TypeScript

import { type SceneStateEvent, SceneState } from "@storyteller/studio";
import { FormEvent, useState } from "react";
import StudioViewer from "./studio-viewer";
import StudioCanvasProvider from "./studio-canvas-provider";
import style from "./app.module.scss";
const OBJECTS = [
{
id: "bookcase",
label: "Bookcase",
},
{
id: "couch",
label: "Couch",
},
{
id: "desk-chair",
label: "Desk Chair",
},
{
id: "desk",
label: "Desk",
},
{
id: "file-cabinet",
label: "File Cabinet",
},
{
id: "potted-plant-1",
label: "Potted Plant #1",
},
{
id: "potted-plant-3",
label: "Potted Plant #2",
},
{
id: "room",
label: "Room (empty)",
},
{
id: "sample-room",
label: "Room (populated)",
},
{
id: "rug",
label: "Rug",
},
{
id: "wicker-basket",
label: "Wicker Basket",
},
];
const SKYBOXES = [
{
id: "gum_trees_4k",
label: "Gum Trees",
},
{
id: "kloofendal_28d_misty_4k",
label: "Kloofendal Misty",
},
{
id: "meadow_4k",
label: "Meadow",
},
{
id: "promenade_de_vidy_4k",
label: "Promenade de Vidy",
},
{
id: "scythian_tombs_4k",
label: "Scythian Tombs",
},
{
id: "test_scene",
label: "Desert Sunrise",
},
];
export default () => {
const [objectId, setObjectId] = useState<string|null>(null);
const [skyboxId, setSkyboxId] = useState("meadow_4k");
const [modalOpen, setModalOpen] = useState(false);
const [loading, setLoading] = useState(true);
const onSkyboxSelect = (event: FormEvent<HTMLSelectElement>) => {
setSkyboxId((event.target as HTMLSelectElement).value)
}
const onSceneStateChange = ({ detail: state }: SceneStateEvent) => {
setLoading(state !== SceneState.Active);
}
return (
<StudioCanvasProvider>
<div className={style.root}>
{OBJECTS.map(({ id, label }) => (
<div
className={style.thumbnail}
key={id}
onClick={() => {
if (id !== objectId) {
setLoading(true);
setObjectId(id);
}
setModalOpen(true);
}}
>
<h3>{label}</h3>
</div>
))}
</div>
{modalOpen && !!objectId && <>
<div
className={style.modalOverlay}
onClick={() => setModalOpen(false)}
/>
<div className={style.modal}>
<StudioViewer
objectId={objectId}
skyboxId={skyboxId}
onSceneStateChange={onSceneStateChange}
/>
<div
className={style.loading}
style={{
opacity: loading ? 1 : 0,
}}
>
<h1>Loading...</h1>
</div>
<div className={style.controls}>
<label className={style.control}>
<span className={style.label}>Skybox</span>
<select
className={style.input}
name="skybox"
value={skyboxId}
disabled={loading}
onChange={onSkyboxSelect}
>
{SKYBOXES.map(({ id, label }) => (
<option
key={id}
value={id}
>
{label}
</option>
))}
</select>
</label>
</div>
<button className={style.modalClose}
onClick={() => setModalOpen(false)}
>
&times;
</button>
</div>
</>}
</StudioCanvasProvider>
);
}