mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-18 07:35:19 +00:00
Storyteller.ai API integration (#49)
This PR integrates asset retrieval from the Storyteller.ai back-end API, and begins implementing long-term scene saving and storage. ### Media retrieval Instead of fetching raw asset URLs, going forward, media files from remote sources (e.g., in URL parameters) should be specified with a path like the following: ``` remote://<token>.<filetype> ``` where * `token` is a media token (e.g., "m_70b77neq9jsgmy5v31mekzchcbd223") * `filetype` is a file extension appropriate for the type of file being fetched (e.g., "gltf") The `remote` protocol will be recognized by a [custom asset reader](https://github.com/storytold/storyteller-bevy/blob/70726db111f6ccafd47feb055c5546540fef1786/studio/src/remote/reader.rs) which will retrieve the file by calling a Storyteller API endpoint. ### Scene storage Additionally, this PR begins laying the groundwork for storing/retrieving Bevy scenes to/from the backend instead of solely in-memory. This work is still incomplete -- as of this PR, a button has been added to the web UI allowing the user to download the scene file locally for testing/debugging purposes. ### Test URL Media retrieval from the backend can be tested by [visiting this URL](https://deploy-preview-49--storyteller-studio.netlify.app/?mode=viewer&mixamo=remote://m_70b77neq9jsgmy5v31mekzchcbd223.gltf) in Chrome with the `--disable-web-security` flag (to bypass CORS errors)
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
type EntityMultiSpawnEvent,
|
||||
type EntitySelectEvent,
|
||||
type EntitySpawnEvent,
|
||||
type SceneSavedEvent,
|
||||
type SceneStateEvent,
|
||||
TransformSpace,
|
||||
TransformType,
|
||||
@@ -150,6 +151,22 @@ export class AppElement extends LitElement {
|
||||
this.#updateHierarchy();
|
||||
}
|
||||
|
||||
@on("window:scene-saved")
|
||||
onSceneSaved({ detail: scene }: SceneSavedEvent): void {
|
||||
const fileBlob = new Blob([scene], { type: "text/ron" });
|
||||
const url = URL.createObjectURL(fileBlob);
|
||||
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = "scene.scn.ron";
|
||||
anchor.click();
|
||||
|
||||
setTimeout(() => {
|
||||
URL.revokeObjectURL(url);
|
||||
anchor.remove();
|
||||
});
|
||||
}
|
||||
|
||||
toggleHelp(): void {
|
||||
this.showHelp = !this.showHelp;
|
||||
}
|
||||
@@ -211,6 +228,7 @@ export class AppElement extends LitElement {
|
||||
class="app-root"
|
||||
${ref(this.#canvasProvider as any)}
|
||||
>
|
||||
<!-- Scene view -->
|
||||
${this.bvhPath ? html`
|
||||
<sts-bvh-viewer
|
||||
bvhPath=${this.bvhPath}
|
||||
@@ -232,11 +250,20 @@ export class AppElement extends LitElement {
|
||||
></sts-viewer>
|
||||
`}
|
||||
|
||||
<!-- Actions toolbar -->
|
||||
<sts-toolbar class="v-start h-start">
|
||||
<sts-toolbar-group>
|
||||
${this.mode === StudioMode.Editor ? html`
|
||||
<sts-button
|
||||
icon="floppy-disk"
|
||||
@click=${() => studio.saveScene()}
|
||||
>
|
||||
Save
|
||||
</sts-button>
|
||||
` : nothing}
|
||||
<sts-button
|
||||
class=${classMap({ active: this.showHelp })}
|
||||
icon="circle-info"
|
||||
icon="question"
|
||||
@click=${this.toggleHelp}
|
||||
>
|
||||
Help
|
||||
@@ -245,6 +272,7 @@ export class AppElement extends LitElement {
|
||||
</sts-toolbar>
|
||||
|
||||
${this.mode === StudioMode.Editor ? html`
|
||||
<!-- Transform toolbar -->
|
||||
<sts-transform-toolbar
|
||||
class="v-start h-center"
|
||||
.type=${this.transformType}
|
||||
@@ -253,6 +281,7 @@ export class AppElement extends LitElement {
|
||||
@space-change=${this.onTransformSpaceChange}
|
||||
></sts-transform-toolbar>
|
||||
|
||||
<!-- Hierarchy and inspector -->
|
||||
<div class="hierarchy v-start h-end">
|
||||
${this.sceneObjects.map(obj => html`
|
||||
<sts-tree class="scene-object"
|
||||
@@ -264,10 +293,12 @@ export class AppElement extends LitElement {
|
||||
</div>
|
||||
` : nothing}
|
||||
|
||||
<!-- Animation timeline -->
|
||||
${this.mode === StudioMode.Editor ? html`
|
||||
<sts-anim-editor class="timeline v-end h-start h-before-hierarchy"></sts-anim-editor>
|
||||
` : nothing}
|
||||
|
||||
<!-- Dialogs and modals -->
|
||||
${this.showHelp ? html`
|
||||
<sts-help class="help v-after-toolbar h-start"></sts-help>
|
||||
` : nothing}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
:host {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 10px 20px;
|
||||
gap: 0.75rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
|
||||
font: {
|
||||
@@ -10,14 +10,14 @@
|
||||
size: 15px;
|
||||
weight: 500;
|
||||
}
|
||||
line-height: 1;
|
||||
line-height: 1.5;
|
||||
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
|
||||
background: #0006;
|
||||
color: #FFF;
|
||||
color: #FFFC;
|
||||
backdrop-filter: blur(16px);
|
||||
transition: {
|
||||
property: background, color;
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
:host(:hover) {
|
||||
background: #0008;
|
||||
color: #FFFF;
|
||||
}
|
||||
|
||||
.icon {
|
||||
|
||||
@@ -6,19 +6,21 @@ import { unsafeSVG } from "lit/directives/unsafe-svg.js";
|
||||
|
||||
import farCrosshairs from "@fortawesome/fontawesome-pro/svgs/regular/crosshairs.svg?raw";
|
||||
import farVideo from "@fortawesome/fontawesome-pro/svgs/regular/video.svg?raw";
|
||||
import fasCircleInfo from "@fortawesome/fontawesome-pro/svgs/regular/circle-info.svg?raw";
|
||||
import fasCircleQuestion from "@fortawesome/fontawesome-pro/svgs/solid/circle-question.svg?raw";
|
||||
import fasKey from "@fortawesome/fontawesome-pro/svgs/solid/key.svg?raw";
|
||||
import fasCube from "@fortawesome/fontawesome-pro/svgs/regular/cube.svg?raw";
|
||||
import fasGlobe from "@fortawesome/fontawesome-pro/svgs/regular/globe.svg?raw";
|
||||
import fasUpDownLeftRight from "@fortawesome/fontawesome-pro/svgs/regular/arrows-up-down-left-right.svg?raw";
|
||||
import farCube from "@fortawesome/fontawesome-pro/svgs/regular/cube.svg?raw";
|
||||
import farGlobe from "@fortawesome/fontawesome-pro/svgs/regular/globe.svg?raw";
|
||||
import farUpDownLeftRight from "@fortawesome/fontawesome-pro/svgs/regular/arrows-up-down-left-right.svg?raw";
|
||||
import fasPlay from "@fortawesome/fontawesome-pro/svgs/solid/play.svg?raw";
|
||||
import fasFwdStep from "@fortawesome/fontawesome-pro/svgs/solid/forward-step.svg?raw";
|
||||
import fasFwdFast from "@fortawesome/fontawesome-pro/svgs/solid/forward-fast.svg?raw";
|
||||
import fasBwdStep from "@fortawesome/fontawesome-pro/svgs/solid/backward-step.svg?raw";
|
||||
import fasBwdFast from "@fortawesome/fontawesome-pro/svgs/solid/backward-fast.svg?raw";
|
||||
import fasRotate from "@fortawesome/fontawesome-pro/svgs/regular/arrows-rotate.svg?raw";
|
||||
import fasScale from "@fortawesome/fontawesome-pro/svgs/regular/arrow-up-right-and-arrow-down-left-from-center.svg?raw";
|
||||
import fasQuestion from "@fortawesome/fontawesome-pro/svgs/solid/question.svg?raw";
|
||||
import farRotate from "@fortawesome/fontawesome-pro/svgs/regular/arrows-rotate.svg?raw";
|
||||
import farScale from "@fortawesome/fontawesome-pro/svgs/regular/arrow-up-right-and-arrow-down-left-from-center.svg?raw";
|
||||
import fasCaretRight from "@fortawesome/fontawesome-pro/svgs/solid/caret-right.svg?raw";
|
||||
import fasFloppyDisk from "@fortawesome/fontawesome-pro/svgs/solid/floppy-disk.svg?raw";
|
||||
|
||||
import styles from "./icon.element.scss?inline";
|
||||
|
||||
@@ -47,18 +49,20 @@ function renderSvg(iconId: string) {
|
||||
const svgSrc = match (iconId, {
|
||||
"bwd-fast": () => fasBwdFast,
|
||||
"bwd-step": () => fasBwdStep,
|
||||
"circle-info": () => fasCircleInfo,
|
||||
"circle-question": () => fasCircleQuestion,
|
||||
"caret-right": () => fasCaretRight,
|
||||
"crosshairs": () => farCrosshairs,
|
||||
"cube": () => fasCube,
|
||||
"cube": () => farCube,
|
||||
"floppy-disk": () => fasFloppyDisk,
|
||||
"fwd-fast": () => fasFwdFast,
|
||||
"fwd-step": () => fasFwdStep,
|
||||
"key": () => fasKey,
|
||||
"globe": () => fasGlobe,
|
||||
"globe": () => farGlobe,
|
||||
"play": () => fasPlay,
|
||||
"rotate": () => fasRotate,
|
||||
"scale": () => fasScale,
|
||||
"up-down-left-right": () => fasUpDownLeftRight,
|
||||
"question": () => fasQuestion,
|
||||
"rotate": () => farRotate,
|
||||
"scale": () => farScale,
|
||||
"up-down-left-right": () => farUpDownLeftRight,
|
||||
"video": () => farVideo,
|
||||
_: () => null,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user