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:
Danny McGee
2024-02-23 21:05:32 -05:00
committed by GitHub
parent fffcb38ec6
commit 6ca2a992e5
10 changed files with 770 additions and 27 deletions
Generated
+393 -6
View File
@@ -2063,7 +2063,7 @@ dependencies = [
"openssl-probe",
"openssl-sys",
"schannel",
"socket2",
"socket2 0.4.9",
"winapi",
]
@@ -2277,6 +2277,16 @@ dependencies = [
"serde",
]
[[package]]
name = "errno"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "error-code"
version = "2.3.1"
@@ -2783,6 +2793,25 @@ dependencies = [
"svg_fmt",
]
[[package]]
name = "h2"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap 2.0.0",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "half"
version = "2.3.1"
@@ -2867,6 +2896,66 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.4.9",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "idna"
version = "0.4.0"
@@ -2955,6 +3044,12 @@ dependencies = [
"mach2",
]
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]]
name = "isahc"
version = "1.7.2"
@@ -3219,6 +3314,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
[[package]]
name = "lock_api"
version = "0.4.10"
@@ -3420,6 +3521,24 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "ndk"
version = "0.7.0"
@@ -3731,6 +3850,32 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "openssl"
version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
"bitflags 2.4.0",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.37",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
@@ -3739,9 +3884,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.99"
version = "0.9.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22e1bf214306098e4832460f797824c05d25aacdf896f64a985fb0fd992454ae"
checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
dependencies = [
"cc",
"libc",
@@ -3755,7 +3900,7 @@ version = "0.3.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8378ac0dfbd4e7895f2d2c1f1345cab3836910baf3a300b000d04250f0c8428f"
dependencies = [
"redox_syscall",
"redox_syscall 0.3.5",
]
[[package]]
@@ -3797,7 +3942,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"redox_syscall 0.3.5",
"smallvec",
"windows-targets 0.48.5",
]
@@ -4097,6 +4242,15 @@ dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "regex"
version = "1.9.5"
@@ -4147,6 +4301,48 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "216080ab382b992234dda86873c18d4c48358f5cfcb70fd693d7f6f2131b628b"
[[package]]
name = "reqwest"
version = "0.11.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251"
dependencies = [
"base64 0.21.4",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tokio-util",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"wasm-streams",
"web-sys",
"winreg",
]
[[package]]
name = "robust"
version = "1.1.0"
@@ -4196,6 +4392,28 @@ dependencies = [
"semver",
]
[[package]]
name = "rustix"
version = "0.38.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3"
dependencies = [
"bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64 0.21.4",
]
[[package]]
name = "ruzstd"
version = "0.4.0"
@@ -4246,6 +4464,29 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys 0.8.4",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys 0.8.4",
"libc",
]
[[package]]
name = "semver"
version = "1.0.21"
@@ -4294,6 +4535,18 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "sharded-slab"
version = "0.1.4"
@@ -4385,6 +4638,16 @@ dependencies = [
"winapi",
]
[[package]]
name = "socket2"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4031e820eb552adee9295814c0ced9e5cf38ddf1e8b7d566d6de8e2538ea989e"
dependencies = [
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "space_bevy_xpbd_plugin"
version = "0.3.1"
@@ -4553,6 +4816,7 @@ dependencies = [
"itertools 0.12.1",
"js-sys",
"parking_lot",
"reqwest",
"serde",
"serde-wasm-bindgen",
"serde_json",
@@ -4601,6 +4865,12 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "sysinfo"
version = "0.29.10"
@@ -4615,6 +4885,27 @@ dependencies = [
"winapi",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys 0.8.4",
"libc",
]
[[package]]
name = "taffy"
version = "0.3.13"
@@ -4633,6 +4924,19 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "tempfile"
version = "3.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5"
dependencies = [
"cfg-if",
"fastrand 2.0.1",
"redox_syscall 0.4.1",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "termcolor"
version = "1.3.0"
@@ -4718,6 +5022,45 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f38200e3ef7995e5ef13baec2f432a6da0aa9ac495b2c0e8f3b7eec2c92d653"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"pin-project-lite",
"socket2 0.5.4",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-util"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
name = "toml_datetime"
version = "0.6.5"
@@ -4746,6 +5089,12 @@ dependencies = [
"winnow",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.37"
@@ -4830,6 +5179,12 @@ dependencies = [
"wasm-bindgen",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]]
name = "ttf-parser"
version = "0.19.2"
@@ -4958,6 +5313,15 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
@@ -5030,6 +5394,19 @@ version = "0.2.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0d046c5d029ba91a1ed14da14dca44b68bf2f124cfbaf741c54151fdb3e0750b"
[[package]]
name = "wasm-streams"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129"
dependencies = [
"futures-util",
"js-sys",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
]
[[package]]
name = "wayland-scanner"
version = "0.29.5"
@@ -5519,7 +5896,7 @@ dependencies = [
"orbclient",
"percent-encoding",
"raw-window-handle",
"redox_syscall",
"redox_syscall 0.3.5",
"wasm-bindgen",
"wayland-scanner",
"web-sys",
@@ -5536,6 +5913,16 @@ dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]]
name = "wyz"
version = "0.2.0"
+32 -1
View File
@@ -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}
+5 -4
View File
@@ -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 {
+16 -12
View File
@@ -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,
});
+10 -4
View File
@@ -27,9 +27,9 @@ console_error_panic_hook = { version = "0.1.7", optional = true }
itertools = "0.12.1"
js-sys = { version = "0.3.65", optional = true }
parking_lot = "0.12.1"
serde = { version = "1.0.192", optional = true }
serde = "1.0.192"
serde-wasm-bindgen = { version = "0.6.1", optional = true }
serde_json = { version = "1.0.108", optional = true }
serde_json = "1.0.108"
studio-bvh = { path = "../studio-bvh" }
wasm-bindgen = "0.2.84"
web-sys = { version = "0.3.65", optional = true, features = [
@@ -49,12 +49,18 @@ bytemuck = "1.14.1"
space_editor = {git = "https://github.com/rewin123/space_editor", rev = "ce83f86dc902787ab16213f3209276a784654df6"}
anyhow.workspace = true
[dependencies.reqwest]
version = "0.11.24"
features = [
"default",
"json",
"stream",
]
[features]
wasm = [
"dep:console_error_panic_hook",
"dep:js-sys",
"dep:serde",
"dep:serde_json",
"dep:serde-wasm-bindgen",
"dep:web-sys",
"bevy_rapier3d/wasm-bindgen",
+3
View File
@@ -28,6 +28,7 @@ mod interaction;
pub mod math;
mod mixamo;
mod persistance;
mod remote;
mod scene;
pub mod screenshot;
mod skybox;
@@ -46,6 +47,7 @@ pub use crate::{
math::InterpolationPlugin,
mixamo::{MixamoImportEvent, MixamoPlugin},
persistance::PersistancePlugin,
remote::RemoteAssetPlugin,
scene::{LoadSceneEvent, ScenePlugin},
skybox::{SkyboxId, SkyboxPlugin},
ui::UiLayerPlugin,
@@ -251,6 +253,7 @@ pub fn start_internal(
#[cfg(feature = "wasm")]
viewport::wasm::ViewportPlugin,
WebAssetPlugin,
RemoteAssetPlugin,
DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
resolution: viewport_size.into(),
+68
View File
@@ -10,6 +10,13 @@ impl Plugin for PersistancePlugin {
EditorTabName::Other("Persistance".to_string()),
DebugPersistanceTab::default(),
);
#[cfg(feature = "wasm")]
app.add_systems(Update, wasm::forward_save_scene_events)
.add_systems(
OnEnter(SaveState::Idle),
wasm::on_scene_saved.map(bevy::utils::warn),
);
}
}
@@ -43,3 +50,64 @@ impl EditorTab for DebugPersistanceTab {
"Persistance".into()
}
}
#[cfg(feature = "wasm")]
mod wasm {
use bevy::{prelude::*, window::PrimaryWindow};
use space_editor::prelude::{EditorEvent, EditorPrefabPath, PrefabMemoryCache};
use wasm_bindgen::prelude::*;
use crate::wasm::{self, StaticBuffer, WasmMessageBuffer};
static SAVE_SCENE: WasmMessageBuffer<()> = WasmMessageBuffer::new();
#[wasm_bindgen(js_name = saveScene)]
pub fn save_scene() {
SAVE_SCENE.write_message(());
}
pub(super) fn forward_save_scene_events(mut ew_editor_events: EventWriter<EditorEvent>) {
if SAVE_SCENE.take_message().is_some() {
ew_editor_events.send(EditorEvent::Save(EditorPrefabPath::MemoryCahce));
}
}
#[wasm_bindgen(typescript_custom_section)]
const SCENE_SAVED_EVENT: &str = r#"
export interface SceneSavedEvent extends CustomEvent {
type: "scene-saved";
detail: string;
}
declare global {
export interface GlobalEventHandlersEventMap {
"scene-saved": SceneSavedEvent;
}
}
"#;
pub(super) fn on_scene_saved(
r_scene_cache: Res<PrefabMemoryCache>,
r_type_registry: Res<AppTypeRegistry>,
ra_scenes: Res<Assets<DynamicScene>>,
q_window: Query<&Window, With<PrimaryWindow>>,
) -> Result<(), String> {
let handle = r_scene_cache
.scene
.as_ref()
.ok_or_else(|| "Scene cache is empty!".to_string())?;
let serialized = match ra_scenes.get(handle) {
Some(scene) => scene
.serialize_ron(&r_type_registry)
.map_err(|err| format!("{err}")),
None => Err("No scene found for cached handle!".into()),
}?;
let Ok(window) = q_window.get_single() else {
return Ok(());
};
wasm::dispatch_to_js(window, "scene-saved", &serialized)
}
}
+22
View File
@@ -0,0 +1,22 @@
use bevy::{
asset::io::{AssetSource, AssetSourceId},
prelude::*,
};
use self::reader::RemoteAssetReader;
mod reader;
mod response;
pub struct RemoteAssetPlugin;
impl Plugin for RemoteAssetPlugin {
fn build(&self, app: &mut App) {
app.register_asset_source(
AssetSourceId::Name("remote".into()),
AssetSource::build()
.with_reader(|| Box::new(RemoteAssetReader))
.with_writer(|| None),
);
}
}
+114
View File
@@ -0,0 +1,114 @@
use std::{io::ErrorKind, path::Path};
use bevy::{
asset::io::{AssetReader, AssetReaderError, PathStream, Reader, VecReader},
utils::BoxedFuture,
};
use reqwest::StatusCode;
use super::response::GetMediaFileSuccessResponse;
pub struct RemoteAssetReader;
impl RemoteAssetReader {
const API_BASE_URL: &'static str = "https://api.fakeyou.com/v1/media_files/file";
const BUCKET_BASE_URL: &'static str = "https://storage.googleapis.com/vocodes-public";
}
impl AssetReader for RemoteAssetReader {
fn read<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
Box::pin(async move {
let token = path
.file_stem()
.ok_or_else(|| {
AssetReaderError::Io(std::io::Error::new(
ErrorKind::Other,
format!("Failed to parse token from '{path:?}'"),
))
})?
.to_str()
.unwrap();
let response = reqwest::get(format!("{}/{token}", &Self::API_BASE_URL))
.await
.map_err(IntoError::<AssetReaderError>::into)?;
let response = match response.status() {
StatusCode::OK => response
.json::<GetMediaFileSuccessResponse>()
.await
.map_err(IntoError::<AssetReaderError>::into),
other => Err(AssetReaderError::Io(std::io::Error::new(
ErrorKind::Other,
format!("Status Code from endpoint: {other}"),
))),
}?;
let url = format!(
"{}{}",
&Self::BUCKET_BASE_URL,
&response.media_file.public_bucket_path
);
let response = reqwest::get(url)
.await
.map_err(IntoError::<AssetReaderError>::into)?;
let bytes = match response.status() {
// TODO: This is not thread-safe, but some variation of this
// would be better than the current solution once Bevy
// WASM becomes multi-threaded:
// StatusCode::OK => Ok(response.bytes_stream().map(|result| {
// result.map_err(|err| std::io::Error::new(ErrorKind::Other, format!("{err}")))
// })),
StatusCode::OK => Ok(response
.bytes()
.await
.map_err(IntoError::<AssetReaderError>::into)?
.into_iter()
.collect::<Vec<_>>()),
other => Err(AssetReaderError::Io(std::io::Error::new(
ErrorKind::Other,
format!("Status Code from bucket URL: {other}"),
))),
}?;
let reader: Box<Reader> = Box::new(VecReader::new(bytes));
Ok(reader)
})
}
fn read_meta<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<Reader<'a>>, AssetReaderError>> {
// TODO: Do we want to try to read .meta files?
Box::pin(async { Err(AssetReaderError::NotFound(path.into())) })
}
fn read_directory<'a>(
&'a self,
path: &'a Path,
) -> BoxedFuture<'a, Result<Box<PathStream>, AssetReaderError>> {
// We won't have to deal with directories
Box::pin(async { Err(AssetReaderError::NotFound(path.into())) })
}
fn is_directory<'a>(&'a self, _: &'a Path) -> BoxedFuture<'a, Result<bool, AssetReaderError>> {
// We won't have to deal with directories
Box::pin(async { Ok(false) })
}
}
trait IntoError<Output> {
fn into(self) -> Output;
}
impl IntoError<AssetReaderError> for reqwest::Error {
fn into(self) -> AssetReaderError {
AssetReaderError::Io(std::io::Error::new(ErrorKind::Other, format!("{self}")))
}
}
+107
View File
@@ -0,0 +1,107 @@
//! Response types copy/pasted straight from the backend source code.
use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub struct GetMediaFileSuccessResponse {
pub success: bool,
pub media_file: MediaFileInfo,
}
#[derive(Deserialize, Debug)]
pub struct MediaFileInfo {
pub token: String,
/// Type of media will dictate which fields are populated and what
/// the frontend should display (eg. video player vs audio player).
pub media_type: String,
/// If the file was generated as part of a batch, this is the token for the batch.
pub maybe_batch_token: Option<String>,
/// URL to the media file
pub public_bucket_path: String,
//// Provenance Data
//pub origin_product: MediaFileOriginProductCategory,
//pub origin_category: MediaFileOriginCategory,
//pub maybe_origin_model_type: Option<MediaFileOriginModelType>,
//pub maybe_origin_model_token: Option<String>,
//pub maybe_origin_filename: Option<String>,
/// Possible model info (if generated by inference)
pub maybe_model_weight_info: Option<GetMediaFileModelInfo>,
/// User info
pub maybe_creator_user: Option<UserDetailsLight>,
pub creator_set_visibility: String,
/// Text transcripts for TTS, etc.
pub maybe_text_transcript: Option<String>,
/// We can simulate media files for "tts_results" records.
/// If this is set as true, this informs the frontend and API callers not to treat
/// the token as a media file and improperly assume it can be used with the rest of
/// the media file APIs.
pub is_emulated_media_file: bool,
/// Statistics about the media file
pub stats: SimpleEntityStats,
pub created_at: String,
pub updated_at: String,
//pub maybe_moderator_fields: Option<ModeratorFields>,
}
#[derive(Deserialize, Debug)]
pub struct GetMediaFileModelInfo {
pub weight_token: String,
pub weight_type: String,
pub weight_category: String,
pub title: String,
/// Cover images are small descriptive images that can be set for any model.
/// If a cover image is set, this is the path to the asset.
pub maybe_cover_image_public_bucket_path: Option<String>,
/// NB: Technically this shouldn't be a nullable field, but since there are so many
/// joins we'll relax that this must exist (for now).
pub maybe_weight_creator: Option<UserDetailsLight>,
}
#[derive(Deserialize, Debug)]
pub struct UserDetailsLight {
/// The token for the user
pub user_token: String,
/// The unique username someone logs in with
/// As of 2023-08-23, this is always lowercase
pub username: String,
/// As of 2023-08-23, this is the username with capitalization
/// (In the future, a display name can be customized by the user.)
pub display_name: String,
/// Email hash for Gravatar
/// Always set for now since login is email/username+password.
/// In the future this will need to become an optional *OR* be filled with a bogus hash.
pub gravatar_hash: String,
/// For users without a gravatar, we show one of our own avatars.
pub default_avatar: DefaultAvatarInfo,
// In the future, we'll also support user-uploaded avatars that we store on our servers.
}
#[derive(Deserialize, Debug)]
pub struct DefaultAvatarInfo {
pub image_index: u8,
pub color_index: u8,
}
#[derive(Deserialize, Debug)]
pub struct SimpleEntityStats {
/// Number of positive ratings (or "likes") for this item
pub positive_rating_count: u32,
/// Number of bookmarks for this item
pub bookmark_count: u32,
}