diff --git a/README.md b/README.md index 2bab7bb..0ec9742 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/framework/src/lib/decorators/on.spec.ts b/framework/src/lib/decorators/on.spec.ts index 430456a..94866fa 100644 --- a/framework/src/lib/decorators/on.spec.ts +++ b/framework/src/lib/decorators/on.spec.ts @@ -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); + }); }); diff --git a/framework/src/lib/decorators/on.ts b/framework/src/lib/decorators/on.ts index dbd4f5b..c3a66a1 100644 --- a/framework/src/lib/decorators/on.ts +++ b/framework/src/lib/decorators/on.ts @@ -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 (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(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(this); + + if (target) { + target.removeEventListener(eventName, this[$handler]); + } else { + this.removeEventListener(eventName, this[$handler]); + } }); return desc; } } + +function assertType(value: unknown): asserts value is T {} diff --git a/framework/src/lib/di/decorators.ts b/framework/src/lib/di/decorators.ts index 6674c96..c49de19 100644 --- a/framework/src/lib/di/decorators.ts +++ b/framework/src/lib/di/decorators.ts @@ -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 = Token @@ -41,6 +42,7 @@ export function provide(options: ProviderOptions) type Decorated = E & { [$$injector]?: Map, any> | undefined; readonly [$injector]: Map, any>; + [$fulfillInjectionRequest](event: InjectionRequest): void; } return (Target: Ctor) => { @@ -48,9 +50,7 @@ export function provide(options: ProviderOptions) assertType(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(options: ProviderOptions) 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); - } + 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(token: Token) { */ export function queryProviders(token: Token) { return (proto: E, key: K) => { - function onProvided(this: E, event: DependencyProvision): void { - const array = this[key] ?? []; - assertType(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): void; + [$onRemoved](event: ProviderRemoval): void; } - function onRemoved(this: E, event: ProviderRemoval): void { + assertType(proto); + + function onProvided(this: Decorated, event: DependencyProvision): void { const array = this[key] ?? []; - assertType(array); + assertType(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, - ); - this.addEventListener( - DIEvent.ProviderRemoval, - onRemoved.bind(this) as Method, - ); + function onRemoved(this: Decorated, event: ProviderRemoval): void { + const array = this[key] ?? []; + assertType(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, - ); - this.removeEventListener( - DIEvent.ProviderRemoval, - onRemoved.bind(this) as Method, - ); + appendRoutine(proto, "disconnectedCallback", function (this: Decorated): void { + this.removeEventListener(DIEvent.DependencyProvision, this[$onProvided]); + this.removeEventListener(DIEvent.ProviderRemoval, this[$onRemoved]); }); } } diff --git a/framework/src/lib/di/di.spec.ts b/framework/src/lib/di/di.spec.ts new file mode 100644 index 0000000..400807a --- /dev/null +++ b/framework/src/lib/di/di.spec.ts @@ -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(); +@customElement("test-token-provider") +@provide(TEST_TOKEN) +class TestTokenProvider extends LitElement { + override render = () => html`` +} + +@customElement("test-concrete-provider") +@provide(TestConcreteProvider) +class TestConcreteProvider extends LitElement { + override render = () => html`` +} + +const TEST_VALUE = UniqueToken.create(); +@customElement("test-value-provider") +@provide({ + token: TEST_VALUE, + provide() { return this.value; } +}) +class TestValueProvider extends LitElement { + @property({ type: Number }) + value = 42; + + override render = () => html`` +} + +@customElement("test-injector") +class TestInjector extends LitElement { + @inject(TEST_TOKEN) + tokenProvider: unknown; + + @inject(TestConcreteProvider) + concreteProvider?: TestConcreteProvider; + + @inject(TEST_VALUE) + valueProvider?: number; + + override render = () => html``; +} + +@customElement("test-queryer") +class TestQueryer extends LitElement { + @queryProviders(TEST_TOKEN) + tokenProviders: unknown[] = []; + + @queryProviders(TestConcreteProvider) + concreteProviders: TestConcreteProvider[] = []; + + @queryProviders(TEST_VALUE) + valueProviders: number[] = []; + + override render = () => html`` +} + +describe("`@provide` and `@inject`", () => { + it("works with abstract tokens", () => { + const providerRef = createRef(); + const injectorRef = createRef(); + + render(html` + + + + `, 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(); + const injectorRef = createRef(); + + render(html` + + + + `, 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(); + const injectorRef = createRef(); + + render(html` + + + + `, 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(); + const childProviderRef = createRef(); + const parentInjectorRef = createRef(); + const childInjectorRef = createRef(); + + render(html` + + + + + + + `, 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(); + const concreteProviderRef = createRef(); + const valueProviderRef = createRef(); + const injectorRef = createRef(); + + render(html` + + + + + + + + `, 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(); + const providerRef = createRef(); + + render(html` + + + + `, 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(); + const providerRef = createRef(); + + render(html` + + + + `, 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(); + const providerRef = createRef(); + + render(html` + + + + `, 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(); + + render(html` + + + + + + `, document.body); + + expect(queryerRef.value).toBeInstanceOf(TestQueryer); + expect(queryerRef.value!.valueProviders).toEqual([42, 420, 69]); + }); + + it("dynamically updates", () => { + const queryerRef = createRef(); + + render(html` + + + + + + `, 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]); + }); +}); diff --git a/framework/src/lib/di/types.ts b/framework/src/lib/di/types.ts index 112076f..a882de9 100644 --- a/framework/src/lib/di/types.ts +++ b/framework/src/lib/di/types.ts @@ -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(name?: string): Token { - return new UniqueToken(name ?? crypto.randomUUID()); + return new UniqueToken(name ?? uuid()); } toString(): string { diff --git a/image-generator-demo/src/app/app.element.ts b/image-generator-demo/src/app/app.element.ts index 853f6d6..3ea93a9 100644 --- a/image-generator-demo/src/app/app.element.ts +++ b/image-generator-demo/src/app/app.element.ts @@ -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(); constructor () { diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..e0eccd8 --- /dev/null +++ b/netlify.toml @@ -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;" + diff --git a/studio-web/src/lib/iframe-messaging-bridge.element.ts b/studio-web/src/lib/iframe-messaging-bridge.element.ts index d065c5b..deca809 100644 --- a/studio-web/src/lib/iframe-messaging-bridge.element.ts +++ b/studio-web/src/lib/iframe-messaging-bridge.element.ts @@ -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): 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): 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; diff --git a/studio/src/ui/inspector.rs b/studio/src/ui/inspector.rs index 61ed1ff..851a4bf 100644 --- a/studio/src/ui/inspector.rs +++ b/studio/src/ui/inspector.rs @@ -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::(); app.register_inspect::(); app.register_inspect::(); + + //register standart material + { + app.register_type::() + .add_event::>>() + .add_systems( + Update, + ( + update_inspect_by_select_change::>, + update_inspect_by_remove::>, + update_inspect_by_change::>, + notify_asset_change_system::, + ) + .in_set(InspectorSystem::DetectChanges), + ) + .add_systems( + Update, + serialize_standart_materal.in_set(InspectorSystem::PerComponentCollect), + ); + + app.world.resource_mut::().getters.insert( + StandardMaterial::get_type_registration() + .type_info() + .type_path() + .to_string(), + Arc::new(|entity, path, world, f| unsafe { + let handle = entity.get::>().unwrap(); + let mut assets = world + .get_resource_mut::>() + .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, + Arc< + dyn Fn(&mut EntityWorldMut, &str, &mut UnsafeWorldCell, &dyn Fn(&mut dyn Reflect)) + + Send + + Sync, + >, >, } trait AppInspectable { fn register_inspect(&mut self); + fn register_asset_inspect(&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::().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(&mut self) { + self.register_type::() + .add_event::>>() + .add_systems( + Update, + ( + update_inspect_by_select_change::>, + update_inspect_by_remove::>, + update_inspect_by_change::>, + notify_asset_change_system::, + ) + .in_set(InspectorSystem::DetectChanges), + ) + .add_systems( + Update, + auto_serialize_asset::.in_set(InspectorSystem::PerComponentCollect), + ); + } } #[derive(Default, Resource)] @@ -160,7 +222,7 @@ impl Default for UpdateInspect { 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::() { - 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::() { + 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::() { + 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::() { + 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::() { + 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::() { + 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::() { + 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::() { + 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::() { + *val = msg.value.as_str().unwrap().to_string(); + } else if let Some(val) = reflect_field.downcast_mut::() { + 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::() { + 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::() { - 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::() { - 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::() { - 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::() { - 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::() { - 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::() { - 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::() { - *val = msg.value.as_str().unwrap().to_string(); - } else if let Some(val) = reflect_field.downcast_mut::() { - 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::() { - 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( ev_update_description.send(UpdateEntityDescription); } +fn notify_asset_change_system( + mut ev_inspect: EventWriter>>, + mut asset_changing: EventReader>, + mut selected: Query<&Handle, With>, +) { + for ev in asset_changing.read() { + for handle in selected.iter_mut() { + if ev.is_modified(handle.id()) { + ev_inspect.send(UpdateInspect::>::default()); + } + } + } +} + +fn auto_serialize_asset( + q_t: Query<&Handle, With>, + mut description: ResMut, + registry: Res, + mut ev_inspect: EventReader>>, + mut ev_update_description: EventWriter, + assets: Res>, +) { + 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>, + mut description: ResMut, + registry: Res, + mut ev_inspect: EventReader>>, + mut ev_update_description: EventWriter, + assets: Res>, +) { + 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, mut ev_update_description: EventReader,