mirror of
https://github.com/storytold/storyteller-bevy.git
synced 2026-10-17 00:13:17 +00:00
Merge branch 'main' of https://github.com/storytold/storyteller-bevy into animation-clips-setup
This commit is contained in:
@@ -21,6 +21,11 @@ frontend project, run:
|
||||
npx nx serve studio-frontend
|
||||
```
|
||||
|
||||
You'll need to install the wasm compiler for Rust:
|
||||
```
|
||||
rustup target add wasm32-unknown-unknown
|
||||
```
|
||||
|
||||
Projects are located in directories at the workspace root. The list of commands
|
||||
supported by a given project can be found under the `target` property of its
|
||||
`project.json` file, which defines the Nx configuration for that project.
|
||||
|
||||
@@ -30,4 +30,18 @@ describe("@on(eventName)", () => {
|
||||
|
||||
expect(counter.count).toBe(3);
|
||||
});
|
||||
|
||||
it("exhibits correct `this` handling when multiple instances are present", () => {
|
||||
const counter1 = document.createElement("test-counter");
|
||||
document.body.appendChild(counter1);
|
||||
|
||||
const counter2 = document.createElement("test-counter");
|
||||
document.body.appendChild(counter2);
|
||||
|
||||
counter1.click();
|
||||
counter2.click();
|
||||
|
||||
expect(counter1.count).toBe(1);
|
||||
expect(counter2.count).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,7 +15,7 @@ export function on(eventSelector: string) {
|
||||
? eventSelector.split(":")
|
||||
: [null, eventSelector];
|
||||
|
||||
let target = match (targetName, {
|
||||
const target = match (targetName, {
|
||||
"window": () => window as EventTarget,
|
||||
"document": () => document as EventTarget,
|
||||
_: () => null,
|
||||
@@ -23,17 +23,35 @@ export function on(eventSelector: string) {
|
||||
|
||||
return <T extends LitElement>(proto: T, propName: keyof T, desc: PropertyDescriptor) => {
|
||||
const handler = getMethodDescriptor(proto, propName)?.value ?? (() => {});
|
||||
const $handler = Symbol(String(propName));
|
||||
|
||||
type Decorated = T & {
|
||||
[$handler]: typeof handler;
|
||||
}
|
||||
|
||||
prependRoutine(proto, "connectedCallback", function (this: T) {
|
||||
target ??= this;
|
||||
target.addEventListener(eventName, handler.bind(this));
|
||||
assertType<Decorated>(this);
|
||||
this[$handler] ??= handler.bind(this);
|
||||
|
||||
if (target) {
|
||||
target.addEventListener(eventName, this[$handler]);
|
||||
} else {
|
||||
this.addEventListener(eventName, this[$handler]);
|
||||
}
|
||||
});
|
||||
|
||||
prependRoutine(proto, "disconnectedCallback", function (this: T) {
|
||||
target ??= this;
|
||||
target.removeEventListener(eventName, handler.bind(this));
|
||||
assertType<Decorated>(this);
|
||||
|
||||
if (target) {
|
||||
target.removeEventListener(eventName, this[$handler]);
|
||||
} else {
|
||||
this.removeEventListener(eventName, this[$handler]);
|
||||
}
|
||||
});
|
||||
|
||||
return desc;
|
||||
}
|
||||
}
|
||||
|
||||
function assertType<T>(value: unknown): asserts value is T {}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Ctor, Fn, Method } from "@storyteller/utility";
|
||||
import { Ctor, Fn } from "@storyteller/utility";
|
||||
import { LitElement } from "lit";
|
||||
|
||||
import { appendRoutine, prependRoutine } from "../decorators/internal";
|
||||
@@ -7,6 +7,7 @@ import { Token } from "./types";
|
||||
|
||||
const $injector = Symbol("injector");
|
||||
const $$injector = Symbol("#injector");
|
||||
const $fulfillInjectionRequest = Symbol("fulfillInjectionRequest");
|
||||
|
||||
export type ProviderOptions<T, E extends LitElement>
|
||||
= Token<T>
|
||||
@@ -41,6 +42,7 @@ export function provide<T, E extends LitElement>(options: ProviderOptions<T, E>)
|
||||
type Decorated = E & {
|
||||
[$$injector]?: Map<Token<any>, any> | undefined;
|
||||
readonly [$injector]: Map<Token<any>, any>;
|
||||
[$fulfillInjectionRequest](event: InjectionRequest<T>): void;
|
||||
}
|
||||
|
||||
return (Target: Ctor<E>) => {
|
||||
@@ -48,9 +50,7 @@ export function provide<T, E extends LitElement>(options: ProviderOptions<T, E>)
|
||||
assertType<Decorated>(proto);
|
||||
|
||||
let descriptor = Object.getOwnPropertyDescriptor(proto, $injector);
|
||||
let needsInit = false;
|
||||
if (!($injector in proto) || !descriptor) {
|
||||
needsInit = true;
|
||||
Object.defineProperty(proto, $injector, {
|
||||
get(this: Decorated) { return this[$$injector] ??= new Map() },
|
||||
configurable: false,
|
||||
@@ -73,17 +73,16 @@ export function provide<T, E extends LitElement>(options: ProviderOptions<T, E>)
|
||||
this[$injector].set(token, value);
|
||||
this.dispatchEvent(new DependencyProvision(token, value));
|
||||
|
||||
if (needsInit) {
|
||||
const fulfill = fulfillInjectionRequest.bind(this);
|
||||
this.addEventListener(DIEvent.InjectionRequest, fulfill as Method<HTMLElement>);
|
||||
}
|
||||
this[$fulfillInjectionRequest] ??= fulfillInjectionRequest.bind(this);
|
||||
this.addEventListener(DIEvent.InjectionRequest, this[$fulfillInjectionRequest]);
|
||||
});
|
||||
|
||||
appendRoutine(proto, "disconnectedCallback", function (this: Decorated): void {
|
||||
prependRoutine(proto, "disconnectedCallback", function (this: Decorated): void {
|
||||
const token = "token" in options ? options.token : options;
|
||||
const value = this[$injector].get(token);
|
||||
|
||||
this.dispatchEvent(new ProviderRemoval(token, value));
|
||||
this.removeEventListener(DIEvent.InjectionRequest, this[$fulfillInjectionRequest]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -140,43 +139,45 @@ export function inject<T, E extends LitElement>(token: Token<T>) {
|
||||
*/
|
||||
export function queryProviders<T, E extends LitElement>(token: Token<T>) {
|
||||
return <K extends keyof E>(proto: E, key: K) => {
|
||||
function onProvided(this: E, event: DependencyProvision<T>): void {
|
||||
const array = this[key] ?? [];
|
||||
assertType<E[K] & T[]>(array);
|
||||
const $onProvided = Symbol(`onProvided:${String(key)}`);
|
||||
const $onRemoved = Symbol(`onRemoved:${String(key)}`);
|
||||
|
||||
if (event.detail.token === token)
|
||||
this[key] = array.concat(event.detail.value) as E[K];
|
||||
type Decorated = E & {
|
||||
[$onProvided](event: DependencyProvision<T>): void;
|
||||
[$onRemoved](event: ProviderRemoval<T>): void;
|
||||
}
|
||||
|
||||
function onRemoved(this: E, event: ProviderRemoval<T>): void {
|
||||
assertType<Decorated>(proto);
|
||||
|
||||
function onProvided(this: Decorated, event: DependencyProvision<T>): void {
|
||||
const array = this[key] ?? [];
|
||||
assertType<E[K] & T[]>(array);
|
||||
assertType<Decorated[K] & T[]>(array);
|
||||
|
||||
if (event.detail.token === token)
|
||||
this[key] = array.filter(el => el !== event.detail.value) as E[K];
|
||||
this[key] = array.concat(event.detail.value) as Decorated[K];
|
||||
}
|
||||
|
||||
appendRoutine(proto, "connectedCallback", function (this: E): void {
|
||||
this[key] = [] as E[K];
|
||||
this.addEventListener(
|
||||
DIEvent.DependencyProvision,
|
||||
onProvided.bind(this) as Method<HTMLElement>,
|
||||
);
|
||||
this.addEventListener(
|
||||
DIEvent.ProviderRemoval,
|
||||
onRemoved.bind(this) as Method<HTMLElement>,
|
||||
);
|
||||
function onRemoved(this: Decorated, event: ProviderRemoval<T>): void {
|
||||
const array = this[key] ?? [];
|
||||
assertType<Decorated[K] & T[]>(array);
|
||||
|
||||
if (event.detail.token === token)
|
||||
this[key] = array.filter(el => el !== event.detail.value) as Decorated[K];
|
||||
}
|
||||
|
||||
appendRoutine(proto, "connectedCallback", function (this: Decorated): void {
|
||||
this[key] = [] as Decorated[K];
|
||||
|
||||
this[$onProvided] ??= onProvided.bind(this);
|
||||
this[$onRemoved] ??= onRemoved.bind(this);
|
||||
|
||||
this.addEventListener(DIEvent.DependencyProvision, this[$onProvided]);
|
||||
this.addEventListener(DIEvent.ProviderRemoval, this[$onRemoved]);
|
||||
});
|
||||
|
||||
appendRoutine(proto, "disconnectedCallback", function (this: E): void {
|
||||
this.removeEventListener(
|
||||
DIEvent.DependencyProvision,
|
||||
onProvided.bind(this) as Method<HTMLElement>,
|
||||
);
|
||||
this.removeEventListener(
|
||||
DIEvent.ProviderRemoval,
|
||||
onRemoved.bind(this) as Method<HTMLElement>,
|
||||
);
|
||||
appendRoutine(proto, "disconnectedCallback", function (this: Decorated): void {
|
||||
this.removeEventListener(DIEvent.DependencyProvision, this[$onProvided]);
|
||||
this.removeEventListener(DIEvent.ProviderRemoval, this[$onRemoved]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
import { LitElement, html, render } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
|
||||
import { provide, inject, queryProviders } from "./decorators";
|
||||
import { UniqueToken } from "./types";
|
||||
|
||||
const TEST_TOKEN = UniqueToken.create<HTMLElement>();
|
||||
@customElement("test-token-provider")
|
||||
@provide(TEST_TOKEN)
|
||||
class TestTokenProvider extends LitElement {
|
||||
override render = () => html`<slot></slot>`
|
||||
}
|
||||
|
||||
@customElement("test-concrete-provider")
|
||||
@provide(TestConcreteProvider)
|
||||
class TestConcreteProvider extends LitElement {
|
||||
override render = () => html`<slot></slot>`
|
||||
}
|
||||
|
||||
const TEST_VALUE = UniqueToken.create<number>();
|
||||
@customElement("test-value-provider")
|
||||
@provide({
|
||||
token: TEST_VALUE,
|
||||
provide() { return this.value; }
|
||||
})
|
||||
class TestValueProvider extends LitElement {
|
||||
@property({ type: Number })
|
||||
value = 42;
|
||||
|
||||
override render = () => html`<slot></slot>`
|
||||
}
|
||||
|
||||
@customElement("test-injector")
|
||||
class TestInjector extends LitElement {
|
||||
@inject(TEST_TOKEN)
|
||||
tokenProvider: unknown;
|
||||
|
||||
@inject(TestConcreteProvider)
|
||||
concreteProvider?: TestConcreteProvider;
|
||||
|
||||
@inject(TEST_VALUE)
|
||||
valueProvider?: number;
|
||||
|
||||
override render = () => html`<slot></slot>`;
|
||||
}
|
||||
|
||||
@customElement("test-queryer")
|
||||
class TestQueryer extends LitElement {
|
||||
@queryProviders(TEST_TOKEN)
|
||||
tokenProviders: unknown[] = [];
|
||||
|
||||
@queryProviders(TestConcreteProvider)
|
||||
concreteProviders: TestConcreteProvider[] = [];
|
||||
|
||||
@queryProviders(TEST_VALUE)
|
||||
valueProviders: number[] = [];
|
||||
|
||||
override render = () => html`<slot></slot>`
|
||||
}
|
||||
|
||||
describe("`@provide` and `@inject`", () => {
|
||||
it("works with abstract tokens", () => {
|
||||
const providerRef = createRef<TestTokenProvider>();
|
||||
const injectorRef = createRef<TestInjector>();
|
||||
|
||||
render(html`
|
||||
<test-token-provider ${ref(providerRef)}>
|
||||
<test-injector ${ref(injectorRef)}></test-injector>
|
||||
</test-token-provider>
|
||||
`, document.body);
|
||||
|
||||
expect(providerRef.value).toBeInstanceOf(TestTokenProvider);
|
||||
expect(injectorRef.value).toBeInstanceOf(TestInjector);
|
||||
expect(injectorRef.value!.tokenProvider).toBe(providerRef.value);
|
||||
});
|
||||
|
||||
it("works with concrete tokens", () => {
|
||||
const providerRef = createRef<TestConcreteProvider>();
|
||||
const injectorRef = createRef<TestInjector>();
|
||||
|
||||
render(html`
|
||||
<test-concrete-provider ${ref(providerRef)}>
|
||||
<test-injector ${ref(injectorRef)}></test-injector>
|
||||
</test-concrete-provider>
|
||||
`, document.body);
|
||||
|
||||
expect(providerRef.value).toBeInstanceOf(TestConcreteProvider);
|
||||
expect(injectorRef.value).toBeInstanceOf(TestInjector);
|
||||
expect(injectorRef.value!.concreteProvider).toBe(providerRef.value);
|
||||
});
|
||||
|
||||
it("works with value tokens", () => {
|
||||
const providerRef = createRef<TestValueProvider>();
|
||||
const injectorRef = createRef<TestInjector>();
|
||||
|
||||
render(html`
|
||||
<test-value-provider ${ref(providerRef)}>
|
||||
<test-injector ${ref(injectorRef)}></test-injector>
|
||||
</test-value-provider>
|
||||
`, document.body);
|
||||
|
||||
expect(providerRef.value).toBeInstanceOf(TestValueProvider);
|
||||
expect(injectorRef.value).toBeInstanceOf(TestInjector);
|
||||
expect(injectorRef.value!.valueProvider).toBe(42);
|
||||
});
|
||||
|
||||
it("works with multiple provider/injector instances", () => {
|
||||
const parentProviderRef = createRef<TestValueProvider>();
|
||||
const childProviderRef = createRef<TestValueProvider>();
|
||||
const parentInjectorRef = createRef<TestInjector>();
|
||||
const childInjectorRef = createRef<TestInjector>();
|
||||
|
||||
render(html`
|
||||
<test-value-provider
|
||||
${ref(parentProviderRef)}
|
||||
.value=${420}
|
||||
>
|
||||
<test-injector ${ref(parentInjectorRef)}></test-injector>
|
||||
<test-value-provider
|
||||
${ref(childProviderRef)}
|
||||
.value=${69}
|
||||
>
|
||||
<test-injector ${ref(childInjectorRef)}></test-injector>
|
||||
</test-value-provider>
|
||||
</test-value-provider>
|
||||
`, document.body);
|
||||
|
||||
expect(parentProviderRef.value).toBeInstanceOf(TestValueProvider);
|
||||
expect(childProviderRef.value).toBeInstanceOf(TestValueProvider);
|
||||
expect(parentInjectorRef.value).toBeInstanceOf(TestInjector);
|
||||
expect(childInjectorRef.value).toBeInstanceOf(TestInjector);
|
||||
|
||||
expect(parentInjectorRef.value!.valueProvider).toBe(420);
|
||||
expect(childInjectorRef.value!.valueProvider).toBe(69);
|
||||
});
|
||||
|
||||
it("works with nested providers", () => {
|
||||
const tokenProviderRef = createRef<TestTokenProvider>();
|
||||
const concreteProviderRef = createRef<TestConcreteProvider>();
|
||||
const valueProviderRef = createRef<TestValueProvider>();
|
||||
const injectorRef = createRef<TestInjector>();
|
||||
|
||||
render(html`
|
||||
<test-token-provider ${ref(tokenProviderRef)}>
|
||||
<test-concrete-provider ${ref(concreteProviderRef)}>
|
||||
<test-value-provider ${ref(valueProviderRef)}>
|
||||
<test-injector ${ref(injectorRef)}></test-injector>
|
||||
</test-value-provider>
|
||||
</test-concrete-provider>
|
||||
</test-token-provider>
|
||||
`, document.body);
|
||||
|
||||
expect(tokenProviderRef.value).toBeInstanceOf(TestTokenProvider);
|
||||
expect(concreteProviderRef.value).toBeInstanceOf(TestConcreteProvider);
|
||||
expect(valueProviderRef.value).toBeInstanceOf(TestValueProvider);
|
||||
expect(injectorRef.value).toBeInstanceOf(TestInjector);
|
||||
|
||||
expect(injectorRef.value!.tokenProvider).toBe(tokenProviderRef.value);
|
||||
expect(injectorRef.value!.concreteProvider).toBe(concreteProviderRef.value);
|
||||
expect(injectorRef.value!.valueProvider).toBe(42);
|
||||
});
|
||||
});
|
||||
|
||||
describe("`@provide` and `@queryProviders`", () => {
|
||||
it("works with abstract tokens", () => {
|
||||
const queryerRef = createRef<TestQueryer>();
|
||||
const providerRef = createRef<TestTokenProvider>();
|
||||
|
||||
render(html`
|
||||
<test-queryer ${ref(queryerRef)}>
|
||||
<test-token-provider ${ref(providerRef)}></test-token-provider>
|
||||
</test-queryer>
|
||||
`, document.body);
|
||||
|
||||
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
|
||||
expect(providerRef.value).toBeInstanceOf(TestTokenProvider);
|
||||
|
||||
expect(queryerRef.value?.tokenProviders).toEqual([providerRef.value]);
|
||||
});
|
||||
|
||||
it("works with concrete tokens", () => {
|
||||
const queryerRef = createRef<TestQueryer>();
|
||||
const providerRef = createRef<TestConcreteProvider>();
|
||||
|
||||
render(html`
|
||||
<test-queryer ${ref(queryerRef)}>
|
||||
<test-concrete-provider ${ref(providerRef)}></test-concrete-provider>
|
||||
</test-queryer>
|
||||
`, document.body);
|
||||
|
||||
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
|
||||
expect(providerRef.value).toBeInstanceOf(TestConcreteProvider);
|
||||
|
||||
expect(queryerRef.value?.concreteProviders).toEqual([providerRef.value]);
|
||||
});
|
||||
|
||||
it("works with value tokens", () => {
|
||||
const queryerRef = createRef<TestQueryer>();
|
||||
const providerRef = createRef<TestValueProvider>();
|
||||
|
||||
render(html`
|
||||
<test-queryer ${ref(queryerRef)}>
|
||||
<test-value-provider ${ref(providerRef)}></test-value-provider>
|
||||
</test-queryer>
|
||||
`, document.body);
|
||||
|
||||
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
|
||||
expect(providerRef.value).toBeInstanceOf(TestValueProvider);
|
||||
|
||||
expect(queryerRef.value?.valueProviders).toEqual([42]);
|
||||
});
|
||||
|
||||
it("works with multiple providers", () => {
|
||||
const queryerRef = createRef<TestQueryer>();
|
||||
|
||||
render(html`
|
||||
<test-queryer ${ref(queryerRef)}>
|
||||
<test-value-provider .value=${42}></test-value-provider>
|
||||
<test-value-provider .value=${420}></test-value-provider>
|
||||
<test-value-provider .value=${69}></test-value-provider>
|
||||
</test-queryer>
|
||||
`, document.body);
|
||||
|
||||
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
|
||||
expect(queryerRef.value!.valueProviders).toEqual([42, 420, 69]);
|
||||
});
|
||||
|
||||
it("dynamically updates", () => {
|
||||
const queryerRef = createRef<TestQueryer>();
|
||||
|
||||
render(html`
|
||||
<test-queryer ${ref(queryerRef)}>
|
||||
<test-value-provider .value=${42}></test-value-provider>
|
||||
<test-value-provider .value=${420}></test-value-provider>
|
||||
<test-value-provider .value=${69}></test-value-provider>
|
||||
</test-queryer>
|
||||
`, document.body);
|
||||
|
||||
expect(queryerRef.value).toBeInstanceOf(TestQueryer);
|
||||
expect(queryerRef.value!.valueProviders).toEqual([42, 420, 69]);
|
||||
|
||||
const middleElement = queryerRef.value!.children.item(1) as TestValueProvider;
|
||||
expect(middleElement).toBeInstanceOf(TestValueProvider);
|
||||
middleElement.disconnectedCallback();
|
||||
middleElement.remove();
|
||||
|
||||
expect(queryerRef.value!.valueProviders).toEqual([42, 69]);
|
||||
});
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type AbstractCtor, type Ctor, type Fn } from "@storyteller/utility";
|
||||
import { type PropertyValues } from "lit";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
export class UniqueToken {
|
||||
get id() { return this.#id; }
|
||||
@@ -10,7 +11,7 @@ export class UniqueToken {
|
||||
}
|
||||
|
||||
static create<T>(name?: string): Token<T> {
|
||||
return new UniqueToken(name ?? crypto.randomUUID());
|
||||
return new UniqueToken(name ?? uuid());
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { customElement, state } from "lit/decorators.js";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { createRef, ref } from "lit/directives/ref.js";
|
||||
import { styleMap } from "lit/directives/style-map.js";
|
||||
import { v4 as uuid } from "uuid";
|
||||
|
||||
import { ImageInfo, LcmClient } from "./lcm-client";
|
||||
|
||||
@@ -38,7 +39,7 @@ export class AppElement extends LitElement {
|
||||
@state() inputUrl?: string;
|
||||
@state() outputUrl?: string;
|
||||
|
||||
#lcmClient = new LcmClient(crypto.randomUUID());
|
||||
#lcmClient = new LcmClient(uuid());
|
||||
#outputImageRef = createRef<HTMLImageElement>();
|
||||
|
||||
constructor () {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
[[headers]]
|
||||
for = "/*"
|
||||
[headers.values]
|
||||
# NB: This is potentially dangerous, but I don't think we have much other choice:
|
||||
Access-Control-Allow-Origin = "*"
|
||||
|
||||
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors
|
||||
# NB: The *.netlify.app is dangerous.
|
||||
Content-Security-Policy = "frame-ancestors 'self' https://fakeyou.com https://*.fakeyou.com https://storyteller.ai https://*.storyteller.ai https://*.netlify.app http://*.fakeyou.com:7000 http://*.fakeyou.com:7001 http://*.fakeyou.com:7002 http://*.storyteller.ai:7000 http://*.storyteller.ai:7001 http://*.storyteller.ai:7002;"
|
||||
|
||||
@@ -4,37 +4,23 @@ import { SceneState, type SceneStateEvent } from "@storyteller/studio";
|
||||
import { LitElement, nothing } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
|
||||
const PARENT_ORIGIN_WHITELIST: readonly string[] = [
|
||||
"https://fakeyou.com",
|
||||
"https://storyteller.ai",
|
||||
"https://staging.fakeyou.com",
|
||||
"https://staging.storyteller.ai",
|
||||
"https://feature-mvp--fakeyou.netlify.app",
|
||||
"https://feature-mvp--storyteller.netlify.app",
|
||||
"http://dev.fakeyou.com:7000",
|
||||
"http://dev.fakeyou.com:7001",
|
||||
"http://dev.fakeyou.com:7002",
|
||||
"http://dev.storyteller.com:7000",
|
||||
"http://dev.storyteller.com:7001",
|
||||
"http://dev.storyteller.com:7002",
|
||||
"http://localhost:7000",
|
||||
"http://localhost:7001",
|
||||
"http://localhost:7002",
|
||||
];
|
||||
|
||||
@customElement("sts-iframe-messaging-bridge")
|
||||
export class IframeMessagingBridgeElement extends LitElement {
|
||||
#parentWindow?: Window;
|
||||
#parentOrigin?: string;
|
||||
|
||||
override connectedCallback(): void {
|
||||
if (
|
||||
window.parent
|
||||
&& window.parent !== window
|
||||
&& PARENT_ORIGIN_WHITELIST.includes(window.parent.origin)
|
||||
) {
|
||||
this.#parentWindow = window.parent;
|
||||
this.#parentOrigin = window.parent.origin;
|
||||
console.log('window.parent', window.parent);
|
||||
|
||||
try {
|
||||
if (
|
||||
window.parent
|
||||
&& window.parent !== window
|
||||
) {
|
||||
this.#parentWindow = window.parent;
|
||||
}
|
||||
}
|
||||
catch {
|
||||
this.#parentWindow = undefined;
|
||||
}
|
||||
|
||||
super.connectedCallback();
|
||||
@@ -42,8 +28,7 @@ export class IframeMessagingBridgeElement extends LitElement {
|
||||
|
||||
@on("window:message")
|
||||
onParentMessage(event: MessageEvent<string>): void {
|
||||
if (!this.#parentOrigin || event.origin !== this.#parentOrigin)
|
||||
return;
|
||||
console.log('window.parent', window.parent);
|
||||
|
||||
if (event.data === "save-scene")
|
||||
studio.saveScene();
|
||||
@@ -51,19 +36,23 @@ export class IframeMessagingBridgeElement extends LitElement {
|
||||
|
||||
@on("window:scene-uploaded")
|
||||
onSceneUploaded(event: CustomEvent<string>): void {
|
||||
if (!this.#parentWindow || !this.#parentOrigin)
|
||||
console.log('window.parent', window.parent);
|
||||
|
||||
if (!this.#parentWindow)
|
||||
return;
|
||||
|
||||
this.#parentWindow.postMessage(`scene-saved:${event.detail}`, this.#parentOrigin);
|
||||
this.#parentWindow.postMessage(`scene-saved:${event.detail}`, "*");
|
||||
}
|
||||
|
||||
@on("window:scene-state")
|
||||
onSceneStateChange({ detail: state }: SceneStateEvent): void {
|
||||
if (!this.#parentWindow || !this.#parentOrigin)
|
||||
console.log('window.parent', window.parent);
|
||||
|
||||
if (!this.#parentWindow)
|
||||
return;
|
||||
|
||||
if (state === SceneState.Active)
|
||||
this.#parentWindow.postMessage("studio-ready", this.#parentOrigin);
|
||||
this.#parentWindow.postMessage("studio-ready", "*");
|
||||
}
|
||||
|
||||
protected override render = () => nothing;
|
||||
|
||||
+255
-64
@@ -1,6 +1,7 @@
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
|
||||
use bevy::{
|
||||
ecs::world::unsafe_world_cell::UnsafeWorldCell,
|
||||
prelude::*,
|
||||
reflect::{serde::ReflectSerializer, GetTypeRegistration},
|
||||
utils::HashMap,
|
||||
@@ -60,6 +61,43 @@ impl Plugin for InspectorPlugin {
|
||||
app.register_inspect::<Transform>();
|
||||
app.register_inspect::<Name>();
|
||||
app.register_inspect::<MeshPrimitivePrefab>();
|
||||
|
||||
//register standart material
|
||||
{
|
||||
app.register_type::<ShortMaterial>()
|
||||
.add_event::<UpdateInspect<Handle<StandardMaterial>>>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_inspect_by_select_change::<Handle<StandardMaterial>>,
|
||||
update_inspect_by_remove::<Handle<StandardMaterial>>,
|
||||
update_inspect_by_change::<Handle<StandardMaterial>>,
|
||||
notify_asset_change_system::<StandardMaterial>,
|
||||
)
|
||||
.in_set(InspectorSystem::DetectChanges),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
serialize_standart_materal.in_set(InspectorSystem::PerComponentCollect),
|
||||
);
|
||||
|
||||
app.world.resource_mut::<InspectGetters>().getters.insert(
|
||||
StandardMaterial::get_type_registration()
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string(),
|
||||
Arc::new(|entity, path, world, f| unsafe {
|
||||
let handle = entity.get::<Handle<StandardMaterial>>().unwrap();
|
||||
let mut assets = world
|
||||
.get_resource_mut::<Assets<StandardMaterial>>()
|
||||
.unwrap();
|
||||
let material = assets.get_mut(handle).unwrap();
|
||||
|
||||
let reflect_field = material.reflect_path_mut(path).unwrap();
|
||||
(f)(reflect_field);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,12 +105,17 @@ impl Plugin for InspectorPlugin {
|
||||
struct InspectGetters {
|
||||
getters: HashMap<
|
||||
String,
|
||||
Arc<dyn Fn(&mut EntityWorldMut, &str, &dyn Fn(&mut dyn Reflect)) + Send + Sync>,
|
||||
Arc<
|
||||
dyn Fn(&mut EntityWorldMut, &str, &mut UnsafeWorldCell, &dyn Fn(&mut dyn Reflect))
|
||||
+ Send
|
||||
+ Sync,
|
||||
>,
|
||||
>,
|
||||
}
|
||||
|
||||
trait AppInspectable {
|
||||
fn register_inspect<T: Component + Reflect + GetTypeRegistration>(&mut self);
|
||||
fn register_asset_inspect<T: Asset + Reflect + GetTypeRegistration>(&mut self);
|
||||
}
|
||||
|
||||
impl AppInspectable for App {
|
||||
@@ -100,7 +143,7 @@ impl AppInspectable for App {
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string(),
|
||||
Arc::new(|entity, path, f| {
|
||||
Arc::new(|entity, path, _, f| {
|
||||
let mut component = entity.get_mut::<T>().unwrap();
|
||||
let reflect_field = component.reflect_path_mut(path).unwrap();
|
||||
(f)(reflect_field);
|
||||
@@ -108,6 +151,25 @@ impl AppInspectable for App {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_asset_inspect<T: Asset + Reflect + GetTypeRegistration>(&mut self) {
|
||||
self.register_type::<T>()
|
||||
.add_event::<UpdateInspect<Handle<T>>>()
|
||||
.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_inspect_by_select_change::<Handle<T>>,
|
||||
update_inspect_by_remove::<Handle<T>>,
|
||||
update_inspect_by_change::<Handle<T>>,
|
||||
notify_asset_change_system::<T>,
|
||||
)
|
||||
.in_set(InspectorSystem::DetectChanges),
|
||||
)
|
||||
.add_systems(
|
||||
Update,
|
||||
auto_serialize_asset::<T>.in_set(InspectorSystem::PerComponentCollect),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
@@ -160,7 +222,7 @@ impl<T> Default for UpdateInspect<T> {
|
||||
|
||||
fn update_field_by_ui(world: &mut World) {
|
||||
unsafe {
|
||||
let unsafe_world = world.as_unsafe_world_cell();
|
||||
let mut unsafe_world = world.as_unsafe_world_cell();
|
||||
let Some(msg) = CHANGE_FROM_UI.take_message() else {
|
||||
return;
|
||||
};
|
||||
@@ -181,68 +243,73 @@ fn update_field_by_ui(world: &mut World) {
|
||||
return;
|
||||
};
|
||||
|
||||
getter.as_ref()(&mut entity_mut, &msg.reflect_path, &|reflect_field| {
|
||||
if let Some(val) = reflect_field.downcast_mut::<f32>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
getter.as_ref()(
|
||||
&mut entity_mut,
|
||||
&msg.reflect_path,
|
||||
&mut unsafe_world,
|
||||
&|reflect_field| {
|
||||
if let Some(val) = reflect_field.downcast_mut::<f32>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<f64>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<u32>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<u64>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<i32>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<i64>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<bool>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<String>() {
|
||||
*val = msg.value.as_str().unwrap().to_string();
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<Vec3>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<Quat>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else {
|
||||
error!("Unsupported type {}!", &msg.reflect_path);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<f64>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<u32>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<u64>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<i32>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<i64>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<bool>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<String>() {
|
||||
*val = msg.value.as_str().unwrap().to_string();
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<Vec3>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else if let Some(val) = reflect_field.downcast_mut::<Quat>() {
|
||||
let Ok(msg_value) = serde_json::from_value(msg.value.clone()) else {
|
||||
error!("Failed to parse value: {}", msg.value);
|
||||
return;
|
||||
};
|
||||
*val = msg_value;
|
||||
} else {
|
||||
error!("Unsupported type {}!", &msg.reflect_path);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,6 +383,130 @@ fn auto_serialize_system<T: Component + Reflect + GetTypeRegistration>(
|
||||
ev_update_description.send(UpdateEntityDescription);
|
||||
}
|
||||
|
||||
fn notify_asset_change_system<T: Asset>(
|
||||
mut ev_inspect: EventWriter<UpdateInspect<Handle<T>>>,
|
||||
mut asset_changing: EventReader<AssetEvent<T>>,
|
||||
mut selected: Query<&Handle<T>, With<Selected>>,
|
||||
) {
|
||||
for ev in asset_changing.read() {
|
||||
for handle in selected.iter_mut() {
|
||||
if ev.is_modified(handle.id()) {
|
||||
ev_inspect.send(UpdateInspect::<Handle<T>>::default());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn auto_serialize_asset<T: Asset + Reflect + GetTypeRegistration>(
|
||||
q_t: Query<&Handle<T>, With<Selected>>,
|
||||
mut description: ResMut<EntityDescription>,
|
||||
registry: Res<AppTypeRegistry>,
|
||||
mut ev_inspect: EventReader<UpdateInspect<Handle<T>>>,
|
||||
mut ev_update_description: EventWriter<UpdateEntityDescription>,
|
||||
assets: Res<Assets<T>>,
|
||||
) {
|
||||
if ev_inspect.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
ev_inspect.clear();
|
||||
|
||||
if let Some(handle) = q_t.iter().next() {
|
||||
if let Some(asset) = assets.get(handle) {
|
||||
let registry = registry.read();
|
||||
info!(
|
||||
"type: {:?}",
|
||||
T::get_type_registration()
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string()
|
||||
);
|
||||
|
||||
let json = serde_json::to_value(ReflectSerializer::new(asset, ®istry)).unwrap();
|
||||
info!("json: {:?}", json.to_string());
|
||||
description.components.insert(
|
||||
T::get_type_registration()
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string(),
|
||||
json,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
description
|
||||
.components
|
||||
.remove(T::get_type_registration().type_info().type_path());
|
||||
}
|
||||
|
||||
ev_update_description.send(UpdateEntityDescription);
|
||||
}
|
||||
|
||||
#[derive(Reflect, Default)]
|
||||
#[reflect(Default)]
|
||||
struct ShortMaterial {
|
||||
pub base_color: Color,
|
||||
}
|
||||
|
||||
impl ShortMaterial {
|
||||
pub fn new(material: &StandardMaterial) -> Self {
|
||||
Self {
|
||||
base_color: material.base_color,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_standart_materal(
|
||||
q_m: Query<&Handle<StandardMaterial>>,
|
||||
mut description: ResMut<EntityDescription>,
|
||||
registry: Res<AppTypeRegistry>,
|
||||
mut ev_inspect: EventReader<UpdateInspect<Handle<StandardMaterial>>>,
|
||||
mut ev_update_description: EventWriter<UpdateEntityDescription>,
|
||||
assets: Res<Assets<StandardMaterial>>,
|
||||
) {
|
||||
if ev_inspect.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
ev_inspect.clear();
|
||||
|
||||
if let Some(handle) = q_m.iter().next() {
|
||||
if let Some(material) = assets.get(handle) {
|
||||
let registry = registry.read();
|
||||
let short_material = ShortMaterial::new(material);
|
||||
let json =
|
||||
serde_json::to_value(ReflectSerializer::new(&short_material, ®istry)).unwrap();
|
||||
let json = json[ShortMaterial::get_type_registration()
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string()]
|
||||
.clone();
|
||||
let json = json!(
|
||||
{
|
||||
StandardMaterial::get_type_registration()
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string(): json
|
||||
});
|
||||
info!("json: {:?}", json.to_string());
|
||||
description.components.insert(
|
||||
StandardMaterial::get_type_registration()
|
||||
.type_info()
|
||||
.type_path()
|
||||
.to_string(),
|
||||
json,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
description.components.remove(
|
||||
StandardMaterial::get_type_registration()
|
||||
.type_info()
|
||||
.type_path(),
|
||||
);
|
||||
}
|
||||
|
||||
ev_update_description.send(UpdateEntityDescription);
|
||||
}
|
||||
|
||||
fn update_description(
|
||||
description: ResMut<EntityDescription>,
|
||||
mut ev_update_description: EventReader<UpdateEntityDescription>,
|
||||
|
||||
Reference in New Issue
Block a user